Creating a Poor Man’s Distributed Cache in Azure
If you’ve read up on the Windows Server AppFabric (which contains Velocity, the distributed caching project) you’re likely familiar with the concepts of distributed cache. Distributed caching isn’t strictly limited to web environments, but for this post (or if I ramble on and it becomes a series) we’ll act like it does.
In a web environment, session state is one of the more problematic server-side features to deal with in multiple server applications. You are likely already familiar with all of this, but for those who aren’t: the challenge in storing session state is handling situations where a user’s first request goes to one server in the farm, then the next request goes to another. If session state is being relied upon, there are only a few options: 1) store session state off-server (for example, in a common SQL Server shared by all web servers) or 2) use “sticky” sessions so that a user’s entire session is served from the same server (in this case, the load balancer typically handles this). Each method has pros and cons.
Caching is similar. In typical web applications, you cache expensive objects in the web server’s RAM. In very complex applications, you can create a caching tier – this is exactly the situation Velocity/AppFabric solves really well. But, it’s often overkill for more basic applications.
The general rule of thumb(s) with caching is: 1) caching should always be considered volatile – if an item isn’t in the cache for any reason, the application should be able to reconstruct itself seamlessly. And 2) an item in the cache should expire such that no stale data retained. (The SqlCacheDependency helps in many of these situations, but generally doesn’t apply in the cloud.)
The last part about stale data is pretty tricky in some situations. Consider this situation: suppose your web app has 4 servers and, on the the home page, a stock ticker for the company’s stock. This is fetched from a web service, but cached for a period of time (say, 60 minutes) to increase performance. These values will quickly get out of sync – it might not be that important, but it illustrates the point about keeping cache in sync. A very simple way to deal with this situation is to expire the cache at an absolute time, such as the top of the hour. (But this, too, has some downsides.)
As soon as you move into a more complicated scenario, things get a bit trickier. Suppose you want to expire items from a web app if they go out of stock. Depending on how fast this happens, you might expire them based on the number in stock – if the number gets really low, you could expire them in seconds, or even not cache them at all.
But what if you aren’t sure when an item might expire? Take Worldmaps … storing aggregated data is ideal in the cache (in fact, there’s 2 levels of cache in Worldmaps). In general, handling ‘when’ the data expires is predictable. Based on age and volume, a map will redraw itself (and stats updated) between 2 and 24 hours. I also have a tool that lets me click a button to force a redraw. When one server gets this request, it can flush its own cache, but the other servers know nothing about this. In situations, then, when user interaction can cause cache expiration, it’s very difficult to cache effectively and often the result is just not caching at all.
With all of this background out of the way, even though technologies like the SqlCacheDependency currently don’t exist in Azure, there are a few ways we can effectively create a distributed cache in Azure – or perhaps more appropriately, sync the cache in a Windows Azure project.
In the next post, we’ll get technical and I’ll show how to use the RoleEnvironment class and WCF to sync caches across different web roles. Stay tuned!