Conditional caching components in Sitecore MVC

I'm writing this post to help remember why I chose a particular solution and to hopefully help anyone else in the same scenario.

I will start off by stating the problem I needed to solve, a couple options I reviewed and why I chose a particular solution.  We are using Sitecore 7.0 and since Sitecore 7.0 requires ASP.NET MVC 3 we are using ASP.NET MVC 3.   We are also using Glass Mapper and love it :-).

PROBLEM:    Users came to me with a late requirement.  They want the ability to add renderings above and below an items content.   The renderings they want must access the database each time they are rendered to show accurate data.

How can we efficiently handle this situation without making major changes to the current design?

BACKGROUND:    Our items are made from a template that has four sections.  (Other sections exist but for this discussion these sections are the only ones relevant).  

Each section consists of the following fields, ContentActive and Style.

Item Template







Section Fields

















LAYOUT A:  I had started off using a layout that looked like the following before the late requirement came in..(section3 and section4 omitted to save space)
Layout A
You can see the MainLayout has three placeholders.     The Sections.cshtml maps to a View Rendering called Sections in Sitecore.   It has four placeholders and it renders the Section content for the page.

Users have the ability to add a Controller Rendering to any of the placeholders on the Sections rendering.   The Controller Rendering must go to the database each time it is displayed to ensure it is not showing stale data.    

You see the problem here,  the Sections rendering is Cached.    So any renderings added to any placeholders will be cached also.   This will keep the Controller Rendering from hitting the database each time it is displayed.

This layout worked fine until I got the last minute requirement to add controls before and after an items content.

LAYOUT B: So I took another approach to configuring the page layout....(again section3 and section4 omitted to save space)

Layout B

In this option SectionContainer.cshtml maps to a View Rendering in Sitecore called SectionContainer.  With this approach the SectionContainer is not cached which means that none of the placeholders will be cached.

I also have  four more view renderings and four more .cshtml files for the rendering of section content (Section1.cshtml, Section2.cshtml, etc).   These are added to the layout of the page statically,  users are not adding these sections.

In this scenario the section renderings are cached and the controller renderings hit the database each time a page with the controller rendering is accessed.

Layout B works,  great!  We are done, end of discussion right?

Well not so fast here Sitecore friends.    I like the simplicity of  layout A.  it has less renderings and less .cshtml files to maintain.

If their was only a way to keep a page from caching if it contained one of the renderings that needed to hit the database.    How could we do that and what would the performance be like?

Lucky for us Sitecore is very extensible!  Looking at the pipelines in the Sitecore.MVC.config I found the SetCacheability processor in the mvc.renderRendering pipeline.

Using Teleriks Just Decompile I looked at the Process method for the SetCacheability class to see what it does.  As the class name implies, it marks whether the item should be cached or not. (I assume we are talking the html cache.).

I decided to create my own processor which I called OverrideCacheability.  What this process does is check if the current item being rendering contains a rendering that cannot be cached, if it does I turn off the caching for the entire item.

Wait,  what about performance?  Aren't we taking a hit here because now the items html is not cached?

Lets take a look at two scenarios and see how performance stacks up.

Scenario 1:  Create two pages, one using layout A and one using layout B.  In this scenario i did not add a controller rendering to either page, thus ensuring each pages html will be cached.

Scenario 2:  Create a page using both layouts but add the controller rendering to each page, thus ensuring that nothing on the page is cached for layout A.  In the case of layout B the items content will be html cached but not the controller rendering.

Below is a summary and comparison of the results of scenario one and scenario two.

DATA SUMMARY
If you don't want to read all the gory details here is a snapshot of the results.

Layout b is 1.49 times slower in scenario one.
Layout b is 1.23 times slower in scenario two. 

*Please not in both scenarios the pages were executed multiple times to ensure html caching was activated.

SCENARIO ONE DATA
Layout A took 6.37 ms to render.
Total (without debug and profile information)6.37 ms

Layout B took 9.46 ms to render.
Total (without debug and profile information)9.46 ms

Here is what it takes to render the body placeholder of  layout A.
End - Pipeline: mvc.renderPlaceholder (time:  0.2  ms)  

Here is what it takes to render the body placeholder of layout B
End - Pipeline: mvc.renderPlaceholder (time: 1.83 ms)
Here is what it takes to complete the mvc.BuildPageDefinition pipeline for layout A
End - Pipeline: mvc.renderRendering (time: 1.75 ms)


Here is what it takes to complete the mvc.BuildPageDefinition pipeline for layout B
End - Pipeline: mvc.buildPageDefinition (time: 3.33 ms)

You can see that the renderPlaceholder and buildPageDefinition pipelines take more time for layout B because it has more renderings.   This just makes sense because their is more to building the page with layout B.

SCENARIO TWO DATA

Now lets move to a more interesting comparison.    What will happen in scenario two?  Shouldn't layout B be more efficient than layout A because nothing in layout A will be html cached?

Lets look at the numbers for scenario two.

Layout A took 735.64 ms to render.
Total (without debug and profile information)735.64 ms

Layout B took 746.72  ms to render.
Total (without debug and profile information)746.72 ms

Now to be fair we need to remove the database latency of the placeholder containing the controller rendering.

For Layout A the placeholder containing the controller rendering took 724.28 ms.
End - Pipeline: mvc.renderPlaceholder (time: 724.28 ms)


For Layout B the placeholder containing the controller rendering took 732.64 ms
End - Pipeline: mvc.renderPlaceholder (time: 732.64 ms)

So it took layout A 735.64 - 724.28  = 11.36 ms to render removing the placeholder containing the controller rendering.

It took layout B 746.72 - 732.64 = 14.08 ms to rendering removing the placeholder containing the controller rendering from the totals.

Wait a minute, layout B took longer than layout A but in scenario two layout A has none of its HTML cached!    How can layout A be quicker to render?

Again lets look at the same pipelines that we did in scenario one.

Here is what it takes to render the body placeholder of  layout A.
End - Pipeline: mvc.renderPlaceholder (time:  725.6 ms)  

Here is what it takes to render the body placeholder of layout B
End - Pipeline: mvc.renderPlaceholder (time: 735.87 ms)
Here is what it takes to complete the mvc.BuildPageDefinition pipeline for layout A
End - Pipeline: mvc.renderRendering (time: 3.03 ms)


Here is what it takes to complete the mvc.BuildPageDefinition pipeline for layout B
End - Pipeline: mvc.buildPageDefinition (time: 4.41 ms)

For a fair comparison we should remove the amount of time it took to render the placeholder that contained the controller rendering which adds database latency.

For layout A 725.6 - 724.28 = 1.32 ms to render the body placeholder removing  the time it took for the child placeholder that contained the controller rendering.

For layout B 735.87 - 732.64 = 3.23 ms to render the body placeholder removing the time it took for the child placeholder that contained the controller rendering.    Also, looking a the trace output I can see that all of the section renderings are cached for layout b, so all of them are coming from the html cache.

This is very interesting indeed!   Layout a is still more efficient then layout b even when layout a does not have its html cached but layout b does have portions of is html cached.

It boils down to the number of renderings that layout b has.   That is what increases the buildPageDefinition pipeline and the rendering of the body placeholder.

OVERALL SUMMARY

What is obvious is that the number of renderings does impact performance even if the renderings html is cached.    Keep this in mind when designing your layouts.    

Sure the difference in numbers we are talking about here are milliseconds.   But when your site load increases saving milliseconds can be the difference between slow and fast response times.    

Keep in mind that Sitecore has multiple levels of caching.  So just because the html cache does not exist in layout b for scenario two, the item itself probably exist in the item or data cache.   So we are not making a database hit each time to retrieve the fields of an item.

The negative to my approach is that I have added a new processor which does add overhead to each request.   I have reviewed the processor code to make it as efficient as I know how. 

Also,  since i'm extending Sitecore, if Sitecore changes how they are setting the cache in future releases I could have a situation where my processor doesn't work anymore.

Well this was a long post.   I hope it helps you!

Thanks for reading.









Comments

Popular posts from this blog

Why I chose Selenium WebDriver instead of Telerik's free testing framework

Displaying shared content across multiple Sites

Handling the situation where scItemPath does not exist in Sitecore MVC