Blog
.
Caching via Sitecore’s HTML Cache
January 25th, 2012
Web Content Management, Web Development
HTML Caching
Sitecore employs a number of caches to improve base performance in the system. Some contain database items, others contain access information. One of the most important from a development perspective is the HTML Cache, which (unless explicitly turned off) exists independently.
Sitecore allows developers to place both .NET sublayouts and XSL renderings in Sitecore’s caches to improve performance. This can be done by setting the sublayout or rendering to Cacheable=”true” in statically assigned calls, or checking the “Cacheable” property in the Layout designer for controls added dynamically through the Sitecore interface. When a control is set to be cacheable and has been recently accessed, it is served by HTML cache designated for the current context site of the user – typically the site named “website” in a stock Sitecore installation’s production environment.
Enabling caching without setting individual VaryBy parameters caches a particular rendering’s output once for the lifetime of that cache – useful in the case of something like header text that never deviates anywhere it is shown on a site. The HTML cache’s lifetime is dependent on several factors. Since it lives in memory with the w3p.exe process, any time that process is restarted or killed, the cache is cleared: an IIS restart, a moved DLL, a web.config change, etc. Additionally, the HTML cache is selectively cleared by publication events, flushing out items that may have been affected by the publication.
VaryBy Parameters
Applying individual VaryBy parameters sets aside a separate copy of the rendering’s output based on the parameters when the page is hit under those conditions. The possible VaryBy parameters are:
- VaryByData
- VaryByDevice
- VaryByLogin
- VaryByParm
- VaryByQueryString
- VaryByUser
The parameters’ exact nature is described in Sitecore’s Presentation Component Reference guide. It is important to understand the differences between each of the VaryBy conditions – it is easy to confuse something like VaryByLogin with VaryByUser, a very different parameter.
How does this variance work? As an example, if an anonymous user hits a page with a sublayout which is set to Cacheable and VaryByLogin, Sitecore stores a copy of the non-logged in version of the sublayout in the HTML cache, serving it up as cached HTML to any other anonymous users. When a logged-in user hits the page, and not until then, Sitecore caches up a “logged in” copy of the sublayout’s HTML and serves that up to further users.
The idea that Sitecore only caches individual VaryBy versions after the page is hit under those particular circumstances might seem obvious, but the consequences often are not. A major goal of caching is to reduce the times code has to be run. When a sublayout is varied by login, there are a maximum of two possible times the underlying code will be hit: logged-in, and not logged-in. If that sublayout requires varying by user, the number of possible times that code might be hit may be in the thousands, or millions, depending on the number of users. Since the sublayout is not placed into cache until the condition is met – generally a good thing, since otherwise a cache could contain gigabytes of nigh unusable data – each individual user is going to get an uncached version the first time they hit that component. If that component is very slow, subsequent actions by that user might be faster, but that first hit will always take place, where it is likely that someone else would have hit a sublayout that is only cached by login.
Below are some practical values for the number of variations a VaryBy parameter might result in, and therefore their rough effectiveness. Note that each VaryBy has a de facto minimum of two, because otherwise there is no need to vary by that condition – if there is only one device on a website, there’s no point in varying by it.
- VaryByLogin (two variations, always)
- VaryByDevice (two to ten, accommodating various mobile or XML versions)
- VaryByQueryString (two to millions, depending on how many unique querystrings might be applied to the sublayout)
- VaryByData(dozens to hundreds of thousands, depending on how many content items are associated with that sublayout)
- VaryByUser(dozens to millions, depending on number of user accounts)
- VaryByParm(two to unlimited, depending how many parameters are used and their values)
The inclusion of additional VaryBy parameters multiplies the number of potential cacheable bits, and tends to drastically reduce the effectiveness of caching. A homepage sublayout which has three rendering options – normal website, mobile website and RSS feed (VaryByDevice), and different content depending on whether a user is authenticated or not (VaryByLogin) will have six variations (3 device * 2 login variations). For a busy site, this is likely a good trade-off. A sublayout that shows a breadcrumb trail for the current page in a thousand page site (VaryByData), and filters out any pages one of the site’s ten thousand users doesn’t have access to (VaryByUser) will have ten million variations — a configuration of no practical benefit and, at the least, be likely to waste valuable cache space.
Application of VaryBy Conditions
As mentioned previously, VaryBy conditions should be applied in <sc:sublayout> calls when the sublayout is statically included in a larger sublayout or layout, or dynamically applied in the design view when the sublayouts are attached to placeholders. Do not put VaryBy conditions on placeholders, but on the controls within them. Placeholders are designed so modular controls can be slotted onto them, and the individual controls should be in charge of whether they are cached and under which circumstances based on their code.
Remember that caching a control automatically caches the underlying controls included in it, statically or through placeholders. A developer cannot “uncache” a particular subcontrol that is part of a larger cached structure. To accommodate such a scenario, follow the Divide and Conquer approach described below.
Divide and Conquer
“Divide and conquer” refers to the act of splitting functionally similar controls into separate sublayouts strictly for the purpose of dividing cacheable “pieces” of functionality from non-cacheable. It often goes against logical ideas of what should be included within a single atomic control. For instance, a footer often does not contain a great deal of complex logic and a developer might structurally conceive of it as a single sublayout. Indeed, many developers are wary of creating too many sublayouts because of the implied performance overhead, so even if it logically can be broken up into separate nested controls, they would choose a single sublayout. This is typically a miscalculation when the potential to increase caching exists.
In the footer above, the left side is utility navigation, dynamically controlled by content authors in the system. Because there is no roll-over state on the links and is consistent across the site, it could be cached without VaryBy deviation, which allows that navigation to be an ideal compromise between content author control and top performance by hard-coding those links. The right side contains several bits of functionality, including ShareThis links, a print friendly option, a language selector, and a font size manipulator. All of that functionality is accomplished with identical, cacheable HTML and Javascript – except for the language selector, which is an interactive postback control, which is generally not eligible for caching (see Interactive Controls, below). Because of that one postback control, a single footer sublayout which contains all this suddenly goes from being entirely cacheable – Sitecore generates it once on the first hit, and then it is served up from memory every time after – to having no potential for caching whatsoever unless it is broken up in smaller sublayouts, which are cached with individual VaryBy criteria.
“Divide and conquer” indicates that the footer should be broken up into smaller sublayouts, because over the lifetime of a site, there is usually nothing that gets hit more frequently than headers and footers, and even small performance savings are replicated hundreds of thousands of times over, resulting in high ROI for low LOE. In the footer example, it makes sense to nest.
An optimized footer with nested sublayouts.
Most comparisons should be able to easily show that the resulting gains are many times any anticipated overhead from splitting up sublayouts when there is a reason to do so. Like all optimization exercises, it is important to do before-and-after metrics to make this demonstrable.
As a final note, this “divide and conquer” approach is a compelling reason to engage in optimization early on in the SDLC, so wireframe and control design account for performance in the componentization of functionality.
Interactive Controls
A control that posts back to the server is almost never a candidate for caching by VaryBy properties, as it varies by the form data that is posted back (not one of the standard VaryBy parameters), and often takes significant action based on that, rather than being just HTML display. In some cases, it may be appropriate to consider rewriting logic so that a control does not require a postback at all. A good example of this is a logout button which kills a user’s authenticated session and redirects them to a login page. A programmer might naturally use a TextButton or other control that runs the code to log a user out on a postback submit. But if this logout button is part of a complex sublayout that does “heavy lifting”, such as producing dynamic navigation, then that technique disallows Sitecore caching of the entire sublayout.
A more optimizable route is to create the logout button as a Hyperlink with a url parameter that forces logout, allowing the sublayout to be cacheable with the VaryByQueryString parameter. Another alternative is to link to a completely different page that performs the logic, requiring no inherent varying of the caching of the original sublayout at all. Finally, a developer may consider a custom Vary By parameter to handle the postback scenario.
Custom VaryBy Parameters
A developer may easily find his or herself in a situation where the standard VaryBy parameters do not fairly represent the conditions by which caching can occur. If the “Divide and Conquer” approach is not sufficient, it is possible to create custom Vary By parameters by one of two methods: using the VaryByParm property and manually specifying the parameter, or by extending a webcontrol base class and inheriting your sublayouts from it, as described in several blog posts by Sitecore experts John West and Alex Shyba:
Custom Caching Criteria with the Sitecore ASP.NET CMS - John West
Sitecore Output Caching: Kick it up a Notch - Alex Shyba
Once again, one must appreciate the caveat that optimization is an exercise in gathering low-hanging fruit, and an analysis of the effort in creating the custom parameter versus potential speed gain is important factor.
Control Hierarchy and DOM Considerations
In an ideal scenario, presentation layouts and sublayouts are divided up in logical manners, and each control only affects its own piece of real estate. Every bit of functionality affects only the area where it “lives” in the control hierarchy, and caching potential is easy to determine.
In the real world, it’s common to see scenarios where controls, either in HTML output level or .NET functionality, need to alter other controls even when they are not in a direct parent/child situation. An often seen example of this is when the HTML for a page is identical, but the dimensions of objects are changed by swapping out a different CSS class on high-level elements, such as the body of the page, when some modular part of the page is there or not.
Take, for instance, a simplified scenario where a page’s interior dimensions change via CSS when there is no left nav. The following pseudocode HTML represents a page with such HTML:
The “left-nav-with-content” class applied to the high level container determines the width of each div column. Now, the same structure without a left navigation sublayout:

The HTML developer has left the leftRail div, and swapped out on the class on the main container for “no-nav-just-content”, adjusting the dimensions of the elements to hide the left nav. If the existence of a left nav is static on a per template basis, this change can be statically applied in the presentation and work well with caching. However, if the nav is dynamically present, say, based off of a checkbox called “Include Nav” on the content page, this makes caching of the navigation sublayout complicated. Since only the output of the sublayout is cached, that cached HTML cannot programmatically affect parts of the page in .NET. It is impossible for it to be cached via normal VaryBy parameters and swap out the CSS class applied to the container div.
How can the issue be addressed? There are several potential methods:
- Ideally, the HTML may be rewritten so that the leftRail elements “push” the content elements, obviating the need to have a specific class on the container object. Each element can be cached appropriately for itself, and the page reflows naturally. This approach is desirable but not always practically possible.
- The swapping of classes may be handled by client-side scripting, where the information about the existence of a left nav element is done in jQuery or pure javascript. This solution bypasses worries about caching, but is prone to having the page visually flash as the browser reflows the page after the document is ready.
- Programmatically use FindControl to alter the class attribute based on the existence or visibility of the left nav. The page itself can determine the state of the interior controls and write out the correct class.
All this goes to say that the hierarchical design of a site’s presentation plays an important role in the applicability of caching individual components, and care should be taken to account for caching during the design and cutting phases.
Alan Gallauresi
.
Tags: Caching, HTML, Sitecore, web development



3 Responses
Really great blog post on one of the most fundamentally important aspects of Sitecore.
awesome article. I also talk about Sitecore’s CustomCache in my blog:
http://blog.image0.com/sitecore/sitecores-customcache-a-simple-implementation/
Thanks, gentlemen. Max, thanks for the CustomCache code — it’s the most trimmed-down code for it I’ve seen yet for implementing a custom cache provider that has the advantage of clearing on publish.