Caching Strategies for Rails

Last Updated: 25 March 2014

action caching caching page caching rails

Table of Contents

Web applications will typically have a small handful of pages that take an exceptionally long time to load. On Heroku, long running requests can tie up your dynos and seriously effect application performance. Use New Relic to determine which pages and database requests are running slowly (see the Web Transactions and Database tabs). Examine the longest running requests. If they are caused by slow database or API transactions, then use low-level caching (i.e. Rails.cache.read/write/fetch) to cache the information.

Heroku recommends against using ‘automagic’ caching libraries such as cache-money or cache_fu. We have spent time searching for an easy caching solution, but haven’t found one that with which we are satisfied. In general caching is an application-specific endeavor. The below is a roadmap for approaching performance enhancement through caching on a Rails app running on Heroku.

Once you have configured your application to use memcached, Rails will automatically use it for both action and fragment caching.

If you have questions about Ruby on Heroku, consider discussing it in the Ruby on Heroku forums.

HTTP caching

Caching using HTTP headers in Rails is a technique that can be easily applied to applications with little code modification and is covered in this separate article.

Page caching

Rails’s built-in page-caching works by creating a file on the file system. Heroku has an ephemeral file store, so while page caching may appear to work, it won’t work as intended. You should instead use action or fragment caching, or alternatively use Rack::Cache as a reverse proxy to avoid requests to your apps at all.

Action caching

If your pages requires authentication or other before/after filters, the content can still be cached using rails built-in action caching. Action caching uses (and requires) a Memcache add-on.

Simply add caches_action :<action_name> to your controller to turn on caching for specific actions. If your layout contains dynamic elements (such as your user’s name or email address in the header), you can render the layout dynamically while still caching the action’s contents. Use the :layout => false flag to accomplish this. Finally, you can use the expire_action command to remove the action from you cache when new data is written.

The following Rails controller code illustrate these concepts:

# products_controller.rb
class ProductsController < ActionController

  before_filter :authenticate
  caches_action :index
  caches_action :show, :layout => false

  def index
    @products = Product.all
  end

  def show
    @product = Product.find(params[:id])
  end

  def create
    expire_action :action => :index
  end

end

(source: Rails Guides)

Fragment caching

Fragment caching is a great mechanism for caching widgets or partials in your application. Fragment caching uses (and requires) a Heroku Memcache add-on. For example, if your app lists products like this:

# index.html.erb
<%= render :partial => "product", :collection => @products %>

# _product.html.erb
<div><%= link_to product, product.name %>: <%= product.price%></div>
<div><%= do_something_comlicated%></div>

Then you could easily cache the partial for each individual product with fragment caching. Rails will automatically generate a cache key if you pass it an ActiveRecord object:

# _product.html.erb
<% cache(product) do %>
   <div><%= link_to product, product.name %>: <%= product.price%></div>
   <div><%= do_something_comlicated%></div>
<% end %>

Another fragment caching strategy is to cache widgets or other discrete portions of pages that do not need to be refreshed from your live datastore for each new page load. If for example the front page of your website listed top selling products, you could cache this fragment. Let’s assume that you want to refresh the information every hour:

# index.html.erb
<% cache("top_products", :expires_in => 1.hour) do %>
  <div id="topSellingProducts">
    <% @recent_product = Product.order("units_sold DESC").limit(20) %>
    <%= render :partial => "product", :collection => @recent_products %>
  </div>
<% end %>

Low level caching

Low-level caching entails using the Rails.cache object directly to cache any information. Use it to store any data that is costly to retrieve and that can afford to be somewhat out-of-date. Database queries or API calls are common uses for this.

The most efficient way to implement low-level caching is using the Rails.cache.fetch method. It will read a value from the cache if it available; otherwise it will execute a block passed to it and return the result:

>> Rails.cache.fetch('answer')
==> "nil"
>> Rails.cache.fetch('answer') {1 + 1}
==> 2
Rails.cache.fetch('answer')
==> 2

Consider the following example. An application has a Product model with a class method returning all out of stock items, and an instance method that looks up the product’s price on a competing website. The data returned by these methods would be perfect for low-level caching:

# product.rb

def Product.out_of_stock
  Rails.cache.fetch("out_of_stock_products", :expires_in => 5.minutes) do
    Product.all.joins(:inventory).conditions.where("inventory.quantity = 0")
  end
end

def competing_price
  Rails.cache.fetch("/product/#{id}-#{updated_at}/comp_price", :expires_in => 12.hours) do
    Competitor::API.find_price(id)
  end
end

Notice that in this final example, we generated a cache-key based on the model’s id and update_at attributes. This is a common convention and has the benefit of invalidating the cache whenever the product is updated. In general when you use low-level caching for instance level information you need to generate a cache key.

Further reading