There are three types of caching supported by Rails ‘Page Caching’, ‘Action Caching’ and ‘Fragment Caching’.
Page caching allows the request for a generated page to be fulfilled by the webserver (i.e. Apache or Nginx), without ever having to go through the Rails stack at all.
This is very nice technique but cannot be used in all the situations like pages which needs authentication, also the overhead of implementing the Cache Expiration mechanism.
Action Caching is same as Page Caching the only difference in this is the request will hit the Rails stack, and will allow the authentication and other restrictions to be enforced before serving the cache.
Fragment caching refers to caching a fragment(part) of a page.
Rails ‘ActionView’ templates provide ease of coding, implementing and maintenance to developer using Templates, Partials and Layouts. But the generation of actual HTML is very complex and slow process.
Fragment Caching allows a fragment of view logic to be wrapped in a cache block and served out of the cache store when the next request comes in.
<% cache do %> <% @projects.each do |project| %> <h3><%= link_to project.name, project %></h3> <%= render partial: 'tasks/task', collection: project.tasks %> <% end %> <% end %>
The cache block in above example will get binded to the action that will call it. To have multiple caches per action cache method must be passed action_suffix as below.
<% cache :action => 'index', :action_suffix => 'all_projects' do %> <% @projects.each do |project| %> <h3><%= link_to project.name, project %></h3> <%= render partial: 'tasks/task', collection: project.tasks %> <% end %> <% end %>
It is possible to define cache without binding it to some action, instead using a global key.
<% cache 'all_projects_with_tasks' do %> <% @projects.each do |project| %> <h3><%= link_to project.name, project %></h3> <%= render partial: 'tasks/task', collection: project.tasks %> <% end %> <% end %>
In all of the previous example, the cache will not get invalidated after a change in any object or in template itself.
Cache can be expired as below
expire_fragment(controller: ‘projects’, action: ‘index’, action_suffix: ‘all_projects’)
expire_fragment(‘all_projects_with_tasks’) # for global keyed cache
ActiveRecord object as Caching key
<% @projects.each do |project| %> <% cache project do %> <h3><%= link_to project.name, project %></h3> <%= render partial: 'tasks/task', collection: project.tasks %> <% end %> <% end %>
In the above example, the raltive cache for the project will get expired automatically after update into it. Because the cache key generated based on object timestamp.
But in the above example the problem is if there will be any changes in template(in html structure) the cache wont get invalidated automatically.
The workaround for it is, add a version number to cache key, and change the version number every time the template is being changed.
Like <% cache ['v1', project] do %>
<% @projects.each do |project| %> <% cache ['v1', project] do %> <h3><%= link_to project.name, project %></h3> <%= render partial: 'tasks/task', collection: project.tasks %> <% end %> <% end %>
<ul> <% cache ['v1', task ] do %> <li><%= task.description %></li> <li><%= task.due_date %></li> <% end %> </ul>
The above is an example of nested caching, But there are few problems with this implementation:
1. Every time the changes done in inner template/partial(here it is tasks/_task.html.erb), the cache version number must get updated for its parent cache fragment member.
2. When the project object is changed the cache will get expired automatically because of change in the object timestamp. But when any task is updated the project cache wont get expired, to overcome with this we can update project object whenever any task is being changed using “:touch => true”
In Task Model:
belongs_to :project, :touch => true # this will update the associated project object on updation on a task. And thus will expire the project cache.
It’s called “Russian Doll Caching” because it nests multiple fragments. The advantage is that if a single product is updated, all the other inner fragments can be reused when regenerating the outer fragment.
Russian Doll Caching with CacheDigest
Forget about all the complex methods of fragment caching like versioning fragments or expiring it manually, because Rails4 has cache_digest to deal with the auto expiration of cache.
With cache digests, unique cache keys are formed using a md5 stamp based on the timestamp of the object being cached
The advantage of this is that when objects are updated, and outer fragments are automatically invalidated, Other nested fragments can be re-used which is called as RussianDoll.
Only the key requirement to this is that children objects should update the timestamps of their parent object by using ActiveRecord touch option. This will invalidate the old cache and will generate and serve new cache.
NOTE: cache_digest is also extracted into a separate gem and can be used with Rails3 applications. https://github.com/rails/cache_digests
Be careful while using cache, because it may decrease your application performance if not used intelligently.
Net cache impact = (benefit from hit x hit rate) – (cost of miss x miss rate)
Cost of a cache miss penalty is always the number of times of cache-hit time, So more frequent cache miss may reduce the application performance significantly instead of improving it.
Points to Remember:
1. Cache fragment with a low hit rate that is also quick to render on a miss might be better off not being cached at all. The costs of all of the misses outweigh the benefits of a hit.
2. A template that is extremely slow to render might still benefit on net even if only 10% of cache requests are successful.
For reference I recommend The performance impact of ‘Russian doll’ caching post by Noah Lorang – data analyst for Basecamp
Hope this post could help fellow RoR enthusiast, Happy coding