[Updated 7/16/2014: Additional blog post on this feature from the ASP.NET team]
Since the earliest days of “classic” ASP through present day ASP.NET 4.5 Web Forms many developers have leveraged a feature called ASP.NET Session State as a means to persist short lived per user data. This feature allows the developer to store and retrieve values for individual users as they navigate around a web application. The session data is automatically persisted and retrieved from a backing store and eventually expires.
They are alternatives to using Session State which are outside the scope of this blog. For applications that require Session State there are also pitfalls the most prevalent being exclusive access to session data on a per-user per-request basis. This exclusive access is a means of maintaining the consistency of Session State and was implemented this way by design. If you are interested the gory details of this design they explained here in the section entitled “Locking Session State Data”. Session State is most common with ASP.NET Web Forms applications and to a lesser degree ASP.NET MVC via TempData (POST data to GET for example).
Web applications that are primarily server side in nature work well with Session State. By contrast client script heavy web applications where many concurrent requests are made to resources using Session State will quickly find that the pessimistic locking nature of Session State becomes a bottleneck. Regardless with either type of web application often the next bottleneck becomes the durable backing store that holds session data. Three are ways to optimize the access of Session States such as marking some requests as not needing session or read-only but ultimately we have bottlenecks once the load on the application grows.
Despite these considerations the usage of ASP.NET Session State remains common even today. Across the field I continue to see many customers with large external facing web applications that use Session State. For larger enterprise customers internally facing ASP.NET Web Forms apps are even more common. For these customers selection of the Session State Store Provider is critical. The provider essentially takes the contents of the Session item dictionary to and from a durable backing store via serialization and deserialization (usually to a binary BLOB). There are many providers available from both Microsoft and third parties. Today Microsoft provides the following Session Store providers assuming on-premises deployment for ASP.NET applications:
Can be Highly Available?
Can be used in Web Farms?
SQL Server (Traditional)
SQL Server (In-Memory)
Both of these SQL 2014 product features address the key performance and contention issues that exist with the disk based implementation for the older traditional SQL Server provider. Installation and configuration of this provider is relatively straightforward. Using the NuGet package manager console the provider can be installed via: Install-Package Microsoft.Web.SessionState.SqlInMemory.
Within your application this NuGet package will add an assembly reference to Microsoft.Web.SessionState.SqlInMemory as well as add a script file to setup the SQL Server 2014 Session State database called ASPStateInMemory.sql that contains necessary DDL to setup the database. There are a several items in the SQL script you will want to review and most likely review and/or modify:
Part 5 above will require some analysis on the part of your existing SQL Server session database and may be as simple as computing the average DATALENGTH() for the BLOB column in the traditional ASP.NET SQL Server session provider schema. For InProc or StateServer usage determining the average size of the session items is more difficult but could be accomplished by capturing memory dumps of the w3wp.exe or StateServer process and reviewing the size and number of items in the SessionState dictionary. There are also perfmon counters for InProc and StateServer as to the number of items in session. As always the best advice is always to test and tune.
By default the SQL Server 2014 In-Memory Session Provider memory optimized tables are marked as non-durable. This means that while changes to data in these tables are transitionally consistent, the changes are not logged meaning that if the SQL Server is restarted, the server is rebooted, OR any form of failover occurs (FCI or AlwaysOn) then all session data is lost. The reason for this default is performance. To make these memory optimized tables durable three changes are required to be made within the ASPStateInMemory.sql script. There are comments within the script that explain why these changes need to be made.
Once these changes are made we can make this database part of a SQL Server AlwaysOn availability group and the session data will follow us during a failover. As an added bonus retry logic exists within the provider so that when either an automatic or manual failover occurs the stale connections in the connection pool won’t result exceptions being thrown to the end user.
Please note that even if we leave the tables as non-durable you could put your session database into a SQL Server AlwaysOn availability group but the data in the session tables won’t be available in the replica (only the schema will be available). For some customer workloads this “schema only” replication model is good enough to warrant the performance boost of using non-durable memory optimized tables.
The simplest highly available topology providing the most bang for the buck for SQL Server In-Memory would resemble the following:
This topology would provide geo-redundancy, automatic failover, and sustain the complete loss of connectivity to one of three data centers. With the dynamic quorum feature of Windows Server 2012 R2 it’s even possible to sustain the loss of connectivity to two data centers automatically (last man standing scenario).
On the ASP.NET web application side to enable the new provider a simple web.config edit is all that is needed.
<sessionState mode="Custom" customProvider="SqlInMemoryProvider"> <providers> <add name="SqlInMemoryProvider" type="Microsoft.Web.SessionState.SqlInMemoryProvider" connectionString="Data Source=AGAspNet; Initial Catalog=ASPStateInMemory;Integrated Security=True;" /> </providers> </sessionState>
In the snippet above the ‘AGAspNet’ is the SQL Server 2014 AlwaysOn availability group listener name.
Using the out-of-the-box ASP.NET Web Forms 4.5 application and writing a simple string with a timestamp to Session produces the following data within SQL Server 2014:
Note the location of our AspStateInMemory database on SQLNode1-2014. Next we manually failover the availability group.
Our sessions are now available on SQLNode2-2014 without interruption to our ASP.NET applications. Simply hitting F5 in the web application retrieves the data from session without any exception being thrown to the client.
In the older SQL Server Session Provider a SQL Agent job was created to delete expired sessions. With the new provider a stored procedure is supplied [dbo].[DeleteExpiredSessions] that must be called by a job. By default this provider assumes a 20 minute session timeout. Each time a session item is accessed the timeout is reset keeping the user’s session “alive”.
There are many interesting details contained within this new Session State provider and I encourage you to dig into the code for yourselves as you will find it a wonderful learning experience about the capabilities and restrictions for the In-Memory OLTP ‘Hekaton’ feature of SQL Server 2014. One particularly clever feature contained in the code resolves around ability to simulate BLOB storage in memory. Memory optimized tables at present do not support BLOB types. Well what is a serialized session dictionary other than a potentially large BLOB? The sprocs used by the provider work to split a serialized session into 7000 byte chunks to enable storage of large session items.
The astute reader may have notice that in my screen shots there were no rows in the [SessionItems] table but rather a single row in the [Sessions] table. If my session contents had exceed 7000 bytes you would have seen “spill over” rows in the [SessionItems] table. I’ll delve into this further hopefully in a future post as this approach has many other potential applications outside of ASP.NET session storage.
The natively compiled stored procedures are also worth a look as there are clever means to dealing with restrictions such as the lack of CASE statement support within a natively compile stored procedure. This restriction is due to the fact that once the sproc is compiled to native code branching is not allowed!
Key considerations and questions to think about if you are thinking of using this new provider:
Good luck and please share your experiences using this new provider in the comments!