Out-of-process Session Options for Sitecore 7.2

Most Sitecore implementations I work with are using multiple active Content Delivery (CD) servers; more rarely, a project will use multiple Content Management (CM) servers.  These multi-server setups are considered “scaled” in Sitecore terminology, and the foundation for setting up a scaled implementation is the Sitecore Scaling Guide.

ScaledThat Scaling Guide is a good reference, but can gloss over certain details or doesn’t speak directly to a customer’s scenario.  Managing HTTP Session state is one such topic. I’ve compiled something like the following analysis for a couple Sitecore customers in the last few weeks, so I’m going to post a general version here since it reflects the state of handling Sitecore session state as of right now, February 2015, for a customer running Sitecore 7.2 today but considering upgrades in the next year or two. The customer isn’t running on Windows Azure, but they are weighing it as an option going forward in the years to come — and this is relevant as you’ll see below.

Overview
By default, Sitecore is configured to use an in-process (“InProc”) session provider for convenience. This is the fastest of all session state providers (don’t just take my word for it), and it’s understandable since any session information remains on the specific IIS server that generated it. I won’t go into the well-trodden path of why one may not want this InProc session provider, since I take it as a given that if you’ve read this far you’re curious about the alternatives. So, without further ado, here is a look at your alternatives to the default InProc session management in Sitecore:

Out-of-process Session State Options for CD Servers in Sitecore 7.2
For Sitecore Content Delivery (CD) servers, out-of-process session state is fully supported. Changing from the default InProc setting to use an out-of-process provider is the same as with any other ASP.Net web application; the “mode” attribute of the sessionState element in the web.config file governs this functionality. See https://msdn.microsoft.com/en-us/library/ms178586%28v=vs.140%29.aspx?f=255&MSPPError=-2147217396 for documentation on StateServer or SQLServer (the two main choices for out-of-process session). Besides just configuring the sessionState mode setting, you should know that there may be other provisioning and setup (the Microsoft documentation I linked to covers it thoroughly).

In addition to StateServer and SQLServer, one could use the “Custom” mode to use something like Redis. The Microsoft.Web.Redis.RedisSessionStateProvider is popular because of fast performance metrics; in the words of the movie Zoolander, that Redis is so hot right now!  Redis is a relatively new option for session state management, however, and Sitecore has not been fully regression tested with it. Still, I know of some customers anecdotally reporting a positive experience with the RedisSessionStateProvider in spite of it being considered “experimental” by Sitecore support.

I mention Redis because it’s a popular topic right now, but for a customer cautious of using officially “experimental” approaches in Sitecore, I think you have to avoid Redis unless you’re on Azure. Furthermore, as I’ll go into, there are session considerations in Sitecore 7.5 and 8 that for the moment make Redis less attractive.

Out-of-process Session State Options for CM Servers for Sitecore 7.2
For Content Management (CM) servers, support for out-of-process session in Sitecore 7.2 is considered “experimental” as it hasn’t been fully regression tested. See https://kb.sitecore.net/articles/901069 for the official statement from Sitecore on this, but my recommendation is to use the default InProc session provider for their CM Server and configure a sticky session for Content Management operations. This is usually not too big a pill to swallow for companies, and know that in the months to come this blog post will no longer be relevant as Sitecore is continuing to work on liberating CM session state.

Sitecore 7.5 and 8 Changes
Starting with Sitecore 7.5 and continued for the future of the platform, the notion of a Private Session State provider and a Shared Session State provider have been introduced. A session provider must support the Session_End event to be compatible with these versions of Sitecore, and Sitecore ships with two session providers that satisfy this requirement:

  • Sitecore.SessionProvider.MongoDB.MongoSessionStateProvider
  • Sitecore.SessionProvider.Sql.SqlSessionStateProvider

One can implement their own provider that will support the Session_End event and use it (for example, Microsoft provides the RedisSessionStateProvider, but it does not support the Session_End event . . . so one would need to extend it to support the Session_End behaviour).

In Summary

Most organizations prefer to run on a platform fully supported by Sitecore without customization unless absolutely necessary, and so this limits the session state options. SQL Server is faster than MongoDB in the performance tests for session state relevant to Sitecore, so SQL Server is the out-of-process provider I’m recommending for a customer who is currently on 7.2 and has their sites set on 7.5, 8, and beyond in the medium term (next year or two).

Returning to the hypothetical organization determining their session state approach for 7.2, it’s a pretty cut-and-dried decision in the abstract since there is only one out-of-process provider that satisfies both Sitecore 7.2 and Sitecore 7.5+ requirements today: SQL Server the session provider. I say “in the abstract” since there are businesses with investments in Memcached out there who would probably not blink an eye at using a custom out-of-proc provider with Sitecore and adding a session_end to their Memcached approach, for them this could be an advantageous use of their resources and in-house expertise . . . but my recommendation here is for an average company running the conventional Sitecore stack of ASP.Net, IIS, SQL Server.

Final Configuration Notes

Let me add to the end of this some less documented advice about out-of-process configuration for Sitecore. Assuming you have CD servers that aren’t using InProc, it makes sense to make the following changes:

<setting name="PageStateStore" value="Sitecore.Web.UI.DatabasePageStateStore, Sitecore.Kernel" />

Also, change the ViewStateStore to DatabasePageStateStore for CD servers:

<setting name="ViewStateStore" value="Sitecore.Data.DataProviders.DatabaseViewStateStore, Sitecore.Kernel" />

Also, disable the caching of ViewState for CD servers:

<setting name="Caching.CacheViewState" value="false" />

Improving Sitecore Package Installation Performance

Occasionally one finds Sitecore packages taking a long time to run . . . like an hour or longer . . . there are some standard practices like disabling indexing or limiting the versions of items in your packages (most update packages don’t need previous versions of content items, for example).  There are some operations in the Sitecore packaging process that have been recently improved and there’s one in particular worth examining: the SqlLinkDatabase.UpdateItemVersionLinks method.

The method in question appears as follows:

protected override void UpdateItemVersionLinks(Item item, ItemLink[] links)
{
    Action action = null;
    lock (this.locks.GetLock(item.ID))
    {
        if (action == null)
        {
            action = delegate {
                using (DataProviderTransaction transaction = this.DataApi.CreateTransaction())
                {
                    this.RemoveItemVersionLinks(item);
                    for (int j = 0; j < links.Length; j++)
                    {
                        ItemLink link = links[j];
                        if (!link.SourceItemID.IsNull && ((((link.SourceItemLanguage == Language.Invariant) && (link.SourceItemVersion == Version.Latest)) || ((link.SourceItemLanguage == item.Language) && (link.SourceItemVersion == Version.Latest))) || ((link.SourceItemLanguage == item.Language) && (link.SourceItemVersion == item.Version))))
                        {
                            this.AddLink(item, link);
                            DataCount.LinksDataUpdated.Increment(1L);
                        }
                    }
                    transaction.Complete();
                }
            };
        }
        Factory.GetRetryer().ExecuteNoResult(action);
    }
}

This can be a package installation performance problem since in order to update a few dependencies, the code is removing all links to the item in the Sitecore Link Database (RemoveItemVersionLinks(item)) and then adding them all back in again — it’s not a surgical update, it’s a brute force approach.

There is a better way of managing the link logic, however, and in the latest releases of Sitecore it’s folded in as part of the Sitecore.Kernel (I believe the updates in the last couple weeks all have it — but I haven’t check all of them).  Here’s the alternative that only operates on the changed links; see the outdatedLinks list variable below:

  protected override void UpdateItemVersionLinks(Item item, ItemLink[] links)
{
    Action action = null;
    List<ItemLink> versionLinks = this.GetVersionLinks(item, new object[] { "itemID", item.ID.ToGuid(), "sourceLanguage", item.Language.ToString(), "sourceVersion", item.Version.Number, "invariantLanguage", Language.Invariant.ToString(), "latestVersion", Version.Latest.Number, "database", this.GetString(item.Database.Name, 50) });
    List<ItemLink> outdatedLinks = versionLinks.Except<ItemLink>(links, new ItemLinkEqualityComparer()).ToList<ItemLink>();
    IEnumerable<ItemLink> newLinks = links.Except<ItemLink>(versionLinks, new ItemLinkEqualityComparer());
    LockSet set = (LockSet) lockSetField.GetValue(this);
    lock (set.GetLock(item.ID))
    {
        if (action == null)
        {
            action = delegate {
                using (DataProviderTransaction transaction = this.DataApi.CreateTransaction())
                {
                    try
                    {
                        this.RemoveOutdatedItemVersionLinks(outdatedLinks);
                    }
                    catch (InvalidCastException exception)
                    {
                        Log.Error("Failed to cast ItemLink collection to custom ItemLinkWithId collection.", exception, this);
                        transaction.Dispose();
                        return;
                    }
                    foreach (ItemLink link in newLinks)
                    {
                        ItemLink link2 = link;
                        if (!link2.SourceItemID.IsNull && ((((link2.SourceItemLanguage == Language.Invariant) && (link2.SourceItemVersion == Version.Latest)) || ((link2.SourceItemLanguage == item.Language) && (link2.SourceItemVersion == Version.Latest))) || ((link2.SourceItemLanguage == item.Language) && (link2.SourceItemVersion == item.Version))))
                        {
                            this.AddLink(item, link2);
                            LinkCounters.DataUpdated.Increment();
                        }
                    }
                    transaction.Complete();
                }
            };
        }
        Factory.GetRetryer().ExecuteNoResult(action);
    }
}

If you needed to, you’d swap in your improved logic via a configuration patch like:

<?xml version="1.0" encoding="UTF-8"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
   <sitecore>
      <LinkDatabase type="Sitecore.Data.$(database).$(database)LinkDatabase, Sitecore.Kernel">
         <patch:attribute name="type">CustomAssembly.Data.SqlServer.SqlServerLinkDatabase, CustomAssembly</patch:attribute>
      </LinkDatabase>
   </sitecore>
</configuration>