Intro
The general assumption is – not using a feature/functionality means no performance price is payed. How far do you agree?
Real life example
Content Testing allows verifying which content option works better. Technically it enables multiple versions of same item being published and injects processor into getItem pipeline to figure out which version to show:
<group name="itemProvider" groupName="itemProvider">
<pipelines>
<getItem>
<processor type="Sitecore.ContentTesting.Pipelines.ItemProvider.GetItem.GetItemUnderTestProcessor, Sitecore.ContentTesting" />
</getItem>
</pipelines>
</group>
As a result, every GetItem API gets extra logic to be executed. What is the price to pay?
Analysis
The processor seem to roughly do following:
- Check if result is there
- yes -> quit
- no – fetch item via fallback provider
- If item was found – is context suitable for Content Testing to be executed:
- Are we outside
shell
site? - Are we seeing
site
inNormal
mode (neither editing, nor preview) ? - Are we requesting the specific version, or just latest?
- Are we outside
Should all be true, a check for cached version is triggered:
- String concatenation to get unique item ID
- Append
sc_VersionRedirectionRequestCache_
to highlight domain
As a result, each getItem
call shall produce strings (strings example)
Question: How much excessive memory does it cost for a request?
Coding time
IContentTestingFactory.VersionRedirectionRequestCache
is responsible for caching and defaults to VersionRedirectionRequestCache
implementation.
Thanks to Sitecore being config-driven, stock implementation can be replaced with own:
using Sitecore;
using Sitecore.ContentTesting.Caching;
using Sitecore.ContentTesting.Models;
using Sitecore.Data.Items;
using Sitecore.Reflection;
namespace ContentTesting.Demo
{
public sealed class TrackingCache : VersionRedirectionRequestCache
{
private const string MemorySpent = "mem_spent";
public TrackingCache() => Increment(TypeUtil.SizeOfObjectInstance); // self
public static int? StringsAllocatedFor
=> Context.Items[MemorySpent] is int total ? (int?) total : null;
private void Increment(int size)
{
if (System.Web.HttpContext.Current is null)
{
return;
}
if (!(Context.Items[MemorySpent] is int total))
{
total = 0;
}
total += size;
Context.Items[MemorySpent] = total;
}
protected override string GenerateItemUniqueKey(Item item)
{
var value = base.GenerateItemUniqueKey(item);
Increment(TypeUtil.SizeOfString(value));
return value;
}
public override string GenerateKey(Item item)
{
var value = base.GenerateKey(item);
Increment(TypeUtil.SizeOfString(value));
return value;
}
public override void Put(string key, VersionRedirect versionRedirect)
{
Increment(TypeUtil.SizeOfObjectInstance); // VersionRedirect is an object
base.Put(key, versionRedirect);
}
}
}
The factory implementation:
using Sitecore.ContentTesting;
using Sitecore.ContentTesting.Caching;
using Sitecore.ContentTesting.Models;
using Sitecore.Data.Items;
namespace ContentTesting.Demo
{
public sealed class DemoContentTestingFactory: ContentTestingFactory
{
public override IRequestCache<Item, VersionRedirect> VersionRedirectionRequestCache => new TrackingCache();
}
}
And the EndRequest processor:
using Sitecore;
using Sitecore.Abstractions;
using Sitecore.Pipelines.HttpRequest;
namespace ContentTesting.Demo
{
public sealed class HowMuchDidItHurt : HttpRequestProcessor
{
private readonly BaseLog _log;
public HowMuchDidItHurt(BaseLog log)
=> _log = log;
private int MinimumToComplain { get; set; } = 10 * 1024; // 10 KB
public override void Process(HttpRequestArgs args)
{
var itHurts = TrackingCache.StringsAllocatedFor;
if (itHurts is null) return;
if (itHurts.Value > MinimumToComplain)
{
_log.Warn($"[CT] {MainUtil.FormatSize(itHurts.Value, translate: false)} spent during {args.RequestUrl.AbsoluteUri} processing.", this);
}
}
}
}
Log how much memory spent on request processing by CT (sitecore.config):
<httpRequestEnd>
<processor type="Sitecore.Pipelines.PreprocessRequest.CheckIgnoreFlag, Sitecore.Kernel" />
<processor type="ContentTesting.Demo.HowMuchDidItHurt, ContentTesting.Demo" resolve="true"/>
...
Wire up new CT implementation Sitecore.ContentTesting.config that logs memory consumed:
<contentTesting>
<!-- Concrete implementation types -->
<contentTestingFactory type="ContentTesting.Demo.DemoContentTestingFactory, ContentTesting.Demo"/>
<!-- <contentTestingFactory type="Sitecore.ContentTesting.ContentTestingFactory, Sitecore.ContentTesting" /> -->
Testing time
The local results are: 16.9 MB empty VS 3.2 MB warm HTML caches.

50 requests processed at a time would give extra 150 MB pressure, and that is only for a single feature out of many. Each relatively low performance price getting multiplied on number of features results in a big performance price to pay.
A system with 20 same price unused components would waste over 3GB of memory. That does not sound as cheap any more, right?
Conclusion
Even though you might not be using Content Testing (or any other feature), the performance price is still payed. Would you prefer not to pay the price for unused stuff?