Every developer at one point or another used caches to increase scalability of typical web application. Up until some time ago, majority of people creating web application would cache most heavily used reference data in-process to the application. That had its own pluses and minuses of course, as any design. On plus side it facilitated fairly quick retrieval and writing to the cache, as there is no network or inter-process communication between application and cache, however there were plenty of minuses to this approach as well. What happens if critical exception forces IIS worker process to recycle? Well, cache is gone on that web front end. What about ability to scale cache out? With caches running separately on all web front ends that doesn’t exist, as IIS worker process hosting both application and cache will continue to increase in footprint. That of course will negatively affect GC performance causing GC to walk “deep roots” and application will be spending more time in GC.
About 5 years ago I first started working with customers that were implementing third party distributed cache clusters as separate tiers of their applications from vendors such as ScaleOut and NCache. Then finally Microsoft introduced Windows AppFabric Cache around 2010 timeframe. Immediately I became very intrigued with this technology, as all in-memory NoSQL\distributed cache technology and through last 4 years was lucky to have helped number of customers in implementation.
So what is Windows AppFabric Cache? Windows AppFabric cache is what I call a distributed cache cluster technology and is very similar in its core idea to products such as memcached or Redis. It provides a distributed cache that you can integrate into both Web and desktop applications. AppFabric can improve performance, scalability and availability while, from the developer perspective, behaving like a common memory cache. You can cache any serializable object, including DataSets, DataTables, binary data, XML, custom entities and data transfer objects.
The AppFabric client API is simple and easy to use, and the server API provides a full-featured Distributed Resource Manager (DRM) that can manage one or more cache servers (with multiple servers comprising a cache cluster). Each server provides its own memory quota, object serialization and transport, region grouping, tag-based search and expiration. The cache servers also support high availability, a feature that creates object replicas on secondary servers. Windows AppFabric exposes a unified cache tier to client applications by fusing together memory across servers. The AppFabric Cache architecture consists of a ring of cache servers running the AppFabric Distributed Caching Windows service as well as client applications that utilize the AppFabric Cache client library to communicate with the unified cache view. The cache cluster is a collection of one or more instances of the Caching Service working together in the form of a ring to store and distribute data. Data is stored in memory to minimize response times for data requests. Cluster management can be performed either by designated lead hosts or by storing the cluster configuration information in a SQL Server database. Each node in the cluster is running AppFabric Distributed Cache Windows Service. For each cache server, only one instance of the Caching Service can be installed
Product is a free add-on to Windows Server – http://www.microsoft.com/en-us/download/details.aspx?id=27115 and requires no additional licensing, however if you will use HA feature it has to be deployed on Windows Server Enterprise or Data Centre edition.
Well, so cache cluster runs across number of dedicated nodes, but what about caches? A named cache, also referred to as a cache, is a configurable unit of in-memory storage that all applications use to store data in the distributed cache. You can configure one or more named caches for each of your applications. Each cache can be configured independent of the others, which lets you optimize the policies of each cache for your application. Each cache spans all cache hosts in the cluster. When the AppFabric Caching features are first set up, a cache comes pre-configured with the name “default.” You can store data in this default cache, or you can create and use named caches.All caches are defined in the cluster configuration. Use the Windows PowerShell administration tool to create or reconfigure caches. Some settings can only be configured when you first create the cache. Other settings can be changed later, but may require the entire cache cluster to be restarted. There is a limit of 128 named caches for the cluster. Restarting your cache cluster causes all data to be flushed from all named caches in the cluster, but the named caches themselves are persisted.
Regions is an additional data container that can be placed inside the cache. Regions are a cache construct: they are not defined in the cluster configuration settings. Regions are optional; if you want to use them, you must explicitly create them at run time with your application code by using the CreateRegion method. I am actually not a big fan of regions for following reason:
To provide this added search functionality, objects in a region are limited to a single cache host. Thus, applications that use that data cannot realize the scalability benefits of a distributed cache. In contrast, if you do not specify a region, cached objects can be load balanced across all cache hosts in the cache cluster. Regions offer searching capabilities, but by limiting cached objects to a single cache host, the use of regions presents a trade-off between functionality and scalability
So how do I program against AppFabric Cache? To start using AppFabric caching in your application, just add the references to CacheBaseLibrary.dll, CASBase.dll, CASMain.dll and ClientLibrary.dll in your Visual Studio project.Make sure that the using statement (Imports in Visual Basic) is at the top of your application code to reference the Microsoft.ApplicationServer.Caching namespace.
Application code should be designed so that it can function independent of the cache, and not require that cached data always be available. Because data in the cache is not persisted in a durable fashion, the possibility exists that the data in the case could be unavailable. DataCacheFactory Class – provides methods to return DataCache objects that are mapped to a named cache. This class also enables programmatic configuration of the cache client. What we are looking at here is a typical Factory Pattern well familiar to any developer. IMPORTANT – constructing DataCacheFactory is very expensive, if possible, store and reuse the same DataCacheFactory object for application lifetime to conserve memory and optimize performance:
// Create instance of cachefactory
DataCacheFactory factory = new DataCacheFactory();
Use the DataCacheFactory object to create a DataCache object (also referred to as the cache client).
// Get a named cache from the factory
DataCache MyCache= factory.GetCache(“catalog_products");
Now its fairly easy to write and read to our cache:
//add string object to cache with key “product100"
myCache.Add(“product100", new Product(“car”));
//add or replace string object in cache using key “product100"
myCache.Put(“product100", new Product (“toaster”));
//get Product from cache using key “Product100"
string myString1 = (Product) myCache.Get(“Product100");
With AppFabric Cache you will be using Cache-Aside Pattern, explained here – http://msdn.microsoft.com/en-us/library/dn589799.aspx to emulate read-through functionality.
When high availability is enabled by setting Secondaries parameter in New-Cache Powershell commandlet to 1, a copy of each cached object or region is maintained on a separate cache host. The cache cluster manages maintenance of these copies and supplies them to your application if the primary copies are not available. No code changes are required to make your cache-enabled applications highly available. This is generally used for Activity data, performance overhead makes it not worth it on reference data, just use cache-aside pattern. The cache cluster chooses where the secondary copies of objects and regions are stored. Just as AppFabric distributes cached objects across all cache hosts in the cluster, it also distributes the secondary copies of those objects across all cache hosts in the cluster
If a cache host fails (assuming there are still a sufficient number of cache hosts available to keep the cluster running) , aside for brief period of rebalancing, nothing changes for the cache-enabled application. The cache cluster re-routes requests for the object to the cache host that maintained the secondary copy of the object. Within the cluster, the secondary copies of all the primary objects are then elevated to become the new primary objects. Then, secondary copies of those new primary objects are distributed to other cache hosts across the cluster. Secondary objects on the cache host that failed are replaced by new secondary objects and distributed across the cluster. This process also applies to regions.
Pessimistic Concurrency or Refusing to Share Your Spoils. In the optimistic concurrency model –default, updates to cached objects do not take locks. Instead, when the cache client gets an object from the cache, it also obtains and stores the current version of that object. When an update is required, the cache client sends the new value for the object along with the stored version object. The system only updates the object if the version sent matches the current version of the object in the cache. Every update to an object changes its version number, which prevents the update from overwriting someone else’s changes. In the pessimistic concurrency model, the client explicitly locks objects to perform operations. Other operations that request locks are rejected (the system does not block requests) until the locks are released. When objects are locked, a lock handle is returned as an output parameter. The lock handle is required to unlock the object. In case the client application ends before freeing a locked object, time-outs are provided to release the locks. IMPORTANT – Pessimistic concurrency is very expensive, obviously affects application throughput and concurrency and I would definitely stay away from this construct unless its absolutely necessary for some reason. Locks are necessary evil, lets not introduce new ones.
Security. Windows Server AppFabric Caching features provide several options for managing security. By default, communication between cache clients and the cache cluster use both encryption and signing. In addition, you must explicitly add a Windows account to the list of allowed accounts before the associated user can access the cache cluster. I would actually recommend turning off transport level security (encryption and signing) and use your own methods like IPSec, VLANs, Firewalls to protect cache. I found that it enhances performance by taking off additional overhead on every call. When the security is enabled, the AppFabric Caching Service must run under an appropriate identity. For domain environments, this should be the built-in “NT Authority\Network Service” account. For workgroup environments, this should be a local machine account. However, there is one exception to the service account setting for a domain environment. When security is disabled by setting the security mode to None, it is possible to run the AppFabric Caching Service as a specific domain account other than Network Service. Finally, only authorized accounts can access cache cluster, use grant-cacheallowedaccessaccount cmdlet to grant access to windows user.
In the next part I will cover management of your cluster, troubleshooting, as well as some interesting best practices and gotchas that I learned so far.