Ruby is a fast language, and a great one in so many ways, but nothing in this world is truly free. It’s very easy to do things that seem inconsequential but that later can bring your application to a grinding halt. In this post, I’ll outline five important ways that you can avoid some of the most common problems Rails apps encounter.
Before continuing, a disclaimer: do not take these tips and refactor your code ad-hoc. Take everything with a grain of salt and perform your own measurements to determine which pieces of your app are slow. Before making any performance optimizations, get set up with a profiling tool, like RubyProf, New Relic, Scout, etc. You always want to know where the most significant bottlenecks are for you, and focus your efforts there first.
Eager Load Associations
The most common and significant problem that I’ve seen in Rails apps has been the lack of eager loaded associations. A simple extra _:include_ when performing ActiveRecord finds will prevent 1+N queries. So for example, if you are displaying a list of articles on your blog homepage and want to display the author’s name as well, load the posts with Post.all(:include => :author). For those complex pages, eager loading works multiple levels deep. Newer versions of ActiveRecord handle complex eager loading cases much more elegantly by splitting up a large join query into multiple smaller queries that make better sense.
Note: only perform the eager load when you actually plan to use the objects, because there’s fairly significant overhead to creating many ActiveRecord objects.
Do Database Work In the Database
In the same vein as the first tip, try leveraging the database when it makes sense. Relational databases are designed to query large amounts of data and return results; Ruby is not.
For example, if you want to check if the user currently logged in has commented on an article, you don’t need to load all the comments for that article. Iterate through each one, and check whether at least one comment was created by the current user. Doing this will instantiate objects for every single comment and then instantly discard them after the check is done. A much better way to obtain the same result is to push the logic to the database by doing a SELECT COUNT statement. ActiveRecord has an easy way to do this: Article.comments.count(:conditions => ["user_id = ?", current_user.id]) > 0
Do as Little as Possible During the HTTP Request Cycle
You want to be able to return a response to the end user’s request as quickly as possible, so only do the bare minimum needed to return the response and defer everything else. Actually sending out an email is relatively slow and users don’t generally care if emails are sent during the request cycle or right after.
Whether this is implemented using a simple Ruby thread or a robust, distributed queuing system like RabbitMQ doesn’t really matter. Rails 3 will ship with a default queuing system, but until then, I suggest checking out DelayedJob and BackgroundJob.
Know Your Gems and Plugins
As Rails applications get more complicated, a good thing to do is to use existing plugins and gems instead of recreating the work in house. This usually introduces a significant amount of new code to the application that is relatively unknown.
There are many great Rails plugins out there. But before depending on a new gem or plugin, I suggest at least skimming the source — check for any craziness. Also be sure you’re using plugins for their intended purposes — or things are likely to go awry.
Avoid Creating Unnecessary Objects
Every time Ruby’s garbage collector is triggered, Ruby will stop running your code and start cleaning up unused objects. This process can take between 100 and 400ms on MRI (JRuby has a better behaved, tunable garbage collector through the JVM), which is a noticeable period of time. Avoid this as much as possible. This means avoid creating unnecessary objects. I have already mentioned a couple of ways to do this in the previous tips.
In general, the best way to avoid the unnecessary creation of objects is to understand how Ruby and the libraries in use work. For example, understand the difference between these two snippets:
sentences.map { |s| s.strip }
sentences.each { |s| s.strip! }
The first snippet creates a new Array object and a new String object for each element in the Array. The second snippet just mutates the String objects in the Array without creating new Ruby objects.
Granted, this tip only makes a significant difference when dealing with large data structures, but it’s a good idea to keep in the back of your mind whether or not you actually need to duplicate objects. If you have arrays containing thousands of ActiveRecord objects and use reject vs. reject!, you’ve just created a second array which could potentially have thousands of objects.
There are many other aspects of a Ruby on Rails application that can cause bottlenecks; listing them all is obviously impossible. That said, the most important thing to learn is how to locate these bottlenecks. Solving them can be handled on a case by case basis.

Don't get me wrong, I love Ruby, but… how exactly is Ruby a fast language? Fast compared to what? Molasses? Turtles? Almost any other modern language you compare it to is going to beat it in a fair fight, as long as raw performance is the only thing you're looking at.
I thought the whole point was that we didn't care that Ruby was slow because the stuff we're trying to do with it is IO-bound and therefore the speed doesn't matter as much. I mean, that first sentence just really doesn't make for a good introduction to the rest of the post.
ruby is faster than python, php and perl – so the article is right, ruby is fast in its field (dynamic languages, weak/no typing)
it's slower than erlang, scheme, oz or lisp.
and of course it's even slower than java, d, c++ or c
and please DO NOT refer "The Computer Language Benchmarks Game" since it is absolutely UNFAIR, for example some python programs are written using C extensions while ruby ones are written in pure-ruby example: http://shootout.alioth.debian.org/u32q/benchmark…. all those including itertools or multiprocessing are also unfair.
um, ruby is slow. But the great thing is, every ruby programmer knows that.
My point with the introduction is that when writing Ruby, the vast majority of an actual ruby application (discounting network operation) is not spent in the actual language (method dispatching) but in data structure manipulation (String, Hash, etc… methods). The fact that ruby makes it so easy to chain these operations and abundantly use features offered by these data structures is usually what slows down your average app. If one were to write an app in ruby using C idioms (initialize Arrays to the correct length, do offset arithmetic, …) vs. the same app using ruby idioms (my_arr.map.flatten.compact), and the first version will most likely be quite a significant amount faster.
Rails 3 is shipping with a default queuing strategy? How did I miss that one? Anyone have any additional information on it?
I'm also curious about this built-in queuing in rails 3. Is this the type of thing where the notion of "put this message on a queue" will have a standard rails syntax, and you'll be able to easily switch out different queuing implementations (ala ActiveRecord and SQL databases)?
@zach we're still tossing some ideas back and forth, but the basic idea is to have a queuing solution that is similar conceptually to Rails' caching support. Since so many Rails applications require queueing, the idea is to provide a standard way to express the concept, while still allowing you to retain the freedom to choose which backend you will use.
u r right
@charlieok exactly right
Good handful of tips. However, the n+1 example isn't good. Fetching a Post and its Comments is only 2 queries (the :include doesn't really buy you anything in this example). Fetching a list of Posts and the Author of each post would be n+1, and the :include would reduce that to 2 queries.
@ Zach, @charlieok RE: queuing in rails 3: It has become apparent that queuing has become a standard operation of the modern web application. Whether it is simply queuing an email notification to sent or managing complex daemons, it only makes sense to abstract the concept.
We are working on this problem and the goal is to ship a working system with Ruby on Rails 3.0. If you have any feedback, please feel free to email us or post a ticket on lighthouse (<a href=”https://rails.lighthouseapp.com” target=”_blank”>https://rails.lighthouseapp.com — we are checking it every day).
"Avoid Creating Unnecessary Objects"
Not to be ignorant, but is that really true?
It seems like its not according to this presentation at RubyConf 2008:
"How Ruby Can Be Fast",
by Glenn Vanderburg,
http://bit.ly/NTsXe
see minute 6:53 to 18:27
The summary is: that if done well, Allocation is very cheap, Collection of young objects are essentially free, and Only long-lived or large objects (such as Session objects) are expensive.
Or maybe I just misunderstood the presentation… Anyone willing to explain it deeper?
@Ilya The main point of that tip is to avoid creating unnecessary objects because the garbage collection phase is slow. I did not mention object allocation.
@Jeremy You are correct, I will update the example. Thanks.
@ilya Glen was talking about the effects of using a well-done generational garbage collector, which MRI does not currently have (outside of JRuby and Rubinius). As you cited, he says that allocation is very cheap if done well.
@carllerche, @wycats Thank you both for your replies…
Any resources or suggestions where I can get more information on rail's or ruby's methodology for this? As far as the app architecture or design is concerned…
[...] 5 Ways to Speed Up Your Rails App | Union Station [...]
good article, thanks
[...] 5 Ways to Speed Up Your Rails App | Engine Yard Blog (tags: rails performance optimization rubyonrails tips) [...]
Of course none of this matters unless you benchmark and use performance tools first to find out where the problems are.
@darkhelmet Which is why Carl said: "Before continuing, a disclaimer: do not take these tips and refactor your code ad-hoc. Take everything with a grain of salt and perform your own measurements to determine which pieces of your app are slow. Before making any performance optimizations, get set up with a profiling tool, like RubyProf, New Relic, Scout, etc. You always want to know where the most significant bottlenecks are for you, and focus your efforts there first."
@dave "and please DO NOT refer "The Computer Language Benchmarks Game" since it is absolutely UNFAIR, for example some python programs are written using C extensions while ruby ones are written in pure-ruby … all those including itertools or multiprocessing are also unfair"
Dave – contribute a Ruby program that uses GMP.
Dave – contribute a Ruby program that uses multiprocessing.