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.
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
: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:
The key is composed of the concatenation of the view path and the model
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:
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:
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