It’s been awhile since a new post again, but I’ve been heads down on a new project at work. With a new project, comes new TIL’s! Today we’re going to talk about a clever caching concept called Donut Caching.
As you may be familiar, caching pertains to the storage of retrieved or generated data so that it can be referred to in the future without incurring the same cost as it took to fetch or generate it. This can generate huge performance gains, but also comes with a bit of complexity – now you have to manage cache expiry, manual cache refresh, and a bunch of other factors that play into the management of “not quite fresh” data.
With ASP.NET MVC, we get the [OutputCache] attribute out of the box – this allows us to cache the result of a controller’s execution, so in the future we can just return the cached result instead of re-generating a view (and all the data behind the scenes). MVC was also nice and gave us a bunch of options with OutputCache, such as “vary by parameter” (allows you to cache based on incoming data) and a bunch of other options that can specify cache duration, location (server and/or client), and the reuse of cache profiles.
The problem with this and most other page or output-level caches is it’s limited by the “most stale denominator”. Let’s take a product page for example:
There’s a number of data points on this average-looking product page, all of which can be cached. The product details themselves are pretty static – the name, image, or description of a MacBook isn’t going to change anytime soon. Price is a bit more volatile – it can change with a price drop or sale every week or few days. Even worse is the store availability – that could change in a matter of minutes. If we were to use an OutputCache attribute on this page, our cache duration would need to be limited by the “refresh rate” of the store quantity information – if our website or data store updates its store quantity numbers every 10 minutes or so, then our max lifetime on our cache would be 10 minutes.
This is where donut caching comes into play. With a donut cache, you can cache portions of an entire page, and leave other portions to be regenerated or cached at a different interval. Using donut caching with the above example, we could “donut cache” the entire product detail page at a reasonable interval (let’s say 24 hours – to allow for price changes overnight) except the store quantity, which we can cache at a different interval (say 10 minutes). That way we still get the benefit of caching on most of the page – avoiding a database call to get the product details and pricing – and can still provide the end user with fresh inventory data without incurring a full page load hit.
There’s a few tricks you can apply to MVC3 and above (namely using child actions alongside Html.Action or Html.RenderAction), but for more advanced scenarios you can take a look at the MVCDonutCaching package on NuGet, and get more information on the package here.