After now a couple of weeks of performance optimisations on Sitecore 7.2, I thought I would detail a little bit about ways you can optimise the performance of your MVC based Sitecore solutions.
Before I go any further with this post, I would like to make it clear that I have actively engaged with Sitecore UK and Sitecore support, sent them all of my findings and made performance improvement suggestions based upon my findings. These will hopefully benefit us all in future updates and if you come to the load testing / performance optimisation phase of your project, I would highly recommend you do the same.
Below you will find a summary of the results of my findings during performance and load analysis on Sitecore. For one of my latest builds for a client, we have pretty much followed as close to best practice as we possibly could (including a site review by Sitecore themselves and actions out of it as such), we underwent load testing on the first generation of the clients new site. The site did perform to the acceptance criteria of approximate 12,000 requests a minute that is required for the site at peak time – but not by much. It also VASTLY outperformed it’s predecessor which was a Sitecore site built by a reputable UK Sitecore partner. Since the results were close to the limit though (and to allow for overhead to allow future growth) I found myself having to analyse the performance of various elements of our solution and subsequently of Sitecore itself in order to gain the maximum out of our build.
I thought I would pass on my findings to help others to get the most out of their Sitecore MVC solutions.
During my analysis, I found that by far the biggest overhead of our site was in the rendering pipelines – not entirely unexpected I will grant you, but it did lead to investigating what I could do to maximise the performance of Sitecore.
The results are from a vanilla Sitecore build with as little code as I could get away with.
Rendering performance in terms of order:
I decided to render a view like this – deliberately keeping the work done by the view to a minimum so I could concentrate on Sitecore:
<div class="something"> <p><span>Hello World</span></p> </div>
I started by creating pages with multiple static bindings (20 to be exact) and multiple ‘renderings’ using simple @Html.Partial(“path to my view”).
- @Html.Partial – Fastest, actually not as inflexible as you think since you can ‘inject’ a property from a model as a data source. There is no caching though.
- @Html.Sitecore.Rendering() – Not far off @Html.Partial, trickier to add the model / datasource
My findings in performance tests on vanilla Sitecore installations was that the static rendering pipeline in sitecore is pretty close to native MVC performance. Obviously we were never expecting exactly the same, but it was encouraging to see.
I then moved on to looking at dynamically bound renderings. I have always known there is an overhead for using these, in webforms it never seemed to have been that costly. Here is the order in which they performed during my tests.
- Dynamically bound view renderings without a model
- Dynamically bound view renderings with a model
- Controller Renderings
In this case I started off with 10 x dynamically bound view renderings. I then stepped this up to 15, 20 and 25 renderings on individual pages. For the purposes of the tests I did not enable caching on these renderings to test the functionality. The reason for this was simple. Under load the site has periods where the cache cycles out and has to reload the rendering from scratch, during this period, the site is at the mercy of the original code and therefore (in my humble opinion) should be optimised in this state during your first round of performance improvements. Once again, in my humble opinion, if you are reliant on your page caching then you are possibly setting yourself up for a fall later on. I believe caching should enhance an already quick implementation.
During my test cycles I tested 75 and 150 users hitting the pages with 500ms delays between requests. I found that the level of throughput drops by approximately 15% for 15 renderings, 33% for 20 and nearly 50% for 25. I also noted that on my test environment the CPU usage quickly became the limiting factor with all but the 10 rendering tests hitting the CPU hard.
I then repeated this test with views containing a very simple model with a hard coded property, I found (which I felt was reasonably obvious before I started) that the performance dropped further still. In my developments, I use models a lot since I like razor and MVC, so this was something I have had to address.
I then tested with 20 dynamically bound controller renderings (using the same .cshtml). The throughput dropped a further 17% compared with their view rendering equivalent.
Caching Saves Your Bacon Though Right?
Well – in short, as always – kinda… I continued my analysis to investigate the effect of caching. I found that it does alleviate some of the problem in that the performance does improve (approximately 17% over its non-cached counterpart), it was nowhere near the near flatline cpu usage I would be expecting or would have hoped for. CPU usage was still the limiting factor – again – particularly on higher numbers of dynamic renderings.
With the findings above in mind, I thought that I would detail some recommendations based upon them.
As per Sitecore best practice (one I disagree with I might add), it is definitely recommended in MVC in particular to statically bind whatever you can. I personally think this is a shame and something I hope I can help Sitecore to rectify in future versions as I think it’s one of the greatest strengths of Sitecore. But since we are dealing with the world as it is today…
Consider using an ORM tool such as Glass Mapper – Sitecore does not allow you to specify a controller for your layout. This in its own right kind of forces you into using a statically bound renderings. This in turn causes you an issue since you cannot then specify a datasource for your rendering. Because Glass Mapper hooks into the GetModel pipeline, it allows you to create a custom model for your layouts, which in turn allows you to specify the model using the necessary @Html.Partial() overload. This does present you with a small issue in you have to then cache sections of your model if you don’t want to execute all of it every time, but publish aware (or not) caches should be a part of every Sitecore developers arsenal.
Another potential option is to consider grouping your renderings, for example – if you have a product details page / home page made up of multiple renderings that don’t require specific datasources (or if you can group the datasources together beneath an item and provide this grouped datasource). This reduces the number of times you call in both dynamic renderings and potentially use of models. In tests 4 x dynamic renderings with 5 statically bound renderings on each performed far better than if it had been dynamically bound.
Consider @Html.Partial instead of @Html.Sitecore().Rendering(). You have to make the decision based on whether you are caching etc, but you might consider occasions (particular when you are using dynamic renderings) that you can cache a grouped rendering.
Consider carefully where / when to allow Dynamic Placeholders since this could seriously affect performance.
I will continue to engage with Sitecore on improving dynamically bound renderings. Early non-production patches for pipelines I have been experimenting with yielded performance within about 5% of their equivalent statically bound renderings when performed against pages with 20 x renderings, furthermore, outperformed the original code by 50% in terms of throughput. I am sure there is more to be gained beyond this.