Skip To Content

The problem with Kentico Partial Caching

Kentico CMS is a great platform that has a lot of options for optimization and caching. In this post, I'd like to describe an interesting issue we ran into when working on a large ecommerce project.

We recently ran into an issue with Kentico web part caching that caused unexpectedly long delays in page load time. It took some time to identify and isolate the cause of this issue, so I hope this information is helpful as you consider your caching strategy for Kentico sites.

As part of our best practices, we have guidelines for page load time, responsiveness, and SQL activity that we review prior to launching any Kentico-based application. This procedure ensures that all sites we launch are optimized. However, when reviewing one particular complex site, we ran into an issue that caused every page that used a certain template to have a 10-15 second delay on initial page load. Normally this only happens the first time a template is viewed on a server, but it was happening on every single page that used that template. Since this was a product template, a 10-15 second delay on every product was a major problem!

Caching Options in Kentico

First, a little background:

Kentico provides four main ways to cache content:

  • Output cache – Caches the entire page output, with a separate version for each site, user, browser, language, device profile, etc.
  • Partial Cache – Caches the output of a given web part on a template by site, user, language, etc.
  • Repeater Cache Items – Allows repeater-type web parts to cache the loaded dataset (and share with other web parts if desired)
  • Data Cache – Using the API layer, this leverages the ASP.NET Caching API

All of these approaches support the use of Cache Dependencies. For example, you can cache a navigation repeater web part to have a dependency on all the pages in the tree. If any one of these pages changes, the cache is invalidated and reloaded on next page load.

In addition, the Info Cache caches the low-level structure of any given object or page so SQL round trips are avoided. This caching is managed internally and thus happens behind the scenes.

Because there are so many options, it often takes a little bit of thought to apply the right balance when selecting caching options for your site. Full-page output caches are ideal in many circumstances, but not practical in others.

For slow or complex web parts, it’s common to use Partial Caching. This is where we ran into the issue. Basically, the product detail template for this site contains a lot of web parts (about 25). Due to the design, they’re all over the page, so we couldn’t group them together in larger, custom web parts.

Some of these web parts needed to scan large amounts of data in order to calculate recommendation and other options. This is a somewhat slow process (as much as 500ms), so partial caches were applied to about 10 different web parts. When we began noticing a delay on each page load, we started our investigation.

ASP.NET JIT for Portal Page Templates

It turned out that the problem was caused by how the ASP.NET WebForms compiler JIT (Just-in-time) compiles code with the Portal Engine model.

Kentico’s Portal Engine is a powerful tool that allows entire sites to be developed from the browser in the Kentico admin. Behind the scenes, the portal engine creates virtual user controls (ASCX), and ASP.NET JIT compiles them just like a physical one. Because of this, the first time a particular template is viewed on a server, some DLL files are created in the Temporary ASP.NET Files folder (C:\Windows\Microsoft .NET\Framework64\{version}\Temporary ASP.NET Files).

Since this process just happens once on page load, sites get up and running fast and it’s not really much of a bother. Internally, Kentico knows that each template has a unique combination of web parts and it re-uses that compiled file any time the template is loaded.

Why We Saw This Issue

When we started troubleshooting the delay, we noticed that on the initial page load of every product, additional DLLs were being compiled. By removing all web parts and re-adding them one by one, we saw that each web part with a Partial Cache configured was actually generating a unique DLL on each page. Essentially, ASP.NET treats each web part with Partial Cache enabled as a separate virtual file to compile, even though a previous page load that uses the same template has already compiled it.

This issue actually happens every time Partial Cache is enabled, but it’s not usually noticed because it happens so fast. We had so many web parts with Partial Caching enabled on this site that it became painfully slow.

Recommendations

While Kentico does not provide any documentation that explains this problem (at least not that we could find or that Support could provide us), the bottom line is this: Any time Partial Cache is enabled on a web part, the ASP.NET compiler will JIT compile the page template on the initial load for every page that uses the template. This is usually a non-issue, but if you have more than a handful of web parts that have Partial Cache enabled, you will start seeing delays on every initial page load.

There are four main ways you can combat this:

  • Use other caching options where possible, such as full-page Output cache
  • Reduce the number of web parts that use Partial Cache to the bare minimum
  • If possible, join multiple web parts into single, custom web parts
  • Use ASP.NET 4.6 and the new Microsoft Roslyn CodeDOM Providers to speed up JIT compilation

Since the Kentico Support team did not consider this issue a “bug” per se, we don't expect a specific fix to occur. In our case, once we identified the problem, we were able to quickly fix it, using a combination of the methods described above. 

I hope that this has been a helpful tip for those that work with the Kentico platform. In our experience, it’s a fantastic framework, and though this is a bit of an edge case, as more and more teams adopt Kentico, these kinds of lessons are great to share with the community.

Have you come across this issue as well? I'd love to hear your feedback or how your team solved the problem!