3 simple tips to make your Rails app 100 times faster

After the initial meltdown of the RefactorMyCode server I had to find a solution to put it back online fast!

Here are 3 things I did that helped me put the app back online and made it amazingly fast serving more then 50 000 page load in a day, even if it was hosted on a shared hosting plan on DreamHost. Not the fastest provider but the cheapest and most supported for sure. I since then switched to SliceHost on an account graciously offered by StandoutJobs (that’s right, for my personal project, how cool is that?).

I know lots of people have wrote guides to make your web app faster, but I tried, with this one, to focus on the simplest, yet most effective, things you can do to dramatically speed up your app without loosing any of Rails and Ruby tasty sugar. I think even people with little experience with Rails and web deployment can follow this guide with no problem.

1. Pack that asset up

First tip is the easiest to put in place. Downloading your 3 stylesheets and 6 javascript files slows down page loading because the web browser has to open a new connection for each one. Plus some browser limits the number of files that can be downloaded from a same host at the same time.
The solution is to reduce the number of external resources your page depend on by merging all of them into one file. AssetPackager is great for doing this in your Rails app, plus it minifies your javascript for free, check out RefactorMyCode stylesheet and javascript file. All that by changing one line in your view layout!

Now to make it load even faster you want to move all your static files to another host (see why here). For example on RefactorMyCode, images, the stylesheet and javascript are loaded from assets.refactormycode.com.
With Rails, this is stupidly easy. In your config/environments/production.rb, set config.action_controller.asset_host to your assets host. Now all the image_tag, javascript_include_tag, etc will point to this host. On your DNS server, all you need to do is setup an alias (A record) pointing to the same IP as the main domain.

2. Remove unused stuff

Something to keep in mind while trying to improve performance is that, for each request, everything will be loaded, checked and executed. Authentication, session, database queries. Do you really need all this stuff on every request?

You can turn off session on specific actions with:

session :off, :only => :index

This boost performance like crazy. So it’s a great alternative to caching if you really need to go to the database but not to login the current user.

If you’re using RestfulAuthentication or ActsAsAuthenticated plugin, you can turn off loading the user form the cookie with:

skip_filter :login_from_cookie

which will save you one query.

Use fragment caching for partial views with heavy data from the db and skip your queries on the controller side with:

@users = User.find('all) unless read_fragment('unique_cache_key')

Also with queries, make sure you know how to use the :include option for eager loading of associations.

Post.find(:all, :include => :user)

will cut the number of queries in half.

3. Cache the whole page

This last tip is by far the most effective. Cache everything you can to reduce queries to the database, or even, requests to the dispatcher.
The best solution is caching the whole page into and HTML file so the web server serves the static file rather then sending the request to the dispatcher. Most web server are serving static files faster then Lance Armstrong on drugs. And can serve lots and lots of them, letting your dispatchers breath a bit between requests. Rails Envy as a great guide on how to set this up.

Now the hard part is caching the layout. To cache a full page, the page needs to be the same for every users, logged in or not. To do that, you have to remove all those <%= ... if logged_in? %> and show everything, at least in the HTML. You still can use javascript to show and hide links that are for logged in users. For example here’s a code sample from RefactorMyCode:

var CurrentUser = {
  loggedIn: false,
  author: false,
  admin: false,

  init: function() {
    this.loggedIn = Cookie.get('token') != null;
    this.admin    = Cookie.get('admin') != null;
  }
};

var Application = {
  init: function() {
    CurrentUser.init();
  },

  onBodyLoaded: function() {
    if (CurrentUser.loggedIn) {
      $$('.if_logged_in').invoke('show');
      $$('.unless_logged_in').invoke('hide');
    }
    if (CurrentUser.admin) {
      $$('.if_admin').invoke('show');
    }
  }
};

Another thing you wont be able to use anymore on your layout is <%= flash[:notice] %>. The flash changes on each request, not cacheable. The solution again is to transfert everything to the client with cookies and javascript. Cacheable Flash is a nice Rails plugin to do just this. Cool thing is, you don’t need to change anything on your controller. Just replace your previous flash ERB block with something like this:

<div id="error_div_id" class="flash flash_error"></div>
<div id="notice_div_id" class="flash flash_notice"></div>
<script type="text/javascript">
  Flash.transferFromCookies();
  Flash.writeDataTo('error', $('error_div_id'));
  Flash.writeDataTo('notice', $('notice_div_id'));
</script>

I hope this was helpful to you. Let me know if you got more tips or questions on how I’ve setup my server.

11 Comments

Filed under rails, tips, tutorial

11 responses to “3 simple tips to make your Rails app 100 times faster

  1. Darius Damalakas

    I don’t do web development, so that was very interesting. Some topics like eager loading, and serving html are just common, but the others were new and helpful
    thanks

  2. Great tips!

    I really like your way of managing the “per user stuff” with javascript.

    That post is going on my del.icio.us right now, there’s no question about that😉

  3. Thanks Darius and Frank,
    What other do you do in your app to make it faster ?
    That was actually the first time for me I had to think about this.

  4. Excellent! Thanks for sharing this info Marc-André.

    One more thing to keep in mind: if the controller queries the database using conditions that compare fields, you most likely want to have SQL indices on those fields (depends on the kind of field and amount of data in the table – of course).

    Another thing where CPU time is lost is when writing to the log files. The query params are logged and therefore if one of those params is a long text area, the controller has to log more data. A handy way to prevent that is to user “filter_parameter_loggin” in the controller. For example, if I don’t want the field “full_description” to be logged:

    filter_parameter_logging “full_description”

  5. webmat

    Hi Marc,
    I don’t know if you’ve seen it, but there’s an article by Todd Hoff at highscalability.com. It speaks about how they managed to make Twitter 10000% faster🙂
    http://highscalability.com/scaling-twitter-making-twitter-10000-percent-faster

  6. @hugo right, Im really curious what’s the real impact on speed of using index on column, 5, 100 times faster ? Depends on the ammount of record I guess. And about writing to log files, that is brilliant! I though this was usefull only for hiding password in log files.

    @mat Yeah I red the article, but sadly none of the tips was usefull to me, I can’t run 180 mongrels instances. With 5 Im already on the limit of 510 MB or ram. And it’s not the same kinda of traffic neither (600 requests per second, holy shit!!!). Did you find any usefull tip in there yourself ?

  7. Pingback: Rails performance: development environment is dog slow — Some French Guy

  8. Don’t you think that just hiding sensitive links and data with javascript results in some security problems? I mean, even if the invasor can’t get on these links, I believe you should always provide the least information that you can to unauthorized users. What do you think?

  9. If your security is based on obscurity (not showing info to the user) that is bad whatever you do.

    There’s always a possibility a user type in a URL even if you don’t expose it to the web.

    The true solution is to secure everything on the server, not the client.

    I hope this make sense to you Dante, thx for the comment!

  10. You’re absolutely right when you say that obscurity is bad if you base your security on it. I just mean that it can work as a second layer of security: if the invader don’t know if it is /admin or /administrator (just a wild guess here) it will take him twice the work to break your security. I mean, I feel more comfortable on not giving away links to important sections of the website (not that we should hide it with bad, bad frames or stuff, since it’s useless), but just not giving it for free.

    Anyway, that’s just a personal feeling. If everything is secured on the server, that should be hard enough to break to make people give up before they get into (which they will, eventually), and we must sleep with that!🙂

    It’s a good tip, though, not something that I’ve thought of before. I should probably test it on my apps! And not only this one, but the other two! Thanks for that!

  11. Hello my loved one! I want to say that this article is awesome,
    nice written and come with almost all significant infos. I would like to peer more posts like this .

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s