Skip Navigation
Show nav
Heroku Dev Center
  • Get Started
  • Documentation
  • Changelog
  • Search
  • Get Started
    • Node.js
    • Ruby on Rails
    • Ruby
    • Python
    • Java
    • PHP
    • Go
    • Scala
    • Clojure
  • Documentation
  • Changelog
  • More
    Additional Resources
    • Home
    • Elements
    • Products
    • Pricing
    • Careers
    • Help
    • Status
    • Events
    • Podcasts
    • Compliance Center
    Heroku Blog

    Heroku Blog

    Find out what's new with Heroku on our blog.

    Visit Blog
  • Log inorSign up
View categories

Categories

  • Heroku Architecture
    • Dynos (app containers)
    • Stacks (operating system images)
    • Networking & DNS
    • Platform Policies
    • Platform Principles
  • Command Line
  • Deployment
    • Deploying with Git
    • Deploying with Docker
    • Deployment Integrations
  • Continuous Delivery
    • Continuous Integration
  • Language Support
    • Node.js
    • Ruby
      • Rails Support
      • Working with Bundler
    • Python
      • Background Jobs in Python
      • Working with Django
    • Java
      • Working with Maven
      • Java Database Operations
      • Java Advanced Topics
      • Working with Spring Boot
    • PHP
    • Go
      • Go Dependency Management
    • Scala
    • Clojure
  • Databases & Data Management
    • Heroku Postgres
      • Postgres Basics
      • Postgres Getting Started
      • Postgres Performance
      • Postgres Data Transfer & Preservation
      • Postgres Availability
      • Postgres Special Topics
    • Heroku Data For Redis
    • Apache Kafka on Heroku
    • Other Data Stores
  • Monitoring & Metrics
    • Logging
  • App Performance
  • Add-ons
    • All Add-ons
  • Collaboration
  • Security
    • App Security
    • Identities & Authentication
    • Compliance
  • Heroku Enterprise
    • Private Spaces
      • Infrastructure Networking
    • Enterprise Accounts
    • Enterprise Teams
    • Heroku Connect (Salesforce sync)
      • Heroku Connect Administration
      • Heroku Connect Reference
      • Heroku Connect Troubleshooting
    • Single Sign-on (SSO)
  • Patterns & Best Practices
  • Extending Heroku
    • Platform API
    • App Webhooks
    • Heroku Labs
    • Building Add-ons
      • Add-on Development Tasks
      • Add-on APIs
      • Add-on Guidelines & Requirements
    • Building CLI Plugins
    • Developing Buildpacks
    • Dev Center
  • Accounts & Billing
  • Troubleshooting & Support
  • Integrating with Salesforce
  • Language Support
  • Ruby
  • Rails Support
  • Caching Strategies for Rails

Caching Strategies for Rails

English — 日本語に切り替える

Last updated September 25, 2018

Table of Contents

  • HTTP caching
  • Page caching
  • Action caching
  • Fragment caching
  • Low level caching
  • Further reading

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

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’ page_caching gem 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 the Rails’ action_caching gem. 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 illustrates 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 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

  • Caching with Rails by RailsGuides

Keep reading

  • Rails Support

Feedback

Log in to submit feedback.

Using Rack::Cache with Memcached in Rails 3.1+ (Including Rails 4) Delayed Job (DJ)

Information & Support

  • Getting Started
  • Documentation
  • Changelog
  • Compliance Center
  • Training & Education
  • Blog
  • Podcasts
  • Support Channels
  • Status

Language Reference

  • Node.js
  • Ruby
  • Java
  • PHP
  • Python
  • Go
  • Scala
  • Clojure

Other Resources

  • Careers
  • Elements
  • Products
  • Pricing

Subscribe to our monthly newsletter

Your email address:

  • RSS
    • Dev Center Articles
    • Dev Center Changelog
    • Heroku Blog
    • Heroku News Blog
    • Heroku Engineering Blog
  • Heroku Podcasts
  • Twitter
    • Dev Center Articles
    • Dev Center Changelog
    • Heroku
    • Heroku Status
  • Facebook
  • Instagram
  • Github
  • LinkedIn
  • YouTube
Heroku is acompany

 © Salesforce.com

  • heroku.com
  • Terms of Service
  • Privacy
  • Cookies
  • Cookie Preferences