How to Use Locking in a Distributed Cache for Data Consistency?

Businesses today are developing high traffic ASP.NET web applications that serve tens of thousands of concurrent users. To handle this type of load, multiple application servers are deployed in a load balanced environment. In such a highly concurrent environment, multiple users often try to access and modify the same data and trigger a race condition. A race condition is when two or more users try to access and change the same shared data at the same time but end up doing it in the wrong order. This leads to high risk of loosing data integrity and consistency. This is where distributed lock mechanism comes in very handy to achieve data consistency.

Distributed Locking for Data Consistency

NCache provides you with a mechanism of distributed locking in .NET/C# that allows you to lock selective cache items during such concurrent updates. This helps ensure that correct update order is always maintained. NCache is a distributed cache for .NET that helps your applications handle extreme transaction loads without your database becoming a bottleneck.

But before going into the details of distributed locking, you first need to know that all operations within NCache are themselves thread-safe. NCache operations also avoid race conditions[1] when updating multiple copies of the same data within the cache cluster. Multiple copies of the data occur due to data replication and NCache ensures that all copies are updated in the same correct order, thereby avoiding any race conditions.

Since we have that part cleared, consider the following code to understand how, without a distributed locking service, data integrity could be violated;

In this example consider the following possibility;

  • Two users simultaneously access the same Bank Account with balance = 30,000
  • One user withdraws 15,000 whereas the other user deposits 5,000.
  • If done correctly, the end balance should be 20,000.
  • If race condition occurs but not handled, the balance would be either 15,000 or 35,000 as you can see above. Here is how this race condition occurs:
    • Time t1: User 1 fetches Bank Account with balance = 30,000
    • Time t2: User 2 fetches Bank Account with balance = 30,000
    • Time t3: User 1 withdraws 15,000 and updates Bank Account balance = 15,000
    • Time t4: User 2 deposits 5,000 and updates Bank Account balance = 35,000

In both the cases, this code block would be disastrous to the Bank. To maintain data consistency in such cases, NCache acts as a distributed lock manager[2] and provides you with two types of locking:

1. Optimistic Locking (Item Versions)

In optimistic locking, NCache uses cache item versioning. At the server side, every cached object has a version number associated with it which gets incremented at every cache item update. When you fetch a CacheItem object from NCache, it comes with a version number. When you try to update this item in the cache, NCache checks if your version is latest or not. If not, then it rejects your cache update. This way, only one user gets to update and other user updates fail. Take a look at the following code explaining the case we presented above;

In the above example, if your cacheitem version is the latest, NCache performs the operation successfully. If not then an operation failed exception is thrown with the detailed message. In this case, you should re-fetch the latest version and redo your withdrawal or deposit operation.

With optimistic locking, NCache ensure that every write to the distributed cache is consistent with the version each application holds.

2. Pessimistic Locking (Exclusive Locking)

The other way to ensure data consistency is to acquire an exclusive lock on the cached data. This mechanism is called Pessimistic locking. It is essentially a writer-lock that blocks all other users from reading or writing the locked item.

To clarify it further, take a look at the following code;

Here, we first try to obtain an exclusive lock on the cache item. If successful, we will get the object along with the lock handle. If another applications had already acquired the lock, a LockingException would be thrown. In this case, you must retry fetching the item after a small delay.

Upon successfully acquiring the lock while fetching the item, the application can now safely perform operations knowing that no other application can fetch or update this item as long as you have this lock. To finally update the data and release the lock, we will call the insert API with the same lock handle. Doing so, it will insert the data in the cache and release the lock, all in one call. After releasing the lock, the cached data will be available for all other applications.

Just remember that you should acquire all locks with a timeout. By default, if the timeout is not specified, NCache will lock the item for an indefinite amount of time. If the application crashes without releasing the lock, the item will remain locked forever. For a work around you could forcefully release it but this practice is ill advised.

Failover Support in Distributed Locking

Since NCache is an In-Memory Distributed Cache, it also provides complete failover support so that there is simply no data loss. In case of a server failure your client applications keep working seamlessly. In a similar fashion, your locks in the distributed system are also replicated and maintained by the replicating nodes. If any node fails while a lock was acquired by one of your applications, the lock will be propagated to a new node automatically with its specified properties e.g. Lock Expiration.

Conclusion

So which locking mechanism is best for you, optimistic or pessimistic? Well, it depends on your use case and what you want to achieve. Optimistic Locking provides an improved performance benefit over Pessimist Locking especially when your applications are read intensive. Whereas, Pessimist Locking is more safe from a data consistency perspective. Choose your locking mechanism carefully. For more details head on to the website. In case of any questions head over to the support page or put in a question either on StackOverFlow or on Alachisoft Forum.

Sources

[1] http://searchstorage.techtarget.com/definition/race-condition
[2] https://en.wikipedia.org/wiki/Distributed_lock_manager
[3] http://stackoverflow.com/questions/129329/optimistic-vs-pessimistic-locking

Leave a Reply

Your email address will not be published. Required fields are marked *