HTTP caching

All Heroku apps are fronted by Varnish, an HTTP accelerator. Varnish will cache output from your application according to the cues provided it by the standard HTTP headers for describing a page’s cacheability. These headers are the same ones used by browsers, so setting these headers correctly gives your app a double boost of speed when on Heroku: at the Varnish layer, and again at the user’s browser.

On the other hand, incorrect caching can cause out-of-date pages to be served, which can result in confusion for your users. So it’s important to understand how to set headers correctly to maximize speed while minimizing stale results.

Static assets

Anything that is served from the filesystem (a Rack::File) is cached for 12 hours. Whenever you push changes, your cache is cleared (see below), and since Heroku filesystems are read-only, it’s safe to cache these for a long period.

Large static assets, such as MP3s or PDFs, should generally not be included in your code tree. Use an external asset hosting service, such as Amazon S3, instead. See this.

Caching dynamic content by age

You can cache dynamically generated output of your app in Varnish, which will result in far greater performance than using Memcache to cache pages or fragments, because your app server is never even accessed. Pages cached this way are served straight from Varnish, which is generally orders of magnitude faster than serving it from Rails.

The simplest way to cache a page is to set a header on output which cues the cache that it is a public page that it can serve unmodified to everyone visiting that same URL, as well as how long it should keep it in the cache before requesting that the page be regenerated. For example:


class MyController < ApplicationController
  def index
    response.headers['Cache-Control'] = 'public, max-age=300'
    render :text => "Rendered at #{Time.now}"
  end
end

This tells the cache to serve the page for 300 seconds (5 minutes).

Nothing in the rendered page should be specific to the user viewing it. For example, if you have "Welcome, Joe" in the upper right-hand corner, then all users (regardless of whether they are Joe or not) will see this message for the next five minutes. A page like this should either be modified to remove the personal greeting, or cached via fragment caching.

Pressing shift-reload in the browser will cue the browser and Varnish to regenerate the page, regardless of the cache state.

Prevent caching

To prevent caching under any circumstance, set the Cache-Control header to no-cache. This should not be necessary in any circumstance on Heroku, but it is mentioned for reference.

Cache purge on deploy

When you git push changes to your app, the entire namespace for your app in the HTTP cache is purged. This will result in your new assets being served immediately.

This may result in a brief performance hit to the app, particularly serving of static assets, for the first user to hit the site after the changes are in place.

The cache relies on the full path, including the domain name, of your app. If you serve your app from multiple domains, the cache will not be shared between them, weakening the effectiveness of the HTTP accelerator. It is recommended you choose a single canonical domain for your app and use a redirect or other method to make sure all your users use the same domain, so that the full path is always the same for identical pages.

Example: caching barcode images generated with ImageMagick

qrcode-rails is an open source Rails app written by Siu Ying that generates a 2D barcode which can be read by many hand-held scanners, including phones. You could use qrcode to generate a barcode with your website URL, print the barcode onto a sticker; and then anyone with the BeeTagg app on their iPhone can take a photo of the sticker and read the web URL encoded there. Try it out at: http://qrcode.heroku.com/

qrcode makes a good example of dynamic content that should be cached. It uses RMagick to generate the images, which takes several seconds of CPU-intensive to generate. But barcodes are always completely deterministic on their input data: a barcode for the text “hello world” will always be the same.

In a conventional hosting environment, you might be inclined to generate the barcode image and write it to disk somewhere in your app’s public directory, where the user’s browser can then read it as a static asset. This is a non-scalable solution and doesn’t work in a cloud computing environment like Heroku. Instead, we serve the image dynamically one time, but include a cache header. Future hits to the same URL will be served from the cache, set to store for one month:

http://github.com/siuying/qrcode-rails/tree/6b878f15b918193760fb5ecacef6489e9a91bd96/app%2Fcontrollers%2Fqrcode_controller.rb#L54

Further reading