I run Rails -- minimally six processes of memory chewing lovin' at any time, plus the assorted DBs and cache stores -- and spend, oh, call it $2,000 a year more on my 1.5GB VPS than I would need to for equivalent functionality for Java.
I get a metric truckload more than $2k of developer productivity out of using Rails instead of Java, though.
If my traffic increased such that I needed 40 Mongrels and 20 Delayed Job workers and I was spending $20k a year on hosting, I'd... I'd... I'd really, really like if that happened, actually.
But he's not saying "we should all switch to Java to save on hosting costs." He's saying he wants it to be fast and still be Rails.
As much as the extra $2k of hosting is worth the productivity boost, wouldn't you love it if you could have the Rails productivity and save that $2k/year compared to current Rails performance?
I've recently started toying with the Play Framework, a Rails-alike written in Java. The other Java web development tools I'd seen all primarily featured a suffocating bureaucracy, but this one pulled a number of tricks to appear almost as concise as Ruby. My take-away was that Java didn't have to be as repetitive as I'd thought, just that most of the Java libraries were written to encourage that. Maybe that's easier to make Java more productive than Ruby more efficient.
I'm curious if people who say they're using some Java framework for the speed in place of some Ruby framework. which they would prefer for the language, know about JRuby?
And if they do, why not use the Ruby app of choice under JRuby? Pretty much all of the dozen+ Ruby Web frameworks can be bundled up as WAR files and dropped off for Glassfish (or tomcat or whatever) to handle.
If you look at the results in the link I posted, you'll see that Play ends up being 6 times the speed of Rails. I know about JRuby and have used it much, but that sort of speed improvement can't be ignored, especially considering how close Play comes to making Java beautiful...
Almost missed seeing the Snap and Play stats because the preformatted text was getting formated into a side-scrolling pane.
Not that I want to code in Java, but the speed is impressive.
Still, I have to wonder what the JRuby speed would be for some of the other Ruby frameworks that are faster than Rails, such as Ramaze and Sinatra. (I doubt they would beat those Play numbers, but they would still do better than Rails. A Duby version of Ramaze might rock.)
Doing this at current job. Deploying Ruby on Rails as a WAR on Tomcat. I'd argue it's one of the fastest way to deploy especially using Warble. Also the speed is not too shabby either.
Fast. I initially had some set-up issues making sure the MySQL jars were found and tweaking Glassfish settings.
Also, development of Ruby + Glassfish almost always meant Rails + Glassfish. I prefer Ramaze, and had to hack a few things (in warbler, for example, and the glassfish3 gem) to get around some things that that assumed you were using Rails.
If you are using one of the anointed frameworks (i.e. Rails or Sinatra) you're probably OK. If you are coloring outside the lines you may have issues. But I've not looked into it much since I got my site running (though now I'm just running it as a rackup app through jruby)
At this point though I'm looking at using Haskell when speed is a concern. But that's a whole other thread ...
Agreed, that's a sweeping statement to make on Rails scalability. Just trying to make a point though. :-)
As for Goliath - we will. Time is the scarcest resource in a startup, and that's why haven't opened it up already. We need to get some documentation in place, and remove a few dependencies.
Point is though, there is nothing inherently novel in our stack! All off the shelf pieces: thin, async-rank, async middleware, em drivers + em-synchrony.
To be clear, I understand fully that time is limited in a startup. I'm not sure you should spend the time to clean up and generalize your Goliath stuff. It may not be worth it.
But in the context of this post, it just came off as a little bit "holier-than-thou" to hear you say more-or-less: jeez everybody, what's your problem? We've solved this so why is it taking everyone else so long to catch up?
Now I believe you didn't mean it that way, but it seems like a helpful way to push things forward is to release bits and pieces of what you have, or at least write up a bit more detailed descriptions of the various pieces. That would be very helpful :)
I think his point was that Postrank needed performance characteristics that are not consistent with Rails' strengths. While the article came off as a rant (probably to help with publicity) it was more of an ad for Postrank and possibly for Goliath.
Rails is released under the MIT license, Stanley. It's been open-source for a long time. Fork them and make whatever improvements you want and start a push request.
Instead of complain about how slow it is, why not attempt making an "evented" version of Rails? Enable thread-safety for Rails to get a speedy application, even on a shared server.
Frankly, I can't write code for EventMachine. Just looking at its style makes me cringe: there's just something about the design of EM apps that I can't compromise with (and it ain't the fact that it's evented).
This completely misses the point of what I wrote. I didn't complain about how slow Rails is. The original post does. I'm saying that we could all use some help in improving Rails performance so why not release Goliath as open-source? Sounds like that is in the pipeline though based on other comments in this thread.
Good piece - having built a big Rails application, I love the speed of development but your comments about scaling totally resonate. Actually it was even worse in my case due to us using jruby (add VM overhead to the mix.) RAM is often the most expensive component of the cost to run an application and as things stand now Ruby/Rails makes it hard to keep those costs down.
I really like the idea of using Ruby's continuations to avoid the need to write all of your logic in CPS (ala node.js.)
From the memory standpoint, doesn't this just move your memory overhead from the machine stack to continuations on the heap? I do realize there are some advantages to heap over stack, especially on 32-bit architectures.
And in addition, what kind of overhead do you get when creating a continuation for the kind of deep call stacks you're dealing with?
Continuations are not the best solution for not writing asynchronous code in CPS, not in terms of ease of use and not in terms of performance. Coroutines are cheaper as they don't involve stack copying or elaborate compilation schemes, and they map more directly to the problem.
That said continuations don't necessarily involve stack copying. The implementations of continuations that don't do make normal procedure calls slower as far as I know.
Perhaps I'm being a bit dense, but it's hard to see how one could take an existing asynchronous API and adapt it to use co-routines, since the point where you'd ideally want to "yield" is behind the API - and it may not be desirable or even possible to change this (for example, changing a C-based driver to use Ruby-level co-routines would be a challenge.) OTOH it's fairly straightforward to store a continuation and resume it in a callback.
So while I think you're correct, pragmatically I suspect this approach has more chance of being implemented.
Yielding inside a callback isn't a problem. I was actually thinking of the other yield you would need, the equivalent of suspending the continuation - but I later realize that this is no harder than the continuation case. So I was just being dense, my apologies.
The problem with continuations is that they can be called multiple times, so before invoking it you have to copy the call stack associated with it and execute on the copy.
Coroutines don't require copying anywhere. Creating a coroutine can be done in a couple of instructions (allocate a new stack and set the top of the stack to the code of the coroutine), and yielding is also just a few instructions (mov a new stack pointer and return).
Essentially if you view coroutines and continuations as data structures then continuations are a functional / non destructive version of coroutines, and like other functional data structures they are slower.
I don't know how Ruby fibers are implemented but they seem to have coroutine semantics, so they are probably very efficient as well.
Adapting a callback library to coroutines is very simple. You have a user coroutine that runs the user code, and you have a system coroutine that manages the resource that calls you back when it's ready (e.g. downloading a web page). When the user code requests something from the resource it yields to the system coroutine. When the system has the response ready the system yields to the user code again.
This is nearly the same for continuations, the difference is that yielding involves copying the stack of the continuation that is yielded to.
If you consider the stack as an implicit closure-like argument, coroutines are continuations for the purpose of cps (no resuming from an earlier activation record).
This is actually a very timely article. I built a web-service in Rails over the weekend that has turned out to be much slower in production than I had anticipated...
The catch is that it spends 96% of it's time waiting for network responses. 10ms in the application code, 289ms waiting.
So now I either re-write the application in a language more suited to the task (node.js) or I work to build async capability into the current app. Personally, I'll take the second if it's not stupidly hard.
Did you use Rails 3 and Ruby 1.9.1? One of the benefits of upgrading to the latest version of Rails and Ruby is supposedly performance. As many people haven't yet converted to the new version, I wonder how much of this discussion is a byproduct of running the older software. Thoughts?
He's waiting for external network I/O. It's about concurrency, not performance. Even if he wrote the app in C it's not gonna help him unless he increases concurrency quite a bit.
I didn't go with Rails 3, I like to keep at least a minor version behind so I don't have to deal with framework bugs in production :-p
In my case the performance issues really have nothing to do with Rails and everything to do with blocking I/O. Rails 3 and Ruby 1.9 may be faster, but that doesn't help you at all if you do something like this:
class ExampleController < ActionController::Base
def index
sleep(1) # contrived example, imagine a slow API call here
render :text => "Completed!"
end
end
Because Rails is setup to be run in a blocking fashion my example will only be able to serve 1 request per second. The fix here is non-blocking languages (like Node.js) or programming in an event-driven fashion (by using EventMachine or the like). For example (in pseudocode):
when( request.received ) do
register slow_api_request, :callback => :answer
end
def answer do
response.send( api_result )
end
Ideally the framework should do this by itself. Standard libraries that are blocking, like Net:Http, should continue to appear to the developer like they are blocking, but in reality would pass back control to other instructions while they're just sitting around waiting for the remote server to respond.
This is totally possible, and getting more feasible by the day. Thin (the application server) and Rack both allow applications to run asynchronously, Neverblock provides a cleaner way for providing pools of Ruby Fibers to an application to allow implicit scheduling, in time Rails will hopefully build upon this. I've spent several hours this morning doing research into how I can fix this problem for my application, some of the stuff I've dug-up is here:
It looks like the solution in my case is going to be something like creating a Rails Metal endpoint for Rack, that completes the Net:Http requests using em-http-request. I'm not sure that this is actually possible yet, but it'll be something close to that.
Are you able to cache the results of the external HTTP calls (assuming that's what you mean by 'network responses')? I did that for one of my Rails apps and it's handling tons of requests fine.
The issue is that there is a lot of possible external calls that could be made. Therefore huge traffic for one particular item is not a problem, but moderate traffic for lots of different items is a huge problem.
I am able to cache the results (and I already am) but I don't want to keep the cache too long (it's setup to expire after 30 minutes at the moment and that's as high as I would like to go). I'm running on Heroku (which uses Varnish for HTTP cacheing) so once the cache is populated I can handle tons of traffic.
Ideally I would like to combine non-blocking HTTP requests with HTTP cacheing. I think the final goal should be the capacity to handle around 50 uncached requests per second.
Using a Rack endpoint only reduces the overhead for that specific expensive call. For example if you have a Net:Http.get_request in a Rack endpoint that takes 3 seconds to complete then that Rack endpoint isn't going to be any faster than writing a Rails controller action that does the same thing.
If the whole thing were handled asynchronously though you would be able to handle dozens of requests (if not hundreds) in the same time that blocking NET:Http.get_request would take to complete.
That's totally true. And as I said in another comment I have come up against this exact problem and had considered switching to Node.js to fix it.
But when it comes down to it, hacking Rails to do async requests is going to teach me a lot about how Rack and Thin work, so that's why I'm continuing to push in that direction.
I get a metric truckload more than $2k of developer productivity out of using Rails instead of Java, though.
If my traffic increased such that I needed 40 Mongrels and 20 Delayed Job workers and I was spending $20k a year on hosting, I'd... I'd... I'd really, really like if that happened, actually.