atrk.gif
    • Quick Contact: 1 212 202-2562

Caching in Ruby with Rails

There are three types of caching supported by Rails ‘Page Caching’, ‘Action Caching’ and ‘Fragment Caching’.

Page 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:
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.

However, ‘Page’ and ‘Action’ cachings have been removed from Rails4 and separated into gems(i.e. actionpack-page_caching gem and actionpack-action_caching gem).

Fragment Caching:

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.

Example:

<% 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.
Example:

<% 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

Example:

<% @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 %>

Nested Caching:

Example:

<% @projects.each do |project| %>
  <% cache ['v1', project] do %>
    <h3><%= link_to project.name, project %></h3>
    <%= render partial: 'tasks/task', collection: project.tasks %>
  <% end %>
<% end %>

tasks/task partial:

<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

Performance Impact:
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 :)

LinkedInShare
This entry was posted in Ruby on Rails Training, Technology and tagged , , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>