Building a Sitecore Redis Session State Provider
With the release of Sitecore 7.5, Sitecore changed their underlying architecture and introduced MongoDB. While this improves the performance of Experience Analytics (formerly Sitecore Analytics), it has brought with it a new set of challenges.
The new architecture persists all user interactions into Session until the session ends at which point it is pushed to MongoDB. Unfortunately many session state providers do not support the Session_End() event as it is not mandatory.
Sitecore's workaround is to provide their own session state providers for both MongoDB and SQL. These providers use a timer to periodically check for and end expired sessions. At the time of writing, Sitecore does not currently have a Redis compatible session state provider and while there is an officially supported Microsoft provider it does not support the Session_End() event.
Nick Hills has covered the basics of creating your own Sitecore Redis SessionState provider, although there is some room to expand especially with Redis Keyspace Notifications becoming more popular.
The high level solution is as follows:
-
Create a basic provider that proxies calls to an instance of the Microsoft Redis Session State Provider.
-
Enhance the session read/write methods so each request that the Microsoft provider performs also creates/updates an additional timeout key following the same format ApplicationName_SessionId_Timeout. The TTL for this key should be approximately one minute less than the timeout of the _Data and _Internal keys.
-
Subscribe to Redis Keyspace Notifications matching the pattern keyspace@0:{ApplicationName}_*_Timeout using ServiceStack.Redis.
-
Create an expiry notification handler to trigger the Session_End() for each expired _Timeout key.
Creating a basic provider
Create a basic session provider class that inherits from SessionStateStoreProviderBase. Is it important to note that the provider must not be static. Under load a static provider will have intermittent failures.
Create an override for the Initialise() and SetItemExpireCallback() methods. The first to create instances of both the Microsoft and ServiceStack providers. The second to ensure .NET is notified that the provider supports Session_End().
Create overrides for most of the methods on the base provider and proxy calls through to the current Microsoft provider _redisSessionStateProviderContext. Do not populate the Dispose() method - Sitecore will call it throughout the application life cycle causing intermittent issues. For brevity only a few full examples are shown below:
Enhance the session read/write methods
Update the methods above so each request that the Microsoft provider performs, also creates/updates an additional timeout key following the same format ApplicationName_SessionId_Timeout. The TTL for this key should be approximately one minute less than the timeout of the _Data and _Internal keys. Once again for brevity only an example is shown below:
Create an expiry handler
Create a method to handle expired messages following the example below. The expiry handler should ensure that it only actions events named expired (not to be confused with expire) and triggers the Session_End() event.
Subscribe to Redis Keyspace Notifications
Now update the Initialize() method to subscribe to the correct Keyspace Notifications and call the Handle method for each notification received.
Implement
The solution above is ready to integrate. Once integrated into the Web.config and the Sitecore.Analytics.Tracking.config data will begin to show in Redis and then eventually MongoDB (on Session_End()) as shown below:
Performance
The integrated solution performs almost exactly as the ServiceStack provider and has passed preliminary performance testing of ~15 concurrent users as shown below:
Notes
Please consider the following:
-
There is no guarantee that the Redis server will be able to generate the expired event at the time the key TTL reaches zero.
-
While the above example uses Keyspace Notifications, Keyevent Notifications could be used to reduce the amount of overhead traffic (one watches all keys by name and the other watches a specific key for all events).
-
At various points in the application's life-cycle, there may be one or more instances of the session state provider due to Sitecore creating/disposing them while performing certain actions.
-
Each instance of the session state provider will consume two connections to the Redis database. If the Microsoft provider exposed it's IConnectionMultiplexer the connection could potentially be re-used.
-
Additional configuration needs to be extracted manually from the NameValueCollection as Microsoft have made their classes private. At a minimum the provider should support timeout, connectionTimeoutInMilliseconds, operationTimeoutInMilliseconds, retryTimeoutInMilliseconds, ssl, host and port.
-
Both the Microsoft and ServiceStack providers assume db0 as the default database.