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.