robotmay

Everyone should be using low level caching in Rails

Low level caching is very easy to get started with in Rails 3, but it seems to be missing from the official guides. I personally use it all the time to help reduce the number of queries or API calls I’m making. Heroku has a pretty good explanation which, if you’re so far unfamiliar with low-level caching, is a good place to start.

Why should you use low level caching, and what should you use it for? Maybe you have some data which you need regularly over multiple pages, e.g. a list of categories for your blog. You might want to display them on every page but they’re not going to need to be entirely up to date for every request. You could stick this in controllers/application_controller.rb:

If you haven’t used this style of caching before, note that I’m calling .all on the Category query. In Rails 3 ActiveRecord now uses lazy loading; it doesn’t perform the query until you try to use it. This is great for fragment caching in your views as it will prevent that query being called if a fragment cache already exists. However, if you’re using low level caching you’ll want to perform that query immediately so that the results, rather than just the query object you’ve built, are stored in the cache.

The mainstay of low level caching in Rails is Rails.cache.fetch. There’s something you need to know before you end up in a spiral of frustration; by default this will cache in your development environment. To remedy this, add this line to your config/environments/development.rb:

config.cache_store = :null_store

Rails.cache.fetch takes 3 arguments; the cache key, an options hash, and a block. It first tries to read a valid cache record based on the key; if that key exists and hasn’t expired it will return the value from the cache. If it can’t find a valid record it instead takes the return value from the block and stores it in the cache with your specified key. It’s well worth reading the API docs for this method, as it explains all the options you can pass to it.

I use low level caching most for API calls. On Days Out Near Me I use the Flickr API to find photographs of places. To do this I’m using the Flickraw gem:

This searches for a Flickr place based on the latitude & longitude of the one in my database. If it finds any matching places it returns the first entry, as that’s most likely to be the correct one. Unless we’re dealing with a moving castle then it’s pretty safe to say that this place is going to be correct for quite a while, so it’s being cached for 7 days. It could however change if we update our record, or if someone adds it as a new place on Flickr where it was previously missing. By using self.updated_at inside the cache key we ensure that the cache is automatically invalidated if we update the place (which could include new co-ordinates).

Caching works best in layers. This is nicely illustrated by 37signals; don’t just use one type of caching, but instead layer one on top of another. You’ll end up with the same data cached in multiple places, but when the cache expires you won’t be hit with a totally uncached page load. Some of the pages of Days Out Near Me would have pretty horrific response rates if I wasn’t using a variety of tricks to reduce how much time it spends in the database and API calls. I’ll be posting a few more of these tweaks in the near future.

Edit: This article makes the assumption that you’re using memcached or an equivalent (i.e. Redis) for your cache store. I’d highly recommend using the Dalli gem with memcached. Thanks to wavesplash for pointing out that I’d missed this section out.

As this is essentially my first blog post, I would be very grateful for any critique or tips you have.