ASP.NET web applications, .NET web service applications, and other .NET server applications need to handle extreme transaction loads without slowing down. Although their application tier scales linearly, the data storage and database tier does not scale and therefore becomes a bottleneck. As a result, the entire application cannot scale.
Originally, simple in-memory distributed key-value stores like Memcached and later Redis was introduced on Unix/Linux platforms to help resolve this scalability problem. They quickly became quite popular mainly because they provided linear scalability just like the application tiers and removed the database bottleneck
NCache Details NCache Docs NCache Client APIs
Limitations in Key Value Stores
But, despite their popularity, these solutions were very simple, basic in nature, and didn’t really solve many problems facing real-life applications. Some of the areas where these solutions were very weak included:
- Lack of high availability
- Lack of intelligent ways of keeping the cache fresh
- Lack of SQL querying
- Lack of server-side caching code (e.g., Read-through)
For example, high availability was so poor in Memcached that third parties started developing high availability “fix ins” for it. But the underlying architecture was not designed for high availability and therefore these solutions remained quite limited in nature. Redis had the same availability issues but later re-architected their product to incorporate some high availability features like data replication and failover support. But there are still big holes in all key-value store products like Memcached and Redis. This is where distributed cache solutions came to the rescue.
.NET Distributed Cache as 2nd Generation Key Value Store
A .NET distributed cache like NCache on the other hand was designed from day one to address all the above-mentioned limitations. So, in essence, NCache is a 2nd Generation to the original key value stores like Memcached and Redis. NCache is a popular 10-year-old distributed cache for .NET.
Dynamic Cache Cluster & Data Replication
A distributed cache like NCache has a self-healing dynamic cache cluster that pools all the CPU and memory resources from all cache servers in the cluster. At the same time, NCache provides a variety of caching topologies with different data distribution and replication strategies. This allows NCache to linearly scale without compromising on high availability. And, even if a cache server goes down, no data loss occurs, the cache cluster continues running, and all the applications using the cache also continue without any interruptions.
Keeping Cache Fresh
Another area where a distributed cache like NCache shines, is keeping the data fresh and always consistent with the database. NCache does this through a variety of features including expirations, event driving SqlDependency, polling based DbDependency, and support for CLR procedures for relational databases. Expirations work just like key value stores but SqlDependency and DbDependency allow NCache to synchronize the cache with any changes in the database for the related data. And, CLR stored procedures allow you to directly update the cache from your SQL Server database when the corresponding data changes.
This means that even if a third-party application changes data in the database, NCache immediately updates itself accordingly. The benefit is that you can cache almost all your application data instead of caching read-only data, providing better performance and scalability gain.
Searching Cache with SQL
So, when you’re able to cache almost all your data due to “keep cache fresh” features, you run into the issues of not being able to find data easily if the only mechanism is key-value. But, if you could search data based on attributes, then a distributed cache like NCache becomes as easy to search as a database. NCache provides you SQL and LINQ querying for this purpose.
In addition to SQL querying based on object attributes, you can assign Groups, Tags, and Named Tags to cached items and include them in your SQL queries. Below is an example of SQL querying in C#. For example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
using Alachisoft.NCache.Runtime; using Alachisoft.NCache.Runtime.Exceptions; using Alachisoft.NCache.Web.Caching; public void SearchDataUsingSQL() { Cache cache = NCache.InitializeCache("myparitionreplica"); string query = "SELECT this.Category, " + "MAX(Prod.Product.ProductID) " + "WHERE this.Category = ? " + "GROUP BY this.Category"; Hashtable values = new Hashtable(); values.Add("Category", 4); ICacheReader reader = cache.ExecuteReader(query, values); if (reader.FieldCount > 0) { while (reader.Read()) { //you can get value through the field name... object category = reader.GetOrdinal("Category"); //perform operations } } reader.Close(); return data; } |
Server-Side Code
Finally, there is server-side code like Read-through, Write-through, Custom Dependency, and Cache-Loader and Refresher that is very useful. This code is developed by you but is called by the distributed cache and runs in the cache cluster. With the help of this code, you can simplify your applications and move a lot of commonly used code to the caching tier.
For example, NCache calls your Read-through handler when your application asks for some data that is not in the cache and the application tells NCache to call Read-through in that case. Similarly, you can combine Read-through with expirations and database synchronizations to auto-reload the cached item instead of removing it from the cache.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
using Alachisoft.NCache.Runtime.Caching; using Alachisoft.NCache.Runtime.DatasourceProviders; using Alachisoft.NCache.Runtime.Dependencies; public class SampleReadThruProvider : IReadThruProvider { public void Init(IDictionary parameters, string cacheId) { // Create SQL connection and other initializations at the server side } //Responsible for loading an item from the external data source. public void LoadFromSource(string key, out ProviderCacheItem cacheItem) { //where LoadFromDataSource is the dummy method to load data from data source. object value = LoadFromDataSource(key); //Attach SQL dependency to your object string query = "SELECT ProductID FROM dbo.Products WHERE ProductID = 1001"; cacheItem = new ProviderCacheItem(value); cacheItem.Dependency = new SqlCacheDependency(connectionString, query); //Set expirations cacheItem.SlidingExpiration = new TimeSpan(0, 5, 0); //Indicates whether item should be reloaded on expiration if //ReadThru provider is specified. cacheItem.ResyncItemOnExpiration = true; } public void Dispose() { //... } } |
Write-through works in the same fashion as Read-through but for updates. It updates your database when your application updates the cache. And, if you prefer, Write-behind updates the database asynchronously even though the cache gets updated synchronously. Finally, Cache Loader is called when the cache is started so you can pre-load it with the desired data.
Conclusion
As you can see, NCache, an open-source .NET distributed cache, offers a lot more power to your cache than a simple Redis key-value store or Memcached. Below is a detailed key value store comparison with a distributed cache service i.e Redis vs NCache and Memcached vs NCache.