Seven Advanced Rails Fragment Cache Techniques

January 18, 2013 Link to post  Permalink

Scattered puzzle pieces next to solved fragment

The Rails Fragment Cache is the easiest way of making your view template code run fast. A fragment is a piece of generated output, usually HTML, that is a part of the total output, usually the HTML page, that is returned to a user. The documentation on caching with Rails explains the basics, but doesn’t go into any depth, especially for new use cases and techniques that have been developed and expanded over the last year or so.

Here are six techniques and tools that can make your fragment caching easy to manage yet provide the benefits of turbo-charging your view rendering.

Don’t Expire Fragments using expire_fragment

Frankly, don’t use this. Trying to expire fragments correctly is hard to get right when you originally write the code, but becomes impossible once you start adding more content to your pages, and re-using fragments across pages.

Expire by time

egg_timer

If you are using a memcache store for your Rails cache store - and your really should be - then you have the option of expiring fragments by time. Here is an example:

<% cache @post, :expire_in => 2.hours %>
  <%= render @post %>
<% end %>

For some use cases, this technique is very useful. If you have some code that pulls data in from an external data source, e.g. a Twitter feed, then you can’t always know when new data is available, but you don’t want to make the request for every page view, expiring the fragment by time is a good compromise.

Generational (or Key) Expiry

The easiest way to get cache expiry work is to not have to expire anything at all. Using the behavior of memcache and its LRU algorithm for removing old data, we can build a naming scheme for our cached fragments that changes when the underlying data changes.

This is simpler system because the name for a cached fragment is used in one place, whereas with a system with explicit expiry, like expire_fragment, the references to any specific fragment are in at least two places in your code, and as code evolves over time, and you patch those corner cases for cache expiry, you may find yourself with 3 or more places where cached fragment handling is performed.

The basics of how to do key based cache expiration were written up at the 37 Signals blog. The quote at the top about Cache Invalidation and Naming Things is a good one. Fragment Caching actually involves both problems - yet with the right naming scheme, the invalidation problem is improved immensly.

Advanced Generational Expiry

To be able to cache as many view fragments as possible, you need to be able to cache fragments that contain dates and times, as well as user specific display data.

If you want to display relative dates on a page, yet cache the fragments that contain those dates, then you’ll need to run some Javascript on the page to process the date information on the client. 37 Signals wrote about how to do that. There’s a JQuery plugin called Timeago that can help with this too. I’ve used this technique with some success on a recent project.

The general case here is that you can put some placeholder information into your view fragment, and then use Javascript on the client to replace these placeholders with specific data as the page renders.

What about templates changes?

04022008

When you start putting fragments inside of bigger fragments, you are now doing Russian Doll caching. Typically, you will need to have a template version number as part of your cache key, and you manually update this every time you change some template code inside that file. With Russian Doll caching, you have to change the version numbers of all the templates that contain the one you just updated too. This process is extremely error prone - in fact, I don’t think I’ve ever got this right the first time I’ve made changes.

In the example below, if I edit the post.html.erb template, I need to change the version number, and then I also need to update the version number in the index.html.erb file.

# index.html.erb
<% cache [ "v1", @posts ] do %>
  <%= render @posts %>
<% end %>

# post/post.html.erb
<% cache [ "v3", post ] do %>
  ...
<% end %>

The Rails 4 team have created a new gem called cache_digests that automates this process. An MD5 hash of each template is generated (and cached!), and all the template dependencies are included in the process. If the gem can’t work out dependencies from the code, you can add a comment that tells it what to do.

Note: When you use the cache_digests gem, the use of vX will actually disable the cache_digests functionality.

Collection fragments can be slow

In the above example, every time a post is rendered, Rails reads the fragment from the cache. If you have, say, 20 posts per page, then you will make 20 calls to memcache to retrieve the cached fragment for that post. This is very inefficient, and we can now do better.

Recently, Nathan Kontny published a new gem that works great for cached fragments for collections of objects. The gem is called multi_fetch_fragments.

Here is our updated example

# index.html.erb
<% cache [ "v1", @posts ] do %>
  <%= render :partial => 'post/posts.html.erb', :collection => @posts, :cache => Proc.new { |post| [ "v3", post ] } %>
<% end %>

# post/post.html.erb
  ...

Now, a single memcache call will be made (if the outer fragment isn’t found), and only the post fragments that are missing will be rendered.

Include Localization!

I’ve only seen this in one place on the internet, but if your site is localized, then you must make sure to include the user’s current locale in all of the fragment cache keys in your templates.

# index.html.erb
<% cache [ "v1", @posts, @user.locale.to_s ] do %>
  <%= render :partial => 'post/posts.html.erb', :collection => @posts, :cache => Proc.new { |post| [ "v3", post, @user.locale.to_s ] } %>
<% end %>

Summary

The basic usage of Rails fragment caching is actually hard to get correct, but once you start using the above techniques, you can get great performance for your view rendering, but also have the caching mechanism be manageable and consistent.

Play around with the available gems and see how much happier your customers are when you improve the speed of your application.

The first issue of my Faster Rails Newsletter is due to be released at the end of January. Use the sign up form to the right now to ensure you get it when it is available.