I'm not trying to hate on the post at all, I think it's awesome that product people are thinking about this problem, and the more of them that do the better the world is going to be for guys like me. I just want them to continue to think hard about it :)
background: i've been a senior staff engineer at Orbitz and i did a lot of work on the caches at Cheaptickets.com
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.
There are a couple of downsides.
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.
Secondly, this technique is very conservative: the goal is to invalidate all fragments which might have been affected by an update, rather than only the fragments which actually 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.
An interesting alternative approach is James Coglan's experimental Primer (https://github.com/jcoglan/primer), 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.
In our case, the site features real estate listings, and at any given time there are about 50,000 listings. Each listing has its own page. There are numerous other pages, some of which are just index pages (lists of listings), a couple of search pages, etc. But the majority of pages are the single listing view pages, and then following that, the lists of listings. Let's call it around 60,000 pages or so.
The total number of pageviews per day (not counting search engine crawlers) is less than the total number of pages, but I would be willing to be that some individual listing pages are accessed at a much higher rate than others (namely, the newest ones). The listings themselves are updated on a daily basis, so the cache stores would get invalidated at least once a day.
In this situation, how do I determine the appropriate caching strategy?
For Basecamp it probably doesn't matter that much. For complex actions that serve mostly public requests that hit mostly "all at the same time" (think of a Media Embargo lifting on a new product announcement) it can cause real problems.
Ideally you want the first request to block the rest, so that the cache is only generated once, the other requests just spinning until the first is done, the cache is ready, and the lock released. That way all those other requests just consume sockets instead of unnecessary CPU/IO/DB time.
So you save the render plus any db calls that would be triggered as part of that render.
I can't believe I didn't think of this before. My initial reaction after I skimmed the post was "uhh, so you still hit the db? what's the point?"
Thanks for sharing this! I definitely want to implement something similar for my future projects!
This would only invalidate data that needs to be invalidated, and it doesn't sound too hard to implement... Does it exist anywhere? What are downsides I haven't thought of?
Anyway, if you use this method, you would expect to have long key, so base64 or binary protocol would be useful.
In addition, I added the pjax system to my app.
In less than 1 day of work, my app feels significantly more responsive to user commands.
(Links are fine, of course)
Now all you need to do is to ensure that whenever an object or its children change, you update their timestamp (the "touch" part), and if you make a changes to the code, you increment the version key for said cache piece. This way new requests will not hit old cached content.
How does the application learn which version an element is at right now? Don't you have to call the database to find out?
Then on first access you save the html for a key "object-x-v1" and on subsequent access you get the same html.
When the object changes (v=2), so will the key ("object-x-v2), and when you access the same content again you will have a cache miss and the html will be regenerated, while the stale cache will just stay in memory until it's naturally evicted from memcache.
It was a good refresher for those who have forgotten or are new to Rails (or web development in general), but to call it innovative is going a little far.