The Basics of Caching and Cache Digests

Share this article

This is an introduction to caching in Rails.  You may know that Rails performs some caching “automagically”, but you aren’t quite sure what that means or what that is. Read on to find out. In a nutshell, caching gives us quick access to values without hitting a more expensive store, such as a database. In this manner, we can improve the performance of our Rails app. Rails will cache almost anything, from an object to a page to a partial. Rails mainly provide three techniques of caching.

  • Page caching
  • Action caching
  • Fragment caching
Before we get started, be sure you have enabled caching in your environment file.
config.action_controller.perform_caching = true
It’s true by default for production.rb but false for development.rb and test.rb.

Page Caching

Page Caching is a mechanism which stores the  output of an action in a file. so when a request comes in the web server can respond without going through Actionpack . To try this out, I have created a sample application . I generated a scaffold blog with title and  description attributes. In our blogs controller, we have an index action and we want to cache that.  To accomplish this, call caches_page method for the action we wish to cache.
class BlogsController < ApplicationController
  caches_page :index
  def index
    @blogs = Blog.all
  end
end
Rails create a file in the /public directory called blogs.html  When we start our server and make a request for ‘/blogs’, Rails caches the first vist to the page in the aforementioned blogs.html file.  Any time after that, you will see that cached file, as shown in the following log entry:
Write page /Users/rashmiyadav/crap/cache_digest_test/public/blogs.html (0.7ms)
If you want to cache another action, just add it to method caches_page
caches_page :index, :show
In order to expire the page so we can cache a new version, issue a call to  expire_page:
class BlogsController < ApplicationController
  caches_page :index
  def  index
    @blogs = Blog.all
  end
  def create
    expire_page :action => :index
    @blog = Blog.new(params[:blog])
    @blog.save
  end
  def update
    expire_page :action => :index
    @blog = Blog.find(params[:id])
    @blog.update_attributes(params[:blog])
  end
end
Anytime we update or create a record it will expire the index cache. You can validate this, again, using the logs :
Expire page /Users/rashmiyadav/crap/cache_digest_test/public/cache/blogs.html (0.2ms)  
The expire_page method takes a number of arguments, such as  the path of cached page like ‘/blogs’, or a hash with action, controller name, and format to expire.
expire_page :controller => 'blogs', :action  => 'show', :format => 'json'
Configuration By default, Rails stores cache files in the /public directory, but we can change it. Simple change the page_cache_directory value in config/application.rb file:
config.action_controller.page_cache_directory = Rails.public_path + "/cache" 
Now all cache files will be in the /public/cache directory. One thing to remember, if you are running the server locally in development  and you have old cache files in you public directory, the server will continue to pick up the old cache files in the public directory. In other words,  don’t forget to remove the old cache files from the public directory. Another configuration item for page caching is the extension of the cache file. The extension of a cache file depends on the incoming request. If it’s json, then cache file will be ‘.json’ and if the request is xml  then the cache file will be ‘.xml’ and so on. If a request does not have any specific extension then, by default , it will get a ‘.html’ extension. We can configure this using config.action_controller.page_cache_extension. Page Caching is one way to speed up your application, but it is not an option if you have content that sits behind authentication.  Let’s look at the other methods of caching in Rails.

Action Caching

Action Caching is similar to Page Caching, except that filters are executed before the caching occurs. Let’s take our blog application example:
class BlogsController < ApplicationController
  before_filter :authenticate
  caches_action :index
  def index
    @blogs = Blog.all
  end
end
we call caches_action in order to cache the  index action. The first time we hit the link http://localhost:3000/blogs results in the following blog entry :
Write fragment views/localhost:3000/blogs (1.8ms)
Now, every time we call the index action,  it  will run the authenticate method before giving the cached response.  This is the main difference between action and page caching . Action caching runs all the filters before serving cache.

Expire the Cache

To expire the cache, we just need to call expire_action:
class BlogsController < ApplicationController
  caches_action :index
  def index
    @blogs = Blog.all
  end
  def create
    expire_action :action => :index
    @blog = Blog.new(params[:blog])
    @blog.save
  end
  def update
    expire_action :action => :index
    @blog = Blog.find(params[:id])
    @blog.update_attributes(params[:blog])
  end
end
When we update and create a record, it will expire the action cache.  Here’s the corresponding log entry:
Expire fragment views/localhost:3000/blogs (59.0ms)
The expire_action method takes the same arguments as expire_page above:
expire_action(:controller => :blogs, :action => :show, :id => 25)

Configuration

If we need to cache an action based on some conditions, we can do that by using :if or :unless with a proc. For example, if we don’t want to cache the index action for json requests:
class BlogsController > ApplicationController
  caches_action :index, :if => Proc.new{|c|!c.request.format.json?}
  def index
    @blogs = Blog.all
    respond_to do |format|
      format.html # index.html.erb
      format.json { render json: @blogs }
      format.csv
    end
  end
end
We can also configure the default cache path.
caches_action :show, :cache_path => { :project => 1 }
If you pass :layout => false, it will only cache your action content. It is useful when your layout has dynamic information .

Fragment Caching

Page caching and action caching both cache an entire page or an entire action,  but what if we just need to cache a part of a view? This is where fragment caching comes into play. We usually have a view with dynamic content, so we can’t store the whole page.  However, it is likely that we can cache bits of the page and expire them as needed. If we are showing a blog post with their comments, we can cache the comments by themselves:
<div>
  <%= @blog.title %><%= @blog.desc %>
</div>

<%cache("blog_comments_#{@blog.id}") do%>
  <h3>Comments</h3>
  <% @blog.comments.each_with_index do|c, i|%>
    <div><%= c.value%><span><%= c.created_at.strftime('%B %d %Y')%></span></div>
  <%end%>
<%end%>
<%= link_to 'Add New comment', new_blog_comment_path(@blog)%>
The comments section should be changed when adding a new comment, and we are caching them with the key “blog_comments_#{@blog.id}”). Here is what the log says:
Write fragment views/blog_comments_1

Expire Cache

To expire this cache, call the expire_fragment method . Here, it is called when a new comment is created:
class BlogsController < ApplicationController
  def create
    @blog = Blog.find params[:blog_id]
    expire_fragment("blog_comments_#{@blog.id}")
    @comment = @blog.comments.new(params[:comment])
  end
end

Configuration

If a key name is not provided to the cache function, then Rails will cache the block based on the name of the controller action. In this case, you would expire the cache by calling expire_fragment(:controller => ‘blogs’, :action => ‘show’). This, obviously, will generate conflicts if
  • We also caching action
  • And  having  multiple fragment cache per action
We can add an action suffix to avoid such conflicts:
cache(:action => 'show', :controller => 'blogs', :action_suffix => 'blog_comments' )
To expire this cache:
expire_fragment(:controller => 'blogs', :action => 'show',:action_suffix => 'blog_comments')

Sweepers

Calling an expire method inside a controller action is a bad idea.  Rails provides another mechanism for this expiration process:  Sweepers. Sweepers are responsible expiring caches . They observe models and execute callbacks as defined in the sweeper. For example I created a blog_sweeper.rb file:
class BlogSweeper < ActionController::Caching::Sweeper
  observe Blog
  def after_update(blog)
    expire_action(:controller => :blogs, :action => :index)
  end
  def after_create(blog)
    expire_action(:controller => :blogs, :action => :index)
  end
end
But we need to  call this sweeper in our controller for this call cache_sweeper method in controller
class BlogController < ApplicationController
  cache_sweeper :blog_sweeper, :only => [:index]
end

Cache Digests 

Cache digests are a better way to handle  fragment caching. It’s based on a Russian Doll  scheme, meaning, when you have nested cached fragments and the nested content changes, only expire the cache for that content reusing the rest of the cache. Here is an example from our same blog application. Our model structure look like :
class User < ActiveRecord::Base
  has_many :blogs
end
# Blog model
class Blog < ActiveRecord::Base
 has_many :comments
 belongs_to :user, :touch => true
end
#comment model
class Comment < ActiveRecord::Base
 belongs_to :blog, :touch => true
end
We are using the touch option here so if we update a comment, the blog will get a new updated_at value. Here is the ‘users/show.html.erb’, showing user’s detail with their blog and comments:
<%cache @user do%>
  <div>  Name:<%= @user.name %></div>
  <div>  Email:<%= @user.email %></div>
  <%= render @user.blogs%>
<%end%>
<!-- 'blogs/blog.html.erb' -->
<%cache blog do%>
  <div><%= blog.title %></div>
  <div><%= blog.desc %></div>
  <%= render blog.comments%>
<%end%>
<!-- 'comments/comment.html.erb' -->
<%cache comment do%>
  <%= comment.value %>
<%end%>
You can see that we cache the comments inside the blog and the blog inside the user. In an example where we have a single user with 2 blog posts and 1 comment, the cache keys look like:
views/users/1-20130118063610
views/blogs/1-20130118063600
views/comments/1-20130118063600
views/blogs/2-20130118063610
It is a combination of view path, object id, and timestamp of the last time it was updated. This combination ensures a cache will always be expired if a model is updated. If we add a new blog or edit a blog it will expire the cache of user along with blog cache. because we have used touch option in our blog model. Likewise, if we add or edit a comment, everything expires. However, if we change any thing in the view itself, such as a new style, reloading the page will not reflect the change as our cache keys are not expired. We can mitigate this by keeping a version of the cache keys and changing that version as needed.
<%cache ['v1', @user] do%>
  <div>Name:<%= @user.name %></div>
  <div>Email:<%= @user.email %></div>
  <%= render @user.blogs%>
<%end%>
<!-- 'blogs/blog.html.erb' -->
<%cache ['v2',blog] do%>
  <div><%= blog.title%></div>
  <div><%= blog.desc %></div>
  <%= render blog.comments%>
<%end%>
<!-- 'comments/comment.html.erb' -->
<%cache ['v3',comment] do%>
  <%= comment.value %>
<%end%>
Every time we modify our view, we need to update the version of the cache key. Now we have more problems:
  • We need to remember the version number of the template
  • Parent template versions should be changed if child template is modified.
  • If the same partial is used in different  views, then it hard to maintain versions
Don’t panic!  Rails always tries to make thing simpler. In Rails 4, cache_digests have been added to maintain nested fragment caching. With Cache Digests, we actually don’t need to worry about expiring caching and versioning  when we have nested fragment caching.  By the way, you can use cache digests in Rails 3.X by adding the cache_digests gem to your Gemfile.
<%cache @user do%>
  Name:<%= @user.name %>
  Email:<%= @user.email %>
  <%= render @user.blogs%>
<%end%>
<!-- 'blogs/blog.html.erb' -->
<%cache blog do%>
  <%= blog.title %>
  <%= blog.desc %>
  <%= render blog.comments%>
<%end%>
<!-- 'comments/comment.html.erb' -->
<%cache comment do%>
  <%= comment.value %>
<%end%>
Rendering the user’s show it will generate a log like: cache_digest1 The cache key  for users/show is suffixed with an MD5 of the template itself and all its dependencies. If  we change anything in any view(users/show, _blog or _comment) and restart the server, it will automatically expire the cache key and recompute a new cache key. In production, we don’t need to worry because every time we deploy our application it restarts the server.

Check Dependencies of Template

To check the dependencies of a view, the cache_digests gem provides two rake tasks 1. rake cache_digests:dependencies TEMPLATE=users/show  I got the following output by running the above task dependency_task It gives you the dependencies of the user/show view only. 2. rake cache_digests:nested_dependencies TEMPLATE=users/show  which gives the output nested_dependencies_task It gives you the dependencies of the user/show view along with all its nested views . Others way to call dependencies: 1.  We can call a method in render mentioning  the partial name and collection explicitly. For example:
<%= render @user.last_one_month_blogs%>
So we need to mention partial name and collection here
<%= render :partial => 'blogs/_blogs', :collection => @user.last_one_month_blogs%>
2. If we are using a helper in our render call. Then we need to use a comment that will specify the template dependency
<%# Template Dependency: comments/comment %>
<%= render_comments(blog)%>

Wrap Up

Basically, I wanted to explain caching in Rails and how many ways caching can be accomplished. More Resources -:

Frequently Asked Questions (FAQs) about Caching and Cache Digest

What is the main purpose of caching in web development?

Caching in web development is primarily used to store copies of files or data in a cache (temporary storage) so that they can be accessed more quickly. When a user visits a website, the browser downloads all the necessary files to display the site correctly. If the user visits the same site again, the browser will not need to download the same files again, instead, it will retrieve them from the cache, which speeds up the loading time. This not only enhances the user experience but also reduces the load on the server.

How does Cache Digest work?

Cache Digest is a mechanism that allows a client (usually a browser) to inform a server about the contents of its cache. This is done using a digest, which is a compact representation of the cache’s content. The server can then use this information to avoid sending resources that the client already has, thereby reducing bandwidth usage and improving page load times.

What is the difference between Cache Digest and Russian Doll Caching?

While both Cache Digest and Russian Doll Caching are caching techniques, they serve different purposes and work in different ways. Cache Digest is a method used by a client to inform a server about the contents of its cache, allowing the server to avoid sending resources that the client already has. On the other hand, Russian Doll Caching is a technique used in Rails applications where one cache is nested inside another. This allows for efficient invalidation of caches, as changing a single element can invalidate all caches that contain it.

How can I implement Cache Digest in my web application?

Implementing Cache Digest in a web application involves several steps. First, the client needs to generate a digest of its cache’s contents. This can be done using a variety of algorithms, such as Gossip or Cuckoo. The client then sends this digest to the server with each request. The server uses this digest to determine which resources the client already has and avoids sending those resources.

What are the benefits of using Cache Digest?

Using Cache Digest can bring several benefits. First, it can significantly reduce bandwidth usage, as the server avoids sending resources that the client already has. This can lead to faster page load times and a better user experience. Additionally, it can reduce the load on the server, as it does not need to send as much data.

Are there any drawbacks to using Cache Digest?

While Cache Digest can bring significant benefits, it also has some potential drawbacks. One of the main ones is the complexity of implementation. Generating and managing cache digests can be complex, especially for large applications. Additionally, if not managed correctly, cache digests can become out of sync with the actual contents of the cache, leading to inefficiencies.

How does Cache Digest compare to other caching techniques?

Cache Digest offers some unique advantages over other caching techniques. Unlike traditional caching, which only stores resources for a limited time, Cache Digest allows the client to inform the server about the contents of its cache. This can lead to more efficient use of bandwidth and faster page load times. However, it also requires more complex implementation and management.

Can Cache Digest be used with other caching techniques?

Yes, Cache Digest can be used in conjunction with other caching techniques. For example, it can be used alongside traditional caching to further optimize resource delivery. Additionally, it can be used with techniques like Russian Doll Caching to provide efficient cache invalidation.

What factors should I consider when deciding to use Cache Digest?

When deciding to use Cache Digest, there are several factors to consider. These include the complexity of your application, the resources available for implementation and management, and the potential benefits in terms of bandwidth usage and page load times. It’s also important to consider the potential drawbacks, such as the possibility of cache digests becoming out of sync.

Where can I find more information about Cache Digest?

There are many resources available online for learning more about Cache Digest. These include technical documentation, blog posts, and tutorials. Additionally, many web development communities and forums have discussions and threads dedicated to caching techniques, including Cache Digest.

Rashmi YadavRashmi Yadav
View Author

Web Developer! Rashmi usually write blog at http://raysrashmi.com.

caching
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week