Russian Doll Caching & Cache Digests [Rails 4 Countdown to 2013]

Posted on

This post is part of a series of 31 Rails 4 articles being released each day in December 2012.

What is Russian Doll Caching?

The technique of nesting fragment caches to maximize cache hits is known as russian doll caching. By nesting fragment caches, it ensures that caches can be reused even when content changes. When a change occurs to the top-most fragment cache, only that cache must be expired. Every nested cache of the parent can be reused, which provides a significant performance increase. A change to the most nested fragment cache would start a chain reaction to expire all parent caches.

Example

Let's look at an example of using the russian doll caching technique. We are going to have a Team model which has many Members. When we display a team, we must also render out member names and biographies.

class Team < ActiveRecord::Base
  has_many :members
end

class Member < ActiveRecord::Base
  belongs_to :team, touch: true
end

Adding the :touch option to belongs_to :team on the Member model, ensures that when a member is changed, we update the Team model as well. This is essential for using russian doll caching, as you must be able to break parent caches once children are modified.

The view template matches the hierarchy of the models. The team is the parent fragment cache, while the collection of members are its children.

<!-- app/views/teams/show.html.erb -->
<% cache @team do%>
  <h1>Team: <%= @team.name %></h1>

  <%= render @team.members %>
<% end %>

<!-- app/views/members/_member.html.erb -->
<% cache member do %>
  <div class='member'>
    <%= member.name %>
    <p><%= member.bio %></p>
  </div>
<% end %>

If we had a team with two members, a total of of 3 fragment caches would be written:

  • views/members/1-20121220141922
  • views/members/2-20121220141922
  • views/teams/2-20121220141922

The key is composed of the concatenation of the view path and the model cache_key. The cache_key returns a combination of the id of a model, plus a timestamp of the last time it was updated. This combination ensures a cache will always be expired if a model is updated.

The above technique will work seamlessly until you have to modify the template of one of the fragments. Since the template is not taken into account in the fragment cache key, any changes to the template will not expire the cache. This quickly progresses in prefixing the fragment cache keys with a version , so that an expiration will be forced. A change in a nested fragment version, will result in all parent versions needing a version bump also.

<!-- app/views/teams/show.html.erb -->
<% cache ["v1", @team] do%>
  <h1>Team: <%= @team.name %></h1>

  <%= render @team.members %>
<% end %>

<!-- app/views/members/_member.html.erb -->
<% cache ["v1", member] do %>
  <div class='member'>
    <span><%= member.name %></span>
    <p><%= member.bio %></p>
  </div>
<% end %>

The above version prefixing results in the following fragment caches:

  • views/v1/members/1-20121220141922
  • views/v1/members/2-20121220141922
  • views/v1/teams/2-20121220141922

Cache Digests

If someone forgets to change the version number of a template, and all its dependents, then the entire russian doll caching technique breaks down quickly. This happens easily, as there is no visual reference of template dependencies. For example, looking at the app/views/teams/show.html.erb template, there is no indication that each member has its own fragment cache.

Rails 4 solves this problem with cache digests. A call to #cache in your views will now suffix a digest of the template and its dependencies. No longer will you need to worry about fragment cache dependencies and versioning!

Let's take a look at out last example, now using cache digests:

<!-- app/views/teams/show.html.erb -->
<% cache @team do%>
  <h1>Team: <%= @team.name %></h1>

  <%= render @team.members %>
<% end %>

<!-- app/views/members/_member.html.erb -->
<% cache member do %>
  <div class='member'>
    <span><%= member.name %></span>
    <p><%= member.bio %></p>
  </div>
<% end %>

This results in the following fragment caches, now suffixed with an MD5 of the template itself:

  • views/members/1-20121220141922/74865fcb3e2752a0928fa4f89b3e4426
  • views/members/2-20121220141922/74865fcb3e2752a0928fa4f89b3e4426
  • views/teams/2-20121220141922/4277f85c137009873c093088ef609e60

Upgrade Path

The code for cache digests has been extracted to a gem. To take advantage of this technique today in your Rails 3 applications, just add the cache_digests gem to your Gemfile.

Further Reading

002

This post is by Kevin Faustino. Kevin is the Chief Craftsman of Remarkable Labs and also the founder of the Toronto Ruby Brigade.


Comments

comments powered by Disqus