Troubleshooting Expired ASP.NET Session State and Your Options
I have a love/hate relationship with the ASP.NET Session. It's such a convenient place to put things, but when you start putting applications into production there are a number of less-than-obvious edge cases that can come up and bite you.
Most often the Session is used when managing state over a long process like a multi-step wizard or questionnaire. However, when people use the Session, they often lean on it a little. They'll bake it into their design so deep that when it doesn't work, they're screwed. That's not to say they shouldn't be able to lean on it, I'm just saying that there's a lot of things going on with Session (not just on ASP.NET, but other frameworks as well) in order to get it to look seamless.
Built in Options
ASP.NET offers three options (four if you count rolling your own).
- Inproc - The default, and usually works fine. However, you can get into trouble in a few scenarios.
- Web Farms - If you have more than one web server, it's important to remember that your users may not "stick" to the same webserver with each request. Some routers offer Sticky-Sessions or the ability to "pin" a user to a server. This works well if the router uses cookies as its key, but it's less reliable if the router uses IP address/source port as the key as these may change, especially if the user is behind a mega-proxy.
- Web Gardening - If you've setup IIS to run multiple instances of the IIS Worker Process on a single multi-proc machine, this is the equivalent of running a Web Farm, just on one machine. This technique is usually only useful when you've got a very CPU-intensive application - in other words, don't just turn on Web Gardening and expect your problems to get better instantly. It's subtle.
- Unexpected Process Recycling - IIS6 had some wonky defaults and would recycle the AppPool or Process when some certain limits were hit, like after x number of requests or after 20 minutes. This is the classic "flaky session state is expiring" issue that lots of folks hit. You'll be more likely to see this if you've got really long running processes where users are logged in for long periods of time.
- Out of proc - A good next step, this moves session out to a Windows Service. You can run one per Web Farm (meaning, you've got multiple machines but one instance of this service) and your session data will survive process recycles, but not system reboots. This is useful for both Web-Gardening and Web-Farming.
- Folks usually forget to mark their objects as [Serializable] which basically gives your objects "permission" to leave their process space and be stored in memory in the State Service. If you've got a high-traffic site you might want to avoid storing complex objects and object graphs as you'll pay for it on the serialization. Of course, with all things, measure everything! You'll get best performance if you stick with basic types like strings, ints, etc.
- UPDATE: I wanted to update this post and point folks to Maarten Balliauw's most excellent series on Out of Proc Session State (StateServer). He covers the basic setup, which is unremarkable, but then digs into the advanced stuff including "partitionResolvers" which I am ashamed to say I hadn't heard of! Recommend.
- SQL Server - The most robust, but now you'll pay for not only serialization, but storage. However, SQL Server is a highly tuned system and if you've got a site with any significant traffic I really recommend just skipping out-of-proc and putting your session state into a SQL Server with a lot of memory. Rather than trusting ASP.NET out of proc Session State Server to be a small database, leave the database work to the databases.
- The benefits of SQL Server for your Session State include surviving process recycles and reboots. but more importantly using removes a lot of variables from your troubleshooting in the sense that you no longer worry about the storage of your Session, now you just need to worry if your Session Cookies are getting passed back and forth from browser to server.
- Make sure you're using Windows Integrated Security and that you decide if you want ASP.NET to store Session in tempdb (which won't survive a SQL recycle) or a dedicated database (my recommendation).
Troubleshooting
There's a number of things that can go wrong, some of which I mention above, but here's what I usually run through when troubleshooting things.
- Is the ASP.NET SessionID Cookie actually moving back and forth between browser and server. This can be confirmed by:
- Using an HTTP Sniffer like ieHttpHeaders or HttpWatch or Fiddler and confirming that the Session ID cookie's value isn't changing between requests.
- Confirming that the cookie isn't being blocked by IE, privacy settings, lack of a P3P policy document, local firewall like ZoneAlarm or Symantec, or a corporate proxy with an attitude problem.
- Is IIS recycling the AppPool or Worker Process? Confirm the settings in IIS manager and make sure they are right for what you're doing.
- Is the session timing out? Are you sure you're hitting the same VDir from whence you came and successfully resetting the sliding expiration on the Session ID?
- Is some other thing like an Ajax call or IE's Content Advisor simultaneously hitting the default page or login page and causing a race condition that calls Session.Abandon? (It's happened before!)
At my last company Session became such a hassle for large high traffic applications that we just stopped using in-proc and started exploring alternatives.
Some 3rd Party Session State Options
- NCache from Alachisoft - An in-memory object cache that's distributed across your web farm. Think of it like Out of Process Session State, but distributed/clustered in their Enterprise Edition.
- ScaleOut Software SessionServer - Fast, scalable in-memory storage that is distributed across machines. Full Disclosure: we worked with these guys while I was at Corillian, but never put them into production.
- Memcached Session State Provider - Fahad has created ASP.NET Session State providers that will talk to memcached, a very popular distributed memory caching system originally created for LiveJournal.com and now used all over.
Related Links you might enjoy
- Moving ViewState to the Session Object and more Wrongheadedness
- Getting Session State in HttpHandlers (ASHX files)
- Enabling Session State on SQL Server 2005 Express
- Load Balancing and ASP.NET
- ViewStateUserKey and "Invalid_Viewstate" when posting back during Forms Authentication
- CSI: ASP.NET - The one where a double HTTP GET from Internet Explorer (IE) causes problems with FormsAuthentication and my sanity
- A multi-request-safe ViewState Persister
How do you manage state at your company?
About Scott
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.
About Newsletter
Eric - I don't know, but I sure hear it mentioned a lot.
I have read various reasons why this is so, but I can't help but be frustrated by this especially when it is so simple to have the objects support [DataContract] (just select Serialization = Unidirectional on the DBML file's properties). To me this seems like a major oversight on the part of the LINQ-To-Sql team...
[b]Take-Away: If you are not using In-Proc session storage, think long and hard before trying to use LINQ-To-SQL.[/b]
Troy Goode
The reason I find this frustrating is that I feel these same issues apply to serialization via WCF using [DataContract], yet that scenario was worked through...
Currently I am using some SessionHelper code I brewed up internally which interrogates an object and if it supports [DataContract] but not [Serializable] it will use the DataContractSerializer convert the object to a string and manually store the serialized form. I do the reverse for retrieving objects. So far this is working, but I have yet to do the work necessary to store/retrieve a List<X> where X is not serializable...
All in all it is possible to work around it, but seems very hackish.
We use Sooda O/R Mapper and serialized transactions stored on server hdd. It's simple solution but very scalable. On client we have only unique indentifier of that transaction :)
I can't remember which ones it was right now, but there didn't seem to be any specific reason for it.
You may be thinking of Dictionary<TKey, TValue>. It's not serializable.
@Scott
While you're checking on Troy's request, could you also get an authoritative answer from the BCL team on why generic dictionaries weren't made serializable? I've heard so many different reasons; it would be nice to get the straight scoop.
A combo of sensible caching and sticky servers helps me avoid Session completely.
[Serializable]
[System.Runtime.InteropServices.ComVisible(false)]
public class Dictionary<TKey,TValue>: IDictionary<TKey,TValue>, IDictionary, ISerializable, IDeserializationCallback {
Forgive my loose language earlier. Generic dictionaries aren't XML serializable. I was guessing at Tobias's intent, since the non-XML-serializability (is that a word?) of IDictionary is one of the more commonly-encountered "gotchas" that I saw in my consulting days.
Thankfully, the blogosphere is rife with workarounds. (Here are three.)
Considering that all three of the above articles begin with the statement "for an unknown reason, IDictionary isn't XML serializable", I'm still curious: why?
PS: As Scott pointed out to me, the SQL Server Session Provider uses binary serialization, my original comment wasn't actually relevant to this topic. My bad.
Thanks for mentioning ScaleOut StateServer! I wanted to point out a couple of ways in which a distributed cache like SOSS can further boost performance for managing session-state. First, SOSS has a transparent, internal, client-side cache so that it avoids repeatedly fetching session-state from the out-of-process distributed cache and deserializing it on every Web hit. To give you an example, we saw a 5X reduction is response time for a 100KB dataset when retrieving it out of the client-side cache. SOSS automatically keeps the client-side cache coherent with the distributed cache.
Second, you can really optimize performance by bypassing the ASP.NET session-state object and storing data directly in the distributed cache using the APIs; consider keeping only some bootstrap information, such as cache keys, in the session-state object. This gives you full control over when large objects are read, and more importantly, when they are updated. For example, a shopping cart would be serialized and updated to the distributed cache only when it actually changes and not on every Web hit, as would be the case with session-state. Maintaining finer-grained control over updates lets you fully optimize overall access performance, which can make a big difference for both network and CPU overhead.
You mentioned you worked with ScaleOut's SessionServer but hadn't implemented in production. Have you also worked with StateServer? Any opinions on the product?
Thanks!
Ian Suttle
Comments are closed.
One of the five best technology decisions that Gratis Internet has ever made has been deploying ScaleOut State Server. Our web sites are popular and when we used SQL as our session provider we were seeing 15,000 transactions per second just related to session management. As any DBA will tell you, it takes some thought when you're running that volume.
SOSS was able to handle it with ease. We process tens of thousands of session-related reads/writes every second and SOSS doesn't even blink. It's fully distributed, so if a web server goes down it is seamless to the end user. It also "just works". It's actually one of the more remarkable products I've ever had the pleasure of working with. Microsoft should buy them and include the product with Windows in my opinion.
No, I am not affiliated with the company in any way, just a huge fan.