When dealing with high transaction applications (especially those that incorporate NCache as part of their caching tier), data integrity is a central concern – as are the measures you employ to avoid such issues. NCache offers its users a Distributed Locking feature to overcome this problem. In this blog, we’ll be discussing NCache Distributed Locking as it pertains to Scala applications.
Data Integrity Problems
Basically, as we’ve previously alluded, Locking becomes necessary in any environment with shared resources. For instance, if your cache cluster has a cache item that two clients access simultaneously for separate operations, they create a conflict. For example, if the item in question relates to items in stock and two employees simultaneously try to adjust it according to the units sold that day and the arriving units in stock. In this case, both employees won’t know the actual quantity since there is no defined way of handling such situations.
NCache Distributed Locking
Ideally, in such situations, you should be able to either lock each cache operation as soon as it becomes involved in an operation. However, we do have to consider that this would slow things down. Thus, alternatively, there should be a mechanism to allow operations to occur simultaneously, catering to them both. NCache lets users use both in the form of Pessimistic and Optimistic Locking, respectively.
Implementing Pessimistic Locking
This technique uses a LockHandle to block all other users from performing operations when an operation is already in progress and releases the cache item upon conclusion. As such, it is best used when your application data is sensitive. Given that, how does one go about Pessimistic Locking their data?
First and foremost, you need to ensure you’ve configured the Scala client with NCache, as discussed in the blog on Scala. Second, you must add the appropriate dependencies and namespaces as identified here. Next, you need to implement a code similar to the following, where a LockHandle is explicitly created on an item with a particular key for a specific TimeSpan.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
// Pre-Requisite: Cache is already connected // Item is already added in the cache // Specify the key of the item val key = "Product:1001" // Create a new LockHandle val lockHandle = new LockHandle // Specify time span of 10 seconds for which the item remains locked val lockSpan = new TimeSpan(0, 0, 10) // Lock the item for a time span of 10 seconds val lockAcquired = cache.lock(key, lockSpan, lockHandle) // Verify if the item is locked successfully if (lockAcquired) { // Item has been successfully locked } else { // Key does not exist // Item is already locked with a different LockHandle } |
You can also lock items during get operatons. Additionally, you can release them explicitly or by using update operations.
Implementing Optimistic Locking
Generally, this technique is employed in read-intensive applications to deal with the data availability problem that Pessimistic Locking causes, and to avoid thread starvation. Additionally, Optimistic Locking involves item versioning, i.e., a numeric value that represents the version of the cached item. This property increments with every update to an item allowing you to track changes. Therefore, provided the pre-requisites discussed previously have been fulfilled, you can implement it as follows:
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 |
// An item is added in the cache with itemVersion val key = "Product:1001" // Initialize the cacheItemVersion val version = new CacheItemVersion // readThruOptions set to null val cacheItem = cache.getCacheItem(key, version, null) // If result is not null if (cacheItem != null) { var prod = cacheItem.getValue(classOf[Product]) prod.setUnitsInStock(100) // Update the retrieved cacheItem according to requirement // Create a new cacheItem with updated value val updatedItem = new CacheItem(prod) // Set the itemVersion. This version will be used to compare the // item version of cached item updatedItem.setCacheItemVersion(version) cache.insert(key, updatedItem) // If it matches, the insert will be successful, otherwise it will fail } |
This code sample demonstrates how to retrieve and update an item with the item version. Alternatively, you can also remove the item with the item version.
Conclusion
Secure and consistent data is crucial for today’s businesses. And should your high-traffic, data-focused Scala application suffer from any inconsistencies, you would do well to take advantage of the Locking strategies offered by NCache.
Of course, the feature isn’t limited to Scala, and you can read about it in the NCache Documentation on the topic or read our blog on Using Locking with Cached Data in NCache for .NET.