One of my rails apps. sizeasy has recently been getting consistently more traffic thanks to a bit of search engine optimisation. I noticed a performance hit on my server with the more constant use and decided it was high time I got serious about caching. I could have done this much earlier but I’m a strong believer in the scale later philosophy , to me as long as you are aware of ways you can scale and optimise and you don’t tie yourself up to start with then you just get more bang for your buck if you do it when it needs doing. Anyway turns out caching in rails is another one of those “is that all I have to do” situations even with my slightly uncommon requirements…
Action Caching
First off sizeasy does lots of server side dynamic image generation and it won’t surprise you to hear that this is slightly memory and cpu intensive and something you want to minimize to the extreme. So basically if I’ve created an image before I just want to cache it and never have to create it again. Seems sensible enough but although Ive used rails action caching before I didn’t think it would work for image outputs. How wrong I was simply do
caches_action :draw_cube
and it works out of the box. Well almost. What makes a cube image unique is its dimensions and its colour which are all passed over as parameters on the image request. By default the action caching in rails doesn’t take into account parameters on the query string so I needed something that did. Plugins, plugins, plugins, rather like Firefox and its extensions something that makes rails so attractive is its community and in turns its plug-ins. So often I find someone has already done it before and I can just plug it in. Were still talking quite low level here not application level e.g. plug-in a blog and plug-in a forum (however a framework for making this kind of thing possible would be awesome) no more coder level chunks that can still save serious developer resource. So anyway back to caching, yes the plug-in I used was the query string action caching plug-in which once pluged in requires no code changes at all it just means now the action_cache mechanism uses the entire url including query string to create and uniquely identify cached content.
Page Caching
So that was the main hurdle crossed query string specific image caching. My app started flying. But I could do more. There are several pretty much static pages in the app for help, instruction, buzz etc and these were still hitting the application server. Well these situations are even easier to deal with. Just a simple one liner and after the first time these guys are just served up as static pages.
caches_page :buzz, :tips, :featured
Partial Caching
People hit the homepage a lot and often its there first impression of the site. We want this to load quickly and make an impression rather than chug away loading and giving them plenty of time to hit the back button and try somewhere else. On sizeasy’s homepage we have a latest and most popular comparisons list which hit the database to populate these lists. Again it seems a waste to hit the database every time a homepage is displayed when these views don’t change anywhere near as often as the page gets displayed. Enter, fragment caching. Simply add a cache block around an arbitrary piece of template code and this chunk gets cached and not regenerated until the fragment is explicitly expired. So the code looks something like this…
<% cache(:action => 'sizeup', :part=>"latest" ) do %>
<%#Put your fragment cacheable code in here %>
<% end %>
Now obviously we need this to change whenever new comparisons are saved so in the action that saves a new comparison we simply need to add a line of code to expire this cache fragment.
expire_fragment(:action=>'sizeup', :part=>'latest')
Now that wasn’t so hard was it.
Summary
So in Rails we have page caching, action caching and fragment caching and by default these caches are all stored as files. Page caching is the quickest as requests don’t hit the application server at all, they are simply served up as static files. Then there is action caching which does hit the application server but if you have database lookups or image generation then you are likely to see massive speed increases from action caching rather than not caching. Fragment caching is also very useful especially in many web 2.0 sites that like to have panels with lists of recent, popular, random stuff on various pages. Fragment caching can easily minimize the amount of times your server has to do database lookups and partial page generation. All in all caching is dead simple in Rails but still I think optimise later is a great approach to getting things done. So caching for many of us comes down the line. Just one more thing.
If you love Capistrano like I do then you may want to maintain your cache between incremental releases. So I added a rake task for creating symlinks and called it in deploy.rb :after_before_update. Here is the rake task…
desc "symlink to shared"
task :symlink_to_shared do
rel_path = ENV['RELEASE_PATH']
share = 'shared'
dir = ENV['DIR']
share_path = File.expand_path("#{rel_path}/../../#{share}/#{dir}")
unless File.exists?(share_path)
system "mkdir #{share_path}"
end
puts "ln -s #{share_path} #{rel_path}/#{dir}"
system "ln -s #{share_path} #{rel_path}/#{dir}"
end
And here is where I call it
task :after_update_code, :roles => :app do
run <<-CMD
cd #{release_path} &&
rake symlink_to_shared DIR=cache RELEASE_PATH=#{release_path} &&
rake deploy_edge REVISION=#{rails_version}
CMD
end





Sorry, comments are closed for this article.