With the development of high transaction distributed applications, distributed caching solutions have become highly desirable to achieve performance scalability. NCache is a good choice as an in-memory distributed data store since it provides linear scalability and high availability.
So far, so good, but how to ensure data integrity in such shared environments is a big deal. Since two or more clients can access and alter the same data simultaneously in your application, the result can be inconsistent data. Once data integrity breaches occur, the data in the cache becomes virtually useless.
In this blog post, I will explain how this problem occurs and how NCache saves you with its distributed locking feature.
NCache Details NCache Locking Ncache Docs
Data Integrity Problems Without Locking
To understand the data integrity problem in detail, we consider the example of an online E-Commerce store where sellers upload their products while 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 its price. Now 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.
The scenario is shown in Figure 1.
- Client 1 reads the details of the product in the cache.
- Client 2 also reads the product data. It is correct.
- Client 1 now updates product details in the cache.
- Client 2 also updates the product details in the cache.
- Updates made by client 1 are lost.
Let’s see how NCache resolves this problem.
NCache Details Pessimistic Locking Optimistic locking
NCache Distributed Locking
NCache provides a flexible way of securing your data according to your business needs by allowing you to lock your data. One user takes control of a chunk of data and updates it. Meanwhile, no other user can manipulate that data.
Based on your application scenario, you can choose either of these NCache locking mechanisms:
- Pessimistic locking (transactional or exclusive locks): Locks an item exclusively which makes it inaccessible for other users.
- Optimistic locking (lock through item versioning): Uses item versioning which makes an item accessible for other users.
Pessimistic Locking for Sensitive Data
You should use the pessimistic locking strategy when the data that requires updates in your application is sensitive. You can use a LockHandle to acquire an explicit lock on your data. Once you are done, the lock is released.
Now, we see that how NCache pessimistic locking solves the data consistency problem in our example. You can acquire an exclusive lock on the product to update meanwhile, no other user can access that product. This is illustrated in figure 2.
- Client 1 acquires lock over the product and starts adding and updating the product details.
- Client 2 is denied access to read product details and must wait until the lock is released.
- Once the lock is released, Client 2 can carry on its regular operations on the product.
It is important to note that, NCache supports two sets of APIs: with and without locking. If the user wants to use pessimistic locking, then APIs with locking parameters should be used everywhere in applications where strong data consistency is the requirement. Let’s see how you can do it with NCache locking feature. In the following code segment, first, a new LockHandle is created and then the timespan is set to specify the time period for which the lock will be acquired. This LockHandle works as a lock ID to identify the lock. A lock is then acquired using the Get method with a key and LockHandle.
Now you can perform your business operations and manually release the lock or wait for the timespan to end.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
// Pre-condition: Cache is already connected // Create a new lock handle to fetch an Item using locking LockHandle lockHandle = new LockHandle(); // Timespan for which lock is to be taken TimeSpan timeSpan = TimeSpan.FromSeconds(5); // Get item from the cache and lock it var result = cache.Get(key, true, timeSpan, ref lockHandle); // Verify if the item is locked successfully if (result != null) { // Item has been successfully locked } else { // Key does not exist // Item is already locked with a different LockHandle } //Unlock item in cache manually cache.Unlock(key, lockHandle); |
Here, Get API with lock parameters immediately returns without waiting for lock acquisition. You need to make explicit reties to acquire a lock on items in case you fail in the first place.
You can also acquire a lock using the Lock method which associates a LockHandle with a key. NCache provides various ways to acquire/release an explicit lock which enables flexible locking. If you intend to implement NCache locking, using the NCache sample application for item locking on GitHub will be helpful.
NCache Details Pessimistic Locking Optimistic locking
Optimistic Locking for Data Availability
Pessimistic locking is great but it may not be an optimal approach when response time is critical for your application. This is where optimistic locking comes in handy.
NCache optimistic locking uses cache item versioning to overcome the thread starvation caused in the case of explicit lock. Hence, you can work on a version of the cached item which is incremented with every update to that item. NCache keeps track of the item version and you don’t need to worry about data consistency.
Figure 3 shows how it’s done:
- Client 1 adds details about the product and the CacheItemVersion is set to v1.
- Client 2 reads updated product details with CacheItemVersion v1.
- Client 1 again alters the details due to which CacheItemVersion is incremented and it becomes v2.
- Now when Client 2 tries to update details using old CacheItemVersion v1, the operation fails with CacheItemVersion
- Client 2 gets the latest CacheItemVersion
- Client 2 updates the product details using CacheItemVersion. Now the operation is performed successfully and increments the CacheItemVersion by 1 automatically.
This ensures all users have updated CacheItemVersion at all times and no user is refused access to any product in the store.
The following code shows how to do it:
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 |
// 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 = cacheItem.GetValue(); 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; //Insert call will fail with LockingException, if cache contains a newer version of the cache item. //In case of LockingException, we can fetch the latest cache item from cache and update it cache.Insert(key, updateItem); // If it matches, the insert is successful, otherwise it fails } |
Why Use NCache Locking
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.
NCache ensures the integrity and consistency of your data in highly distributed environments with utmost flexibility. Based on your application scenario, you can adopt different locking mechanisms in various ways provided by NCache. Eventually, you will have concurrency without any data inconsistency!
Such amazing information you have, to give others. Thanks for letting us know about it. Please keep sharing such information, that ensures the security of our properties.