

Rails Performance Needs an Overhaul - igrigorik
http://www.igvita.com/2010/06/07/rails-performance-needs-an-overhaul/

======
patio11
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.

~~~
alec
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.

~~~
jamesbritt
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.

~~~
rufugee
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...

~~~
jamesbritt
Very interesting. Thanks.

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.)

------
stanleydrew
" It just baffles me that [Rails scalability] is not a solved problem
already."

Maybe if they open-sourced this "Goliath" framework they created we'd be a
little closer.

~~~
trebor
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).

~~~
igrigorik
Yep, here's the async activerecord mysql driver:
[http://www.igvita.com/2010/04/15/non-blocking-
activerecord-r...](http://www.igvita.com/2010/04/15/non-blocking-activerecord-
rails/)

It's a start.

~~~
stanleydrew
Sweet. Checking this out now.

------
allertonm
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?

~~~
jules
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.

~~~
allertonm
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.

~~~
jules
Why is yielding to a coroutine from inside a callback a problem, but calling a
continuation isn't?

~~~
allertonm
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.

And it appears that Ilya (the OP) has been looking at this approach using
fibers too [http://www.igvita.com/2010/03/22/untangling-evented-code-
wit...](http://www.igvita.com/2010/03/22/untangling-evented-code-with-ruby-
fibers/) \- and the comment thread on the OP suggests that this is in use at
Postrank.

~~~
jules
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.

------
aarongough
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.

~~~
ryanhuff
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?

~~~
FooBarWidget
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.

~~~
aarongough
Exactly...

------
grandalf
Is this article applicable to Rails 3?

Why not just handle expensive parts of one's app with a simple rack handler?
Do you really need form helpers, etc. in every part of the app?

~~~
aarongough
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.

~~~
grandalf
True.. not sure Rails necessarily needs to be the right tool for every job,
though. If I had a workflow like that I would probably not use Rails.

~~~
aarongough
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.

------
bradgessler
<http://www.espace.com.eg/neverblock/> is the closest thing I've heard of to
an async Rails framework.

