Heroku

How It Works

HTTP Caching on Aspen/Bamboo

Last Updated: 30 January 2012

bamboo

This article applies to apps on the Aspen or Bamboo stacks. Varnish is not available on the most recent stack, Cedar - use application/framework caching mechanisms, or add-ons such as memcache

Table of Contents

Heroku apps on the Aspen and Bamboo stacks are fronted by Varnish, an HTTP accelerator. Varnish will cache output from your application according to cues provided by standard HTTP headers to describe 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

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 article for more information.

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.

Caching Dynamic Content by Age

Caching dynamically-generated content in Varnish can result in far better performance than using Memcache, because your app server is never even accessed for cache hits in the former case. Pages cached this way are served straight from Varnish, which is generally orders of magnitude faster than serving them from Rails.

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.

The simplest way to cache a page is to set a header on output which cues the cache that this page can be served, unmodified, to everyone visiting that same URL. The Cache-Control header also specifies how long the cached copy should be retained:

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

Preventing Caching

To prevent caching under any circumstance, set the Cache-Control header to no-cache. This is not generally necessary, but it may help prevent some stale pages in older versions of IE.

Purging the Cache on Deploy

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

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.

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

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; 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 work to generate. Barcodes, however, are completely deterministic: a barcode for the text “hello world” will always be the same. This means that you should potentially never need to generate a new barcode for input that has been previously processed.

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 create and serve the image dynamically the first time, but add a Cache-Control header (see the code) to store it for one month:

Further Reading