Chapter 6. Session State

When we talked about concurrency, we raised the issue of the difference between business and system transactions (Chapter 5, page 74). As well as affecting concurrency, this difference affects how to store the data that’s used within a business transaction but isn’t yet ready to be committed to the general database of record.

The differences between business and system transactions underlie much of the debate over stateless versus stateful sessions. There’s been a lot written about this issue, but in my view the basic problem is often disguised behind the technical questions of stateless and stateful server systems. I think the fundamental issue is realizing that some sessions are inherently stateful and then deciding what to do about the state.

The Value of Statelessness

What do people mean by a stateless server? The whole point of objects, of course, is that they combine state (data) with behavior. A true stateless object is one with no fields. Such animals do show up from time to time, but frankly, they’re pretty rare. Indeed, you can make a strong case that a stateless object is a bad design.

As it turns out, however, this isn’t what most people mean when they talk about statelessness in a distributed enterprise application. When people refer to a stateless server they mean an object that doesn’t retain state between requests. Such an object may well have fields, but when you invoke a method on a stateless server the values of the fields are undefined.

An example of a stateless server object might be one that returns a Web page telling you all about a book. You invoke a call on it by accessing a URL—the object might be an ASP document or a servlet. In the URL you supply an ISBN number that the server uses to generate the HTTP reply. During the interaction the server object might stash the book’s ISBN, title, and price in fields when it gets them back from the database, before it generates the HTML; maybe it does some business logic to determine which complimentary reviews to show the user. Once it’s done its job, however, these values become useless. The next ISBN is a whole new story, and the server object will probably reinitialize to clear out any old values to avoid mistakes.

Now imagine that you want to keep track of all the ISBNs visited by a particular client IP address. You can keep this in a list maintained by the server object. However, this list must persist between requests and thus you have a stateful server object. The shift from stateless to stateful is much more than three or four letters at the end of the word. For many people stateful servers are nothing short of disastrous. Why is this?

The primary issue is one of server resources. Any stateful server object needs to keep all its state while waiting for a user to ponder a Web page. A stateless server object, however, can process other requests from other sessions. Here’s a completely unrealistic yet helpful thought experiment. We have a hundred people who want to know about books, and processing a request about a book takes one second. Each person makes one request every ten seconds, and all requests are perfectly balanced. If we want to track a user’s requests with a stateful server object, we must have one server object per user: one hundred objects. But 90 percent of the time these objects are sitting around doing nothing. If we forgo the ISBN tracking and just use stateless server objects to respond to requests, we can get away with only ten server objects fully employed all the time.

The point is that, if we have no state between method calls, it doesn’t matter which object services the request, but if we do store state we need to always get the same object. Statelessness allows us to pool our objects so that we need fewer objects to handle more users. The more idle users we have, the more valuable stateless servers are. As you can imagine, stateless servers are very useful on high-traffic Web sites. Statelessness also fits in well with the Web since HTTP is a stateless protocol.

So everything should be stateless, right? Well, it would be if it could be. The problem is that many client interactions are inherently stateful. Consider the shopping cart metaphor that fuels a thousand e-commerce applications. The user’s interaction involves browsing several books and picking which ones to buy. The shopping cart needs to be remembered for the user’s entire session. Essentially we have a stateful business transaction, which implies that the session has to be stateful. If I only look at books and don’t buy anything, my session is stateless, but if I buy, it’s stateful. We can’t avoid the state unless we stay poor; instead, we have to decide what to do with it. The good news is that we can use a stateless server to implement a stateful session; the interesting news is that we may not want to.

Session State

The details of the shopping cart are session state, meaning that the data in the cart is relevant only to that particular session. This state is within a business transaction, which means that it’s separated from other sessions and their business transactions. (I’ll continue to assume for this discussion that each business transaction runs in one session only and that each session does only one business transaction at any one time). Session state is distinct from what I call record data, which is the long-term persistent data held in the database and visible to all sessions. Session state needs to be committed to become record data.

Since session state is within a business transaction, it has many of the properties that people usually think of with transactions, such as ACID (atomicity, consistency, isolation, and durability). The consequences of this are not always understood.

One interesting consequence is the effect on consistency. While the customer is editing an insurance policy, the current state of the policy may not be legal. The customer alters a value, uses a request to send this to the system, and the system replies indicating invalid values. Those values are part of the session state, but they aren’t valid. Session state is often like this—it isn’t going to match the validation rules while it’s being worked on; it will only when the business transaction commits.

The biggest issue with session state is dealing with isolation. With many fingers in the pot, a number of things can happen while a customer is editing a policy. The most obvious is two people editing the policy at the same time. But it’s not just direct changes that are a problem. Consider that there are two records, the policy itself and the customer record. The policy has a risk value that depends partially on the zip code in the customer record. The customer begins by editing the policy and after ten minutes does something that opens the customer record so he can see the zip code. However, during that time someone else has changed the zip code and the risk value—leading to an inconsistent read. See page 76 for advice on how to deal with this.

Not all data held by the session counts as session state. The session may cache some data that doesn’t really need to be stored between requests but is stored to improve performance. Since you can lose the cache without losing correct behavior, this is different from session state, which must be stored between requests for correct behavior.

Ways to Store Session State

So, how do you store session state once you know you have to have it? I divide the options into three blurred but basic choices.

Client Session State (456) stores the data on the client. There are several ways to do this: encoding data in a URL for a Web presentation, using cookies, serializing the data into some hidden field on a Web form, and holding the data in objects on a rich client.

Server Session State (458) may be as simple as holding the data in memory between requests. Usually, however, there’s a mechanism for storing the session state somewhere more durable as a serialized object. The object can be stored on the application server’s local file system, or it can be placed in a shared data source. This could be a simple database table with a session ID as a key and a serialized object as a value.

Database Session State (462) is also server-side storage, but it involves breaking up the data into tables and fields and storing it in the database much as you would store more lasting data.

There are quite a few issues involved in the choice of option. First off, I’ll talk about bandwidth needs between the client and the server. Using Client Session State (456) means that session data needs to be transferred across the wire with every request. If we’re talking about only a few fields, this is no big deal, but larger amounts of data result in bigger transfers. In one application this data amounted to about a megabyte or, as one of our team put it, three Shakespeare plays worth. Admittedly, we were using XML between the two, which is not the most compact of data transmission forms, but even so there was a lot of data to work with.

Of course, some data will need to be transferred because it has to be seen on the presentation. But using Client Session State (456) implies that with every request you have to transfer all the data the server uses for it, even if it isn’t needed by the client for display. All in all this means that you don’t want to use Client Session State (456) unless the amount of session state you need to store is pretty small. You also have to worry about security and integrity. Unless you encrypt the data, you have to assume that any malicious user could edit your session data, which might lead you to a whole new version of “name your own price.”

Session data has to be isolated. In most cases what’s going on in one session shouldn’t affect what’s going on in another. If we book a flight itinerary there should be no effect on any other user until the flight is confirmed. Indeed, part of the meaning of session data is that it’s unseen to anything outside the session. This becomes a tricky issue if you use Database Session State (462), because you have to work hard to isolate the session data from the record data that sits in the database.

If you have a lot of users, you’ll want to consider clustering to improve your throughput. In this case you’ll want to think about whether you need session migration. Session migration allows a session to move from server to server as one server handles one request and other servers take on the others. Its opposite is server affinity, which forces one server to handle all requests for a particular session. Server migration leads to a better balancing of your servers, particularly if your sessions are long. However, that can be awkward if you’re using Server Session State (458) because often only the machine that handles the session can easily find that state. There are ways around that—ways that blur the lines between Database Session State (462) and Server Session State (458).

Server affinity can lead to bigger problems than you might initially think. In trying to guarantee server affinity, the clustering system can’t always inspect the calls to see which session they’re part of. As a result, it will increase the affinity so all calls from one client go to the same application server. Often this is done by the client’s IP address. If the client is behind a proxy, that could mean that many clients are all using the same IP address and are thus tied to a particular server. This can get pretty bad if you see most of your traffic handled by one server that bags the IP address for AOL!

If the server is going to use the session state, it needs to get it into a form that can be used quickly. If you use Server Session State (458), the session state is pretty much there. If you use Client Session State (456), it’s there, but often needs to be put into the form you want. If you use Database Session State (462), you need to go to the database to get it (and maybe do some transforming as well). This implies that each approach can have different effects on the system’s responsiveness. The size and complexity of the data will have an effect on this time.

If you have a public retail system, you probably don’t have that much data going into each session, but you do have a lot of mostly idle users. For that reason Database Session State (462) can work nicely in performance terms. For a leasing system you run the risk of schlepping masses of data in and out of the database with each request. That’s when Server Session State (458) can give you better performance.

One of the big bugbears in many systems is when a user cancels a session and says forget it. This is particularly awkward with B2C applications because the user usually doesn’t actually say forget it, it just disappears and doesn’t come back. Client Session State (456) certainly wins here because you can forget about that user easily. In the other approaches you need to be able to clean out session state when you realize it’s canceled, as well as set up a system that allows you to cancel after some timeout period. Good implementations of Server Session State (458) allow you to do this with an automatic timeout.

As well as what happens when a user cancels, consider what happens when a system cancels: A client can crash, a server can go south, and a network connection can disappear into the ether. Database Session State (462) can usually cope with all three pretty well. Server Session State (458) may or may not survive, depending on whether the session object is backed up to a nonvolatile store and where that store is kept. Client Session State (456) won’t survive a client crash, but should survive the rest going down.

Don’t forget the development effort involved in these patterns. Usually the Server Session State (458) is the easiest on development resources, particularly if you don’t have to persist the session state between requests. Database Session State (462) and Client Session State (456) will usually involve code to transform from a database or transport format to the form that the session objects will use. That extra time means that you aren’t able to build as many features as quickly with as you would with Server Session State (458), particularly if the data is complex. On first sight Database Session State (462) might not seem that complex if you’ve already got to map to database tables, but the extra development effort comes in keeping all the other uses of the database isolated from the session data.

The three approaches aren’t mutually exclusive. You can use a mix of two or three of them to store different parts of the session state. This usually makes things more complicated, however, as you’re never sure which part of the state goes in what part of the system. Nevertheless, if you use something other than Client Session State (456), you’ll have to keep at least a session identifier in Client Session State (456) even if the rest of the state is held using the other patterns.

My preference is for Server Session State (458), particularly if the memento is stored remotely so it can survive a server crash. I also like Client Session State (456) for session IDs and for session data that’s very small. I don’t like Database Session State (462) unless you need failover and clustering and if you can’t store remote mementos or if isolation between sessions isn’t an issue for you.