InProc Cache or Session Are Bad for Your Health
I've been doing a lot of performance tuning for our application lately, and the headline above is my number one tip for you. Unless you're writing a tiny application that will never ever have to scale, relying on the default ASP.NET Cache and Session modes - in process mode, that is - is a very bad idea.
If you use in-process memory, you will get stuck without the ability to scale, that is move to a web-garden (more than one worker process on the same server) or a web-farm (more than one server serving requests for your app). Anyway, the idea is the same: Most applications will need more than one process to run them.
And why is that, might you ask. Well, consider that on a 32-bit machine every process can utilize up to 2GB of memory. 2GB is not a lot for ASP.NET applications which are quite the memory dogs: All these server controls that get created on each postback/UpdatePanel Ajax callback don't come in cheap, and if you have enough users, well, these OutOfMemoryExceptions are right around the corner. Even on a 64-bit maching, using more than one worker process can help you better utilize your server resources. Not to talk about web-farm scenarios where at least 2 worker processes is a given (one for each server).
And why would InProc bother you when trying to use more processes, you might ask. The session is the easiest example, and also the easiest one to fix. If you use In-Proc session the user's data will be stored in one process, but when his request is served in another process, there'll be nothing there and he'll be treated as if he hadn't logged in. Why is it easy to fix? Well, all you need is to change the storage definition in your web.config to either state-server or SQL. Of course, you'll soon be getting some SerializationExceptions if you stored in the session anything that is not serializable, so you'd have to fix that as well.
The bigger problem is caching. On a 2 and more worker processes scenario, each process has it's own Cache object. Suppose you have the following code (not best practice code, mind you):
public List<User> GetAllUsers()
{
List<User> users = HttpContext.Current.Cache["AllUsers"] as List<User>;
if (users == null)
{
users = UsersRepository.GetAllUsers();
HttpContext.Current.Cache["AllUsers"] = users;
}
return users;
}
public void Add(User u)
{
UserRepository.Add(u);
HttpContext.Current.Cache.Remove("AllUsers");
}
You're storing the database outcome of UsersRepository.GetAllUsers() in the cache. The process that called the method will put the objects in the cache, and the other processes will have nothing. But that's OK, right? They'll just go to the database again and put the objects in their own cache. Thing is, adding another user is also done by a single process. And it will remove the cache-key only from its own cache. The other processes will not know that the data has been updated, and requests routed to them will have data that is obsolete, that is, cached data without the newly added user. In some applications that might not be the worst thing in the world, but you should definitely take this into consideration.
ASP.NET doesn't have a built-in out of process solution for caching, so if you're going to use cache in your application you will have to look at third party providers. There is a pretty good Memcached provider for .NET we've started to use. Memcached is an open source distributed caching solution. It provides your application with an out-proc caching, and the cache processes can even be on different servers, allowing your application to scale if it needs to. You can read more about that here.
You can also look into Cacheman, which is written in managed code, but I haven't checked that out yet. Also, there are many retail solutions, which tend to be rather expansive.
Whatever solution you choose, the code you write should be ignorant of it, so you should probably wrap it in your own ICacheProvider or something. The underlying service shouldn't matter much at the end. The only thing that client code should be aware of is the fact that the objects will be stored on a different process. So all items must be Serializable, and getting and retrieving items from the cache is bound to be slower than the default ASP.NET cache, so that should be done sparingly. Once client is aware of that and behaves accordingly, you should be able to easily switch your cache provider to a more sophisticated one, if you see the need to.
This is something you should take into account early on in the project. Assume that your application will run on more than one process and test it for that. Session and cache is the beginning, but you should also be careful with static variables, the ASP.NET Application hash-table and anything else that is stored in-process or gets run once for a single AppDomain. Remember, practically any web-application will have more than one process, so that means more than one AppDomain, more than one HttpApplications, more than one HttpContext.Current.Cache, more than one anything.
I think the main problem is that everything defaults to InProc in ASP.NET, and we have a tendency to leave defaults as they are. This is one case where leaving the defaults as they are is a really bad idea.