Login

Blog Entry For 21/3/2015

MVC Caching, Doughnuts and Yum Yums

I recently made a small improvement to my website to increase responsiveness I thought I'd share. It's the old chestnut of caching server output. I found many examples of caching approaches, and the typical flavours are:

Doughnut Caching: This is where you want to cache the majority of the page but exclude a small portion of it from being cached. For example, you might want to exclude the login details as it's rendered on a per-user basis.

Doughnut Hole Caching: Think about a typical on-line web-store that lists thousand of products divided up into departments. The biggest performance hit is rendering the views of the many different product categories when a user changes their selection. You would want such portions of the page cached for quick rendering.

Yum Yum Caching: In this scenario, you are in the bakers buying lunch. You want a yum yum but, knowing yourself as well as you do, feel that you may have a guilty urge at dinner time for another portion of pastry excitement. You buy two yum yums, consume one now and cache one in the fridge for later.

Okay, the last one might not have anything to do with web applications, but it's an important tip none the less. On top of this, you also have the tricky decision of whether it is suitable to cache entries on the server or the client. The latter is obviously faster, but there are time-specific scenarios where you may wish to have more exact control of when caches are cleared. For example, I post a new blog article and I want everyone to see it as soon as possible. If I opted for client-side, I'd probably have to settle for picking a suitable client-side cache duration and just be patient, and hope the visit my web-page again soon.

On my own website, the dynamic aspects are my Twitter feed and blog. My Twitter feed is handled asynchronously through AJAX so no problems there. My blog though. If I adopted client-side caching and tweeted a link, a user with an old cached version might go to my website, navigate through to my blog and see....not the article I tweeted. This, and other implementation concerns, led me to a simple decision that my caching should be server-side. Also, given only I change content, I wanted to avoid implementing the complexity of doughnut(hole) caching if I could for such a simple website.

I opted for a simple workaround. A caching system that caches pages server side which is cleared whenever I choose. I opted to choose cache clearing when I logout of my website, as that usually coincides with me creating a new blog post. In this way, whenever I create a new blog post, users get a faster, cached version of the page.

Step 1:
The easy part. We need to add a controller decoration attribute to enable server-side caching. You can do this on a per-class or per-action basis, whichever suits you. The example below shows the controller for my Projects page, decorated with the 'OutputCache' attribute:

using System.Web.Mvc;
using CWALKER.ME.UK.Models;
using System.Web.UI; namespace CWALKER.ME.UK.Controllers
{
    [OutputCache(Duration = 3600, VaryByParam = "*", Location = OutputCacheLocation.Server)]
    public class ProjectController : BaseController
    {
        string description = "The website of Christopher Walker, a freelance software developer from Scotland. Details of my projects.";

        public ActionResult Index(Project model)
        {
            ViewBag.Description = description;
            return View(model);
        }
    }
}

I have set the cache time to 1 hour (60secs * 60 minutes = 3600 seconds). I have also set the parameter to all to ensure a separate cached page is created for each unique combination of parameters that are passed in (not really relevant for this particular controller). Finally, I set the cache location to the server.

Step 2:
I need to cache the pages when they are rendered for the first time. To do so, I have a custom filter class called CustomFilter that derives from ActionFilterAttribute. It does a few things, but I added logic to the OnResultExecuting method to carry out the cache:

public override void OnResultExecuting(ResultExecutingContext filterContext)
{
    base.OnResultExecuting(filterContext);
    filterContext.RequestContext.HttpContext.Response.AddCacheItemDependency("CachedPages");
}

This block of code will only execute if the controller action actually needs to be executed. In other words, if a cached version of the page exists on the server, it is simply returned to the client, the action method code is not executed, and the filter code isn't executed either. However, if there is no cached page, the controller action will execute, a cached version of the page will be created on the server and the above code will be run. In effect, all the above code does is create a dependency to 'cachedPages' we can use to clear all pages cached since the last time we cleared them all out.

Step 3:
Next, we need to create a means to clear the cached pages when we want to. I used my generic, static helper class to do this. The method is very simple as you can see below:

public static void ClearOutputCache()
{
    HttpContext.Current.Cache.Insert("CachedPages", DateTime.Now, null,
    System.DateTime.MaxValue, System.TimeSpan.Zero,
    System.Web.Caching.CacheItemPriority.NotRemovable, null);
}

Step 4:
Last, but not least, we need to decide when we nuke the cache. For me, I decided to drop the call into my 'LogOff' method. It's a typical implementation that, amongst other things, clears out authentication cookies and sends you back to the homepage. Before it does so, it makes the call to the 'ClearOutputCache' method to make sure that subsequent visitors will receive newly cached versions of pages.

[Authorize] 
public ActionResult LogOff()
{
    FormsAuthentication.SignOut();
    Session.Abandon();
    Response.Cookies.Add(new HttpCookie("ASP.NET_SessionId", ""));
    Helper.Helper.ClearOutputCache();
    return RedirectToAction("Index", "Home");
}

And that's it! When the cache clears, the very first visitors will create the cached pages and visitors behind them will benefit from the speedy, server-cached version of those pages. You can play around with different values for timings to suit yourself. This approach also benefits from ensuring you are also setting cache header son static assets like JPGs and PNGs, and definitely server-side content compression. If you adopt all of these, you should notice a marked improvement in speed. There are a miriad of approaches that improve performance, and they all depend on the website, the bandwidth and user-base you are targeting. Every problem in software tends to need a unique variety of solutions. The more you know about, the more you know can address.

I hope you found this useful. There are tons of resource out there relating to performance, caching and everything in between. Try Hunt's article is particularly interesting, as it takes you through a real-world example he encountered when a website's popularity took off.

Micro optimising web content for unexpected, wild success
How to programmatically clear outputcache for controller action method
Improving Performance with Output Caching
Donut Caching and Donut Hole Caching with Asp.Net MVC 4

3/21/2015 Tags: mvc cache caching server client doughnut donut

Post Comment


Captcha

If you found any of my software useful, please consider a small donation to help me keep my software free.
PayPal button image
tag heuer florida mall high quality rolex replicas replica watches buy hublot replica watches online india audemars piguet royal oak chronograph blue dial price swiss replica watches tag heuer aquaracer replica tag heuer caz1014 ba0842 review uk replica watches rolex daytona platinum replica breitling chronospace night mission replica watches