I’ve given numerous talks on performance in Sitefinity. During these talks, I always have a lengthy segment on the page rendering lifecycle in Sitefinity and how to best optimize it. It is important to understand the functionality behind the scenes which ultimately delivers your page HTML. The two main steps I’m going to discuss are compilation and output-generation.
When you use the drag-and-drop page editor in Sitefinity, you’re really building an ASPX page. Sitefinity is based on ASP.NET Web Forms – meaning, it serves up ASPX pages. This is vital to understand. Each widget you drag onto the page is a User Control. For every page in the database, Sitefinity stores each user control definition and its placement on the page. Even when you use the Content Block widget… You may think it is storing your HTML right into the page. It is not. It is storing the definition for the content block user control and the HTML is just a property (assuming it isn’t shared content). Now, because all of the user control definitions and are stored in the database, there is no physical ASPX page in the project for IIS to serve up when you request your “About Us” page. This is where compilation comes into play.
On the very first request to a page, Sitefinity must compile the page. What does this really mean? It queries the database for all of the user control definitions, mashes them together into an ASPX file, and then compiles that ASPX file into a DLL with a reference to the page version. If you watch your Temporary ASP.NET Files (C:\Windows\Microsoft.NET\Framework\v4.0.30319\Temporary ASP.NET Files\root or C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files\root) you can see these DLL files. Open some of these with JustDecompile and you will see the ASPX pages. Again, these are NOT HTML. Sitefinity still later has to take that ASPX page, and render each user control based on their definitions into HTML – and this usually involved database lookup.
I intentionally bolded “with a reference to the page version”. This is important to understand because it uses this version to determine if the compiled version is outdated. If the requested page version does not match the compiled version, Sitefinity will re-compile the page. Page versions are created every time you re-publish the page. It re-compiles because you might be adding/removing user controls, or moving them around on the page – and those kind of changes require the ASPX to be regenerated.
Now that we have that settled, lets move onto the next step.
Browsers render HTML, not ASPX. So Sitefinity needs to give HTML back to the browser at some point after compilation happens. Once Sitefinity has a compiled ASPX for the requested page version, it must render that to HTML. A lot happens behind the scenes here. For instance, let’s assume you added a news control to your page. For it to generate the HTML, it must query the last 5 news items from the database and render them to HTML. If you have news, blog posts, images (from libraries), and events all on your homepage – that turns into many database queries when rendering the HTML. Something to keep in mind. After Sitefinity has rendered the HTML for a page, it places its output into cache.
This is where things start getting really fast. If all of the pages on a website exist in Output Cache, it should be really fast. But this doesn’t always happen. There are many reasons that a page isn’t loading from cache. Here most of them:
- You have Output Cache turned off in your configuration or turned off on specific pages
- Your page uses query strings or postback parameters. By default, Sitefinity varies cache by the values of these parameters.
- Example of this would be a search results page. It uses a query string for the searched keywords. So unless people are searching the same query, then each search is going back to the database.
- The requested host name doesn’t match one in cache.
- Example of this is if you have multiple bindings setup for a site in IIS. Maybe you have dev.mysite.com and localhost pointing to the same website. Each one has its own instance of cache, because by default, cache is varied by host name.
- The user agent doesn’t match one in cache. By default, Sitefinity 9+ has VaryByHeaders.UserAgent disabled. This has helped a ton, because in the past, this meant that if someone visited a page in Chrome, and then loaded it in IE – it wouldn’t load it from cache – it would regenerate the HTML output.
- The page was expired from cache
- The page was ejected from cache via a cache dependency
As of Sitefinity 10, you can see how Sitefinity is populating OutputCache in this class: Telerik.Sitefinity.Web.PageOutputCacheStrategy
Keep in mind, Output Cache varies by the URL of the page. So if you have one Sitefinity page which is delivering different content determined by the URL, then each URL is going to have its own instance of cache (and from IIS’s perspective, it is its own page). For example, you have a single News page in your Sitefinity site which serves up all of the news detail content. From a compilation perspective, it is one ASPX. But each route goes through output generation separately and have their own instances of cache.
Page Cache Dependencies
Alright, now that was understand Output Cache, let’s talk about one way cache gets purged automatically in Sitefinity. A cache dependency tells the page to purge itself from cache whenever something notifies it to. Most of the out-of-the-box content widgets have cache dependencies built in. In these cases, the notifications are from content updates. For instance, if you added a News widget to your homepage, it added a cache dependency on the page to the news content type. That means, whenever News is added/deleted/updated, it purges Output Cache for the home page. Without cache dependencies, your news updates would never appear on the homepage – because its cached HTML would still contain the old news.
I’m trying to stick to just making this post an explanation of Sitefinity behavior versus optimization. But I want to stress the importance of understanding what this means. If you have a dynamic module which is updated very frequently, you may want to rethink using the out of the box widgets for display on the page. Every time a page is purged from cache, it has to regenerate its HTML (query everything from the database again). You don’t want this to happen very often throughout the day… as that means the page will load slow sometimes. Consider using web services or cache substitution for content that changes frequently during the day.
That’s all for now. I’ll have a best-practices post up soon that makes use of this information.