More and more applications are becoming high transaction applications which all require extreme performance during peak hours. And, for this reason, they’re adopting distributing caching in their application architectures to help achieve this scalability goal. Just like a database, a distributed cache like NCache is also a datastore, but it is distributed and it is also in-memory.
Due to the increased use of distributed caching, more and more data is being put in these distributed caches. As a result, just like databases, a distributed cache also has to ensure data integrity. With high transaction environments, it is possible for two or more clients to access and alter the same data simultaneously which can be a threat to data integrity. Once data integrity is violated, the data in the cache becomes virtually useless.
In this blogpost, I will explain how this problem occurs and how NCache solves it for your .NET applications.
Data Integrity Problems Without Locking
NCache does hold the inherent facility to maintain the concurrency and integrity of the data that is being put into the cache, much like the concept of transactions in database systems – the data update is completed and committed or is rolled back if it is unsuccessful. This implies that all data within the cache is always correct as in-memory datastore which stays synchronized across multiple servers ensures integrity within the cache.
To understand how NCache ensures data integrity as your distributed cache system let us consider the example of a bank with two account holders who make a transaction at the same time. Suppose the balance of the account is $5,000.
Person 1 withdraws $2000 from the account and Person 2 deposits $1000 into the account. Now Person 1 checks the balance of the account (which shows $5000) and withdraws $2,000 and the total balance shown is $3,000.
Person 2 checks the balance of the account at the same time (which shows $5000 as well) and deposits $1,000 and the total balance is $6,000 which is incorrect. The remaining balance should have shown $4,000 (as $5,000 – $2,000 + $1,000 = $4,000). Scaling up this problem for large businesses can create torrents of problems.
Similarly, if we consider the example of an online E-Commerce store where sellers upload their products and customers view and place orders for those products to purchase. Suppose a customer wants to view a certain product and the seller of that product wants to update it to add or remove some discount. If a customer is viewing a product at a higher price and the seller adds a discount that the user cannot see yet, the user ends up purchasing the product at a higher price.
To understand this problem in a little more depth, consider Figure 1.
Figure 1. Problem with data inconsistency
- Client 1 adds details about the product in cache
- Client 1 reads this data; it is correct.
- Client 2 now updates the same product data.
- Client 1 updates this data – changes made by client 2 are lost
- When Client 2 wishes to read the value now, it gets the value updated by client 1. Updates made by client 2 are lost
The Solution: NCache Data Locking
To address these problems, NCache provides a flexible way of securing your data according to your business needs by introducing locking where one user takes control of a chunk of data to make changes and updates while no other user client can.
NCache provides two kinds of locks:
- Pessimistic locks (transactional or exclusive locks): Pessimistic locking resembles the concept of transactions in databases. When a user has obtained a lock on a certain chunk of data, other users do not have the authorization to access or modify this data until the first user releases that lock.
- Optimistic locks (locking with dirty reads and item versioning): Optimistic locking allows you to maintain data consistency while ensuring that the data is available for other users to read. This is done using the concept of item versions that keeps track of the version of data that is currently in the cache.
Pessimistic Locking for Sensitive Data
Pessimistic locking strategy is used when the user needs to alter or update very sensitive data. With this locking strategy, you acquire a lock over a product for a specific amount of time using lock handles and perform all operations necessary. Once it is done, the lock is released.
During the time the lock is acquired, no other user can access that product. This is illustrated in figure 2.
Figure 2. Pessimistic Locking
- Client 1 acquires a lock over the product and starts adding and updating the product details
- Client 2 wishes to read and alter the product details. The request is denied and Client 2 has to wait till the lock acquired is released
- After the time span set for the lock is complete, the lock is released and Client 2 can now carry on its regular operations on the product.
With a minor alteration to your code after you have installed NCache, you can acquire this feature and secure any sensitive data in your distributed cache. In this code segment, first, a new LockHandle is made which is used to fetch an item from the cache. This LockHandle works as a lock ID to identify the lock. Next, the timespan for which the lock is acquired is set in terms of minutes. Now the lock is acquired for the specified timespan with given LockHandle and key of the data that is to be locked. Now you can perform your business operations and manually release the lock or wait for it to automatically release the lock when the timespan ends.
// Pre-condition: Cache is already connected
// Create new lock handle to fetch Item using locking
LockHandle lockHandle = new LockHandle();
// Timespan for which lock is to be taken
TimeSpan timeSpan = TimeSpan.FromSeconds(5);
// Get item from cache and lock it
result = cache.Get(key, true, timeSpan, ref lockHandle);
// Your business logic goes here
//Unlock item in cache manually
Pessimistic locking is a great way to ensure the integrity of sensitive data but the major problem with this is that if the lock is acquired for too long, other requests for the locked data are queued up which increases the application response time and users are sent into waiting which causes starvation. To solve this, NCache allows the concept of Optimistic locking.
Optimistic Locking for Data Availability
In order to prevent starvation with pessimistic locking as discussed above, NCache provides optimistic locking to get the benefits of the locking feature without actually locking the items. optimistic locking allows you to maintain data consistency while ensuring that the data is available for other users to use. This is done using the concept of item version. An item version keeps track of the version of the data that is currently in the cache. Every time a user alters the product details, a linear increment is made in its item version. Figure 3 shows us how it’s done.
Figure 3. Optimistic Locking
- Step 1: Client 1 adds details about the product and the item version is set to 1001 and Client 2 reads the details with item version 1001
- Step 2: Client 1 again alters the details and the ItemVersion is incremented to 1002
- Step 3: Now when Client 2 tries to read details using ItemVersion 1001, it receives an error saying that the latest item version has been changed
- Step 4: Client 2 then sends a command asking for the latest ItemVersion
- Step 5: Once received, this new ItemVersion is used until further updates are made
- Step 6: Similarly, again if another update is made in the product, the ItemVersion is updated again
This ensures all users have updated ItemVersion at all times and no user is refused access to any product in the store. The code below shows how to do exactly that. This code segment first specifies the key of the cache item and initialize ItemVersion. It then checks if there is an item in the cache with the same ItemVersion and fetches that. Next, the cache item is updated under that ItemVersion. After doing so, an increment is made to the ItemVersion which is now used to fetch that particular cache item until further updates are made.
// Specify the key of the cacheItem
string key = "Product:1001";
// Initialize the cacheItemVersion
CacheItemVersion version = null;
// Get the cacheItem previously added in the cache with the version
CacheItem cacheitem = cache.GetCacheItem(key, ref version);
// If result is not null
if (cacheitem != null)
// CacheItem is retrieved successfully with the version
var prod = new Product();
prod = cacheitem.GetValue<Product>();
prod.Discount = 0.5;
// Create a new cacheItem with updated value
var updateItem = new CacheItem(prod);
//Set the itemversion. This version is used to compare the
// item version of the cached item
updateItem.Version = version;
// If it matches, the insert is be successful, otherwise it fail
// Item could not be retrieved due to outdated CacheItemVersion
Why Use NCache?
NCache is a fast and scalable .NET / .NET Core market-leading in-memory distributed cache that caches application data and removes performance bottlenecks related to your data storage and databases. It ensures data integrity in two different ways which are efficient and flexible to meet your business needs.
Secure and consistent data is crucial for today’s businesses and it would be a shame if something as simple as multi-user transactions damages your data integrity. Databases allow some level of protection from these inconsistencies and your distributed in-memory system should also have the ability to protect your data. NCache ensures the security of your data in a highly productive and flexible way. Head over to our website to learn more about how you can use our in-memory distributed cache and datastore for your .NET application data and reduce expensive database trips and bottlenecks as well as protect your data from inconsistencies!