"Dig within. Within is the wellspring of Good; and it is always ready to bubble up, if you just dig."
-- Marcus Aurelius


a matter of scalability
In the days of static content, web sites were ideally scalable -- or at least, it was as close to ideal as technology allows. If a site outgrew its capacity, another server could be added to the infrastructure. This architecture scales outward. That's because each instance is self contained -- there's no cross talk between servers and as far as the user or the architecture is concerned, it doesn't matter if there are 1 or 1,000 servers behind the scenes serving content. A typical diagram may look something like this:


In this type of architecture, the largest problem is scaling a load balancing mechanism; generally, though, that's more of an issue of redundancy. While of course our solutions must be somewhat scalable, we need them to be redundant, too. A single server may very well handle the load, but it's a scary world if it's the only thing between being on or offline.

While scaling outward is as close to ideal as we can hope for, it's not easily obtained with web applications. Since the web is inherently stateless, we rely on building an infrastructure to support data, dynamic content, and of course user state. In most cases, these solutions scale upward. That's because a server needs context to respond to a request, and a group of servers need to be able to share data.

That's where scalability takes a hit. In a data driven application, an architecture may look something like:


Data needs to be equally available to all servers, but a single database server may collapse under the pressure (not to mention the fact that it offers no redundancy). It's easy to see the problem in the above diagram. That's where clustering helps -- but even in an active-active configuration, the biggest bang for the buck for performance improvements come from throwing more hardware at the cluster -- more CPU, more RAM, faster disks; in other words, scaling upwards.

Replication may help scale out a solution, but for geographically balanced environments, replication can only take you so far.

where does the state handler come in?
If you're trying to persist user information from request to request, it's clear that an extensible database solution is 1) not easy, 2) not cheap, and 3) requires a lot of maintenance. Prior to ASP.NET, developers had to roll their own solution for managing state beyond a single server instance. .NET offers two new methods for storing state: State Server and SQL. Both offer advantages and disadvantages -- and while both are great options, they still present scalability issues. In fact, for SQL Servers already under load, throwing user state on top of it is just too much.

The StructureTooBig StateManager was designed to solve the user state scalability and redundancy issues in a secure way. It does this by relying on each client to maintain a copy of their own state in an encrypted form, easing the pressure from the backend to maintain state. Even for single web servers, this solution has many advantages over typical (in process) session management.

how does it work?
I thought you'd never ask!

The module plugs itself (via the web.config) into the HTTP pipeline. If the user already has a session, the state itself is serialized and the resulting serialized text is then encrypted and stored in an in-memory session cookie on the user's machine. When the next request is made, the module is invoked by the Http Application. The State object within the module attempts to decrypt the cookie, and then deserialize the state (see diagram to the right). Assuming this is successful, the state is rehydrated as it was on the last request.

When the page has completed processing, the module serializes the StateManager and then encrypts the contents back into the session cookie. If any of the fields or properties in the StateManager are marked with the [Persisted] attribute, they'll be serialized and encrypted into a persisted cookie on the users machine. Next time the user visits the site, they'll automatically rehydrate into the StateManager object.

but doesn't all that encryption, reflection, and serialization kill performance?
You'd think, but it holds up pretty darn well. Of course, InProc session state will win hands down in any contest in terms of speed, but it's also the most limited. On a single IIS box with no database usage except on the first hit, InProc could churn about 240 requests per second on my test application. That's really impressive on my hardware. But it's also not very "real-world." With an IIS box connected to a dedicated State Server (gigabit LAN), we still got an impressive 195 requests per second. An IIS box connected to a dedicated SQL State Server processed around 185 requests per second. The StructureTooBig StateManager, with full AES encryption, delivers around 190 requests per second. Without encryption, the number jumps to 225. With no persisted values (values serialized to a persisted cookie) the number hovers around 205 (the session data is still encrypted).

With multiple servers, the numbers change. InProc yields the same output, but unless you're using sticky server assignments, it's not real. The dedicated State Server performance on each machine starts to dive to about 80% on each box. SQL performance scales back similarly. This decrease continues as more load is introduced. The StructureTooBig StateManager numbers remain the same because the design uses each user as a distributed database.

This is what I mean when we talk about the advantages of scaling out vs. scaling up.

i've only a got a single server, so why should I use this?
I developed this module specifically because I'm on a single server! First, if you know the application will always live on a single server, and you own and manage the server, stick with InProc session state.

If you've got a website that's hosted with a bazillion other websites, this may be just what you need. While I'm happy with my hosting for what I pay, I have no control over what everyone else is doing. This triggers a large number of Application Pool recycles -- meaning everyone's session state is lost. Just look at my Server Stats page that shows the number of recycles -- I've seen it as high as 90 recycles in one day! That's nearly 1 every 15 minutes! If I had a shopping cart or high volume site, this would be infuriating and would no doubt affect sales.

This may also be a good solution if you're currently using InProc session state, but want to design your application with scalability in mind for future growth.

Now, with this solution, it's possible to have sessions live indefinitely if you wanted to -- 100% independent of Application Recycles. The Session Timeout is now a matter of preference, rather than a balancing act between resources on the server and user experience. The session is only lost if the user closes their browser, or if the encryption keys are changed (something you control and generally speaking, not needed).