Thoughts on Caching

Published July 1, 2009 in Rails

I’m sure this has been said a thousand times in a thousand places, but it was a revelation to me:

Don’t delete memcached keys.

Instead, focus your caching strategy on including a timestamp. If you have a direct mapping between a model and a cache (say a fragment cache), then include that record’s updated_at.to_i in the cache key. If you don’t have a direct mapping, or if there’s a hierarchy of records that could require cache expiration, store a cached_at timestamp on the most relevant item.

For example, if you have a list with multiple items, each of which might have an attachment, you could have an after save hook (either in an observer or in the model itself) that updated the cached_at stamp on the list. If your updated_at times are important, try

List.update_all ['cached_at = ?', Time.now.utc], ['id = ?', @list.id]

Which conveniently updates just the record in question without messing with updated_at or, importantly, setting off any callbacks.

If you have multiple records that relate to a particular cached entry, include the timestamps for both.

This strategy works best with memcached, since older cached items will just work their way out of the cache “at some point.”

Here’s a variation (far more verbose, but not dependent on helper methods like try and soft_send) of cache_key method that I’m using for this pattern:

def cache_key(*items)
  items.map do |item|
    if item.is_a? ActiveRecord::Base
      if method = %w(cached_at updated_at).detect{|m| item.respond_to? m}
        cache_time = item.send method
      end
       
      [
        item.class.name.underscore,
        item.to_param,
        cache_time
      ].compact.join '-'
    else
      item.to_s
    end
  end.join '/'
end

This is way more sane than trying to hunt down all of the cached items that refer to a particular item (say every user has a cached item for a list and you want to expire all of them when you change the name of the list). Trust memcached and change your cache key instead of deleting the cached items.