HTTP Caching

At RailsConf 2009 on Thursday, Ryan Tomayko, creator of Rack::Cache, gave a talk on using HTTP-based caching, and the value of using it in addition to client caching and object caches like Varnish.

Types of HTTP Caching

Ryan gave an overview of the various kinds of caching using our friends Alice, Bob and Carol as examples. The first type of caching is browser-based caching, which reduces traffic from the server, but each browser only gets to access its own browser cache. For example, even if they're sitting right next to each other accessing the same website, Alice can't access Bob's browser cache.

The second type of caching is simple proxy caching--a server that sits between the browser and the destination site that silently serves content that it has previously served. According to Ryan, the creators of the original http caching capabilities thought that there might eventually be a multi-leveled network of shared proxy caches around the country--there might be company caches, city caches, even country caches. But this didn't really happen. (Although, to my knowledge, I believe most serious ISP's have multiple levels of proxy caching inside their networks.)

In any case, the benefits of proxy caches are that multiple browsers get to share the same cache stores. Shared proxy caches are good bandwidth savers in that they keep the data close to the browser.

Gateway Caches/Reverse Proxy Caches

Reverse proxies are deployed directly in front of the web server and pre-process http requests. Reverse proxies don't create bandwidth savings since the browser requests/responses still have to travel all the way from the browser to the website. Reverse proxies are instead beneficial in reducing server load. Once a particular page is hit by one browser which generates a dynamic page, that page can be cached, and served to other browsers.

Ryan then gave a quick overview of the history of http caching:

  • November 1990: Web population = 1 (no need to cache anything :-)
  • November 1996: 20M people on the web and the state of the art modem was 28.8kbps.
  • Caching was important because of bandwidth; you had to keep stuff off the wire because the last mile bandwidth was so tiny.
  • Result: Feb 1996: RFC 1945 - HTTP/1.0, included basic caching with the "EXPIRES" tag.

HTTP Gateway Caches Explained

Expiration tags allows the web server to tag a particular response as cacheable by a proxy server for a certain amount of time. Rails provides a controller method for this, eg. expires_in 60.seconds. It's important to remember that this is HTTP caching, not Rails fragment caching.

etags allow per request validation, so when a request comes in, the proxy cache can ask the web server whether it's ok to serve up a cached response. The exact response from the web server that says it's ok to serve from cache is a "304 not modified" HTTP response. The Rails controller method for etag is fresh_when :etag... or you can use Rails: stale? The controller method might be a little too helpful/obscuring--you should know what's happening down at the HTTP level when you call these methods.

Most people will combine expiration tags with explicit validation tags, since an unexpired response can be served immediately to a browser.

Ryan recommended Rack::Cache as a lightweight way to get started, there's only a 5 line config and a gem install. The newest Rack::Cache has :allow_reload and :allow_revalidate, (currently a bit under-documented) ; these override the browser-side cache control settings that can ask for requests to be served directly from an application (aka disabling browser forced reloads).

In the Q&A session, the audience discussed partial page caching via ESI, the mechanics of calculating an etag (MD5 hashing), and web server page caching vs. http caching. Ryan's point was that http caching is a lot simpler than page caching because you don't have to lace your page with conditional purge settings. Page caches are also not shared across multiple app servers (although this is what memcache helps with). One of the audience pointed out that there was lots of stuff on Rails trunk to help with better cache invalidation (touch), but it didn't make it into 2.3.2, so people should stay tuned to that.