How key-based cache expiration works
It's good to see this written up somewhere. The technique is widely known in the Rails community but I haven't seen it explained in one place. (For example, the Rails API documentation and guides don't even mention that the cache helper can take an Active Record instance as the key, so you'd be hard pressed to discover this technique without reading the Action Pack source.)<p>As DHH says, the upside is that it's simple and, as long as you get the cascading updates right, makes sure you don't ever show stale content.<p>There are a couple of downsides.<p>Firstly, once a fragment becomes invalid, it's not regenerated until the next request that needs it, so someone somewhere gets a slow[er] page load; even worse, if yours is a large public site with lots of long-tail pages, that "someone" is likely to be the Googlebot, and the slower page times might adversely affect your search ranking. You could ameliorate this by having some kind of automated process for periodically pre-warming certain parts of the cache after certain kinds of update, but that can get difficult to maintain.<p>Secondly, this technique is very conservative: the goal is to invalidate <i>all</i> fragments which <i>might</i> have been affected by an update, rather than <i>only</i> the fragments which <i>actually</i> have been. (It's less brute-force than the even simpler "invalidate the entire cache every time any record is updated" strategy, but it's making a similar trade-off.) This works well for an application like Basecamp where the hierarchical presentation of the data matches its hierarchical organisation at the model level, but is more problematic in applications with lots of different ways of presenting the same underlying data; if you have lots of cached representations of projects which don't actually include any information about the child todo lists or todos, then tough shit, those are all going to be invalidated when a single todo is updated, and you'll have to spend (page load) time waiting for them to be regenerated. In principle you can get around this by having multiple timestamps per model to represent different kinds of update, but again that's more difficult to manage correctly over time.<p>An interesting alternative approach is James Coglan's experimental Primer (<a href="https://github.com/jcoglan/primer" rel="nofollow">https://github.com/jcoglan/primer</a>), which dynamically tracks which Active Record instances & attributes are used during the generation of a cached fragment, and then actively invalidates that cache when any of those attributes change. The downside to Primer is that it's more complex, not production-ready, and may contain traces of magic.