• Facebook
  • Twitter
  • Youtube
  • LinedIn
  • RSS
  • Docs
  • Comparisons
  • Blogs
  • Download
  • Contact Us
Download
Show / Hide Table of Contents

Locking with Cache Item Versioning (Optimistic Locking)

Optimistic Locking in NCache provides a non-blocking concurrency control mechanism designed for read-intensive distributed applications. Instead of locking cache items for the duration of an operation, NCache uses CacheItemVersion, a numeric version automatically associated with each cache item and incremented on every update. This allows clients to detect concurrent modifications without blocking other threads, improving scalability and preventing thread starvation.

When a client retrieves an item from the cache, it can also retrieve the item's current version. During an update or remove operation, the same version is submitted back to the cache. If the version on the server has changed, the operation fails. Unlike Pessimistic Locking, where an item remains locked until an operation completes and may cause thread starvation, Optimistic Locking is better suited for scenarios where read operations are frequent and write conflicts are rare.

When to Use Optimistic Locking

In the previous example, we considered a single bank account used by two users simultaneously. Let's suppose User 1 acquired the lock for performing a deposit transaction on the bank account. User 2 is waiting for User 1 to free the lock before making a withdrawal. If User 1 enters an unstable state due to network connectivity issues without releasing the lock, User 2 will continue to wait without knowing about the failure. This results in starvation because User 2 remains blocked until the lock is released.

To avoid this problem, Optimistic Locking is a useful solution. With Optimistic Locking, User 1 can update the account without locking it, and the item version is updated accordingly. When User 2 wants to update the data, they will get the updated version based on the item version, ensuring that no data integrity issues occur. If any user operates on the data using an outdated item version, the operation will fail because the version no longer matches.

The CacheItemVersion adds an additional dimension to the application development using NCache. Applications achieve optimistic concurrency through NCache Item Versioning. When any item is added to the cache, the CacheItemVersion is returned to the cache client. This value indicates the number of updates performed on that item. With each update, the item version increments.

Prerequisites

  • .NET
  • Java
  • Python
  • Node.js
  • Legacy API
  • To learn about the standard prerequisites required to work with all NCache client-side features, please refer to the given page on Client-Side API Prerequisites.
  • For API details, refer to: Add, ICache, CacheItem, CacheItemVersion, Contains, Count, GetIfNewer, Insert, Remove.
  • To learn about the standard prerequisites required to work with all NCache client-side features, please refer to the given page on Client-Side API Prerequisites.
  • For API details, refer to: add, Cache, CacheItem, CacheItemVersion, contains, getCount, getIfNewer, insert, remove.
  • To learn about the standard prerequisites required to work with all NCache client-side features, please refer to the given page on Client-Side API Prerequisites.
  • For API details, refer to: Cache, CacheItem, insert, get_cacheitem, CacheItemVersion, getValue, get_if_newer.
  • To learn about the standard prerequisites required to work with all NCache client-side features, please refer to the given page on Client-Side API Prerequisites.
  • For API details, refer to: add, Cache, CacheItem, CacheItemVersion, contains, getCount, getIfNewer, insert, remove.
  • Install either of the following NuGet packages in your .NET client application:
    • Enterprise: Install-Package Alachisoft.NCache.SDK -Version 4.9.1.0
    • Professional: Install-Package Alachisoft.NCache.Professional.SDK -Version 4.9.1.0
  • Create a new Console Application.
  • Make sure that the data being added is serializable.
  • Add NCache References by locating %NCHOME%\NCache\bin\assembly\4.0 and adding Alachisoft.NCache.Web and Alachisoft.NCache.Runtime as appropriate.
  • Include the Alachisoft.NCache.Web.Caching namespace in your application.
  • To learn more about the NCache Legacy API, please download the NCache 4.9 documents available as a .zip file on the Alachisoft Website.

How to Retrieve and Update the Item with the Item Version

An Add operation returns the CacheItemVersion. If an item is added for the first time, a long value containing the TimeStamp of its creation is returned. This version is incremented by "1" every time an operation is performed on the item.

Optimistic Locking ensures that the user always gets the most updated copy of the item in the cache. If the user attempts to perform operations with an outdated version, NCache throws an exception, prompting the user to get the latest version from the cache.

In the example below, a cache is used by multiple applications and contains product data. A CacheItem is added to the cache. Both applications fetch the item with the current version. Application 1 modifies the productName and reinserts the item in the cache, which increments the item's version. Application 2 still holds the older version. If Application 2 updates the item's units in stock and reinserts the item in the cache, the insertion will fail. Application 2 will have to fetch the updated version to perform operations on that CacheItem.

Note

You can add an item to the cache using either Add or Insert method.

  • The Add method adds a new item to the cache and saves the item version for the first time.
  • The Insert method adds an item to the cache if it is not present already. If the item is present, it overwrites the existing value of an item and updates the item version.

The following code sections explain the operations performed by the application.

  • .NET
  • Java
  • Python
  • Node.js
  • Legacy API
// Precondition: Cache is already connected
// The item version is saved when item was added in cache
// 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
    // If result is Product type
    var prod = new Product();
    prod = cacheItem.GetValue<Product>();
    prod.UnitsInStock++;

    // Create a new CacheItem with updated value
    var updateItem = new CacheItem(prod);

    // Set the item version. This version will be used to compare the item version of cache item
    updateItem.Version = version;
    cache.Insert(key, updateItem);

    // If it matches, the insert will be successful, otherwise it will fail
}
else
{
    // Item could not be retrieved due to outdated CacheItemVersion
}
// Precondition: Cache is already connected
// The item version is saved when item was added in cache
// Specify the key of the CacheItem
String key = "Product:1001";

// Initialize the CacheItemVersion
CacheItemVersion version = new CacheItemVersion();

// readThruOptions set to null
CacheItem cacheItem = cache.getCacheItem(key, version, null);

// If result is not null
if (cacheItem != null)
{
    var prod = new Product();
    prod = cacheItem.getValue(Product.Class);
    prod.unitsInStock++;

    // Update the retrieved CacheItem according to requirement
    // Create a new CacheItem with updated value
    CacheItem updatedItem = new CacheItem(prod);

    // Set the item version. This version will be used to compare the item version of cache item
    updatedItem.setCacheItemVersion(version);
    cache.insert(key, updatedItem);

    // If it matches, the insert will be successful, otherwise it will fail
}
else
{
    // Item could not be retrieved due to outdated CacheItemVersion
}
# Precondition: Cache is already connected
# The item version is saved when item was added in cache

# Specify the key of the CacheItem
key = "Product:1001"

# Initialize the CacheItemVersion
version = ncache.CacheItemVersion(0)

cache_item = cache.get_cacheitem(key, cacheitemversion=version)

# If result is not None
if cache_item is not None:
    prod = cache_item.get_value(Product)
    prod.set_units_in_stock(20)

    # Update the retrieved CacheItem according to requirement
    # Create a new CacheItem with updated value
    updated_item = ncache.CacheItem(prod)

    # Set the item_version. This version will be used to compare the
    # item version of cache item
    updated_item.set_cache_item_version(version)

    cache.insert(key, updated_item)

    # If it matches, the insert will be successful, otherwise it will fail
// This is an async method
// Precondition: Cache is already connected
// The item version is saved when item was added in cache
// Specify the key of the CacheItem
var key = "Product:1001";

// Initialize the CacheItemVersion
var version = new ncache.CacheItemVersion();

// readThruOptions set to null
var cacheItem = await this.cache.getCacheItem(key, null, version);

// If result is not null
if (cacheItem != null)
{
    var prod = new Product();
    prod = cacheItem.getValue(ncache.JsonDataType.Object);
    prod.unitsInStock++;

    // Update the retrieved CacheItem according to requirement
    // Create a new CacheItem with updated value
    // You also need to specify the FQN(Fully Qualified Name) of the class
    var updatedItem = new ncache.CacheItem(prod,"FQN.Product");

    // Set the item version. This version will be used to compare the item version of cache item
    updatedItem.setCacheItemVersion(version);

    await this.cache.insert(key, updatedItem);

    // If it matches, the insert will be successful, otherwise it will fail
}
// Using NCache Enterprise 4.9.1
// Precondition: Cache is already connected
// The item version is saved when item was added in cache
// Specify the key of the CacheItem
string key = "Product:1001";

// Initialize the CacheItemVersion
CacheItemVersion version = null;

// Retrieve the cache item with version
CacheItem cacheItem = cache.GetCacheItem(key, ref version);

if (cacheItem != null)
{
    // Cast the object to Product
    Product prod = (Product)cacheItem.Value;

    // Modify the product
    prod.UnitsInStock++;

    // Create updated cache item and assign the version
    CacheItem updatedItem = new CacheItem(prod);
    updatedItem.Version = version;

    // Attempt to update (will only succeed if version matches)
    cache.Insert(key, updatedItem);

    // If it matches, the insert will be successful, otherwise it will fail
}
else
{
    // Item could not be retrieved due to outdated CacheItemVersion
}
Note

To ensure the operation is fail-safe, it is recommended to handle any potential exceptions within your application, as explained in Handling Failures.

How to Retrieve New Cache Item Version if Present

The GetIfNewer method is used to fetch an item if a newer version is available in the cache. By specifying the current version as an argument in the method call, the cache returns the appropriate result. If the specified version is older than the one in the cache, only then does the method return a new item, else null will be returned.

The following example adds an item to the cache with the key Product:1001 and its item version. It then retrieves the item using GetIfNewer method, which gets the cache item only if a newer version exists in the cache.

  • .NET
  • Java
  • Python
  • Node.js
  • Legacy API
// Precondition: Cache is already connected
// Get updated product from database against given product ID
Product product = FetchProductByProductID(1001);

// Generate a unique key for this item
string key = $"Product:{product.ProductID}";

// Create a new CacheItem
var item = new CacheItem(product);

// Add CacheItem to cache with new item version
CacheItemVersion version = cache.Insert(key, item);

// Get object from cache
var result = cache.GetIfNewer<Product>(key, ref version);

// Check if updated item is available
if (result != null)
{
    // An item with newer version is available
    if (result is Product)
    {
        // Perform operations according to business logic
    }
}
else
{
    // No new item version is available
}
// Precondition: Cache is already connected
// Get updated product from database against given product ID
Product product = fetchProductByProductID(1001);

// Generate a unique key for this item
String key = "Product:" + product.getProductID();

// Create a new CacheItem
CacheItem item = new CacheItem(product);

// Add CacheItem to cache with new item version
CacheItemVersion version = cache.insert(key, item);

// Get object from cache
Object result = cache.getIfNewer(key,version,Object.class);

// Check if updated item is available
if (result != null)
{
        // An item with newer version is available
    if (result == Product)
    {
        // Perform operations according to business logic
    }
}
else
{
    // No new item version is available
}
# Precondition: Cache is already connected
# Get updated product from database against given product ID
product = fetch_product_from_db(1001)

# Generate a unique key for this item
key = "Product:" + product.get_product_id()

# Create a new CacheItem
item = ncache.CacheItem(product)

# Add CacheItem to cache with new item version
version = cache.insert(key, item)

# Get object from cache
result = cache.get_if_newer(key, cacheitemversion=version, objtype=Product)

# Check if updated item is available
if result is not None:
    # Perform operations according to business logic
    print("New version is available")
else:
    # No new item version is available
    print("New version is not available")
// Precondition: Cache is already connected
// This is an async method
// Get updated product from database against given product ID
var product = this.fetchProductByProductID(1001);

// Generate a unique key for this item
var key = "Product:" + product.getProductID();

// Create a new CacheItem
// You also need to specifyt the FQN(Fully Qualified Name) of the class
var item = new ncache.CacheItem(product,"FQN.Product");

// Add CacheItem to cache with new item version
var version = await this.cache.insert(key, item);

// Get object from cache
var result = await this.cache.getIfNewer(key, ncache.JsonDataType.Object, version);

// Check if updated item is available
if (result != null)
{
    // Perform operations according to business logic
}
else
{
    // No new item version is available
}
// Using NCache Enterprise 4.9.1
// Precondition: Cache is already connected

// Get updated product from database against given product ID
Product product = FetchProductByProductID(1001);

// Generate a unique key for this item
string key = $"Product:{product.ProductID}";

// Insert using CacheItem
CacheItem item = new CacheItem(product);
cache.Insert(key, item);

// Retrieve the item to get version
CacheItem cachedItem = cache.GetCacheItem(key);
CacheItemVersion itemVersion = cachedItem.Version;
object result = cache.GetIfNewer(key, ref itemVersion);

// Check if updated item is available
if (result != null)
{
    // An item with newer version is available
    if (result is Product)
    {
        // Perform operations according to business logic
    }
}
else
{
    // No new item version is available
}

How to Remove Item with Item Version

An item can be removed from the cache using an overload of Remove method that accepts the item version. If the specified version is different from the one stored in the cache, an exception is thrown.

The following example shows how to remove an item from the cache by specifying the item version using the Remove method.

Tip

You can monitor/verify item removal:

  • "Cache Count" Counter in NCache Monitor or PerfMon Counters.
  • Using cache.Contains after the expiration interval has elapsed.
  • Using cache.Count before and after specifying expiration.
  • .NET
  • Java
  • Python
  • Node.js
  • Legacy API
// Precondition: Cache is already connected
// Get updated product from database against given product ID
Product product = FetchProductByProductID(1001);

// Cache key remains the same for this product
string key = $"Product:{product.ProductID}";

// Create a new CacheItem
var item = new CacheItem(product);

// Insert CacheItem to cache with new item version
CacheItemVersion version = cache.Insert(key, item);

// Remove the item from the cache using the item version
cache.Remove(key, null, version);
// Precondition: Cache is already connected
// Get updated product from database against given product ID
Product product = fetchProductByProductID(1001);

// Cache key remains the same for this product
String key = "Product:" + product.getProductID();

// Create a new CacheItem
CacheItem item = new CacheItem(product);

// Add CacheItem to cache with new item version
CacheItemVersion version = cache.insert(key, item);

// Remove the item from the cache using the item version
// lockHandle and writeThruOptions set to null
cache.remove(key, null, version, null, Object.class);
# Precondition: Cache is already connected
# Get updated product from database against given product ID
product = fetch_product_from_db(1001)

# Cache key remains the same for this product
key = "Product:" + product.get_product_id()

# Create a new CacheItem
item = ncache.CacheItem(product)

# Add CacheItem to cache with new item version
version = cache.insert(key, item)

# Remove the item from the cache using the item version
cache.remove(key, version=version, objtype=Product)

value = cache.get(key, Product)

if value is None:
    # Item has been removed from cache
    print("Remove successful")
else:
    # Item is still in cache
    print("Remove failed")
// Precondition: Cache is already connected
// Get updated product from database against given product ID
var product = this.fetchProductByProductID(1001);

// Cache key remains the same for this product
var key = "Product:" + product.getProductID();

// Create a new CacheItem
// You also need to specify the FQN(Fully Qualified Name) of the class
var item = new ncache.CacheItem(product,"FQN.Product");

// Add CacheItem to cache with new item version
var version = await this.cache.insert(key, item);

// Remove the item from the cache using the item version
// lockHandle and writeThruOptions set to null
await this.cache.remove(key, ncache.JsonDataType.Object, null, version, null);

var value = await this.cache.get(key, ncache.JsonDataType.Object);

if (value == null)
{
    // Item has been removed from cache
}
else
{
    // Item is still  in cache
}
// Using NCache Enterprise 4.9.1
// Precondition: Cache is already connected
// Get updated product from database against given product ID
Product product = FetchProductFromDB("1001");

// Cache key remains the same for this product
string key = $"Product:{product.ProductID}";

// Create a new CacheItem
var item = new CacheItem(product);

// Insert CacheItem to the cache
cache.Insert(key, item);

// Remove the item from the cache
cache.Remove(key);

Topology Wise Behavior

The behavior of item versioning varies slightly depending on your NCache topology. This ensures consistency across the cluster.

  • For the Mirror and the Replicated Cache

In the Mirror Topology, when an item is added or updated, its version is generated at the active node. The same version is then replicated to the passive node along with the item, ensuring that when an active node becomes passive, the item version remains the same.

In the Replicated Topology, the client is connected to one node and the item version is generated at a node that receives client update/add operation. This same version, along with the item replicates to all other nodes for data consistency.

  • For the Partitioned and the Partition-Replica Cache

In the Partitioned Topology, the item version is generated and exists on the same node that contains the item. During state transfer, version is also transferred along with the item in case the item moves to another node.

In the Partition-Replica Topology, the version is generated on the active node that contains the item. The same version, along with the item, is then replicated to its replica for data consistency. During state transfer, version is also transferred along with the item in case item moves to another node.

  • Client Cache

In the Client Cache Topology, all version-related information is maintained at the clustered cache. Whenever a version-related API is called, the client gets its version from the clustered cache.

Additional Resources

NCache provides a sample application for Item Locking on GitHub.

See Also

.NET: Alachisoft.NCache.Runtime.Caching namespace.
Java: com.alachisoft.ncache.runtime.caching namespace.
Python: ncache.runtime.caching class.
Node.js: Cache class.

Contact Us

PHONE

+1 214-619-2601   (US)

+44 20 7993 8327   (UK)

 
EMAIL

sales@alachisoft.com

support@alachisoft.com

NCache
  • Edition Comparison
  • NCache Architecture
  • Benchmarks
Download
Pricing
Try Playground

Deployments
  • Cloud (SaaS & Software)
  • On-Premises
  • Kubernetes
  • Docker
Technical Use Cases
  • ASP.NET Sessions
  • ASP.NET Core Sessions
  • Pub/Sub Messaging
  • Real-Time ASP.NET SignalR
  • Internet of Things (IoT)
  • NoSQL Database
  • Stream Processing
  • Microservices
Resources
  • Magazine Articles
  • Third-Party Articles
  • Articles
  • Videos
  • Whitepapers
  • Shows
  • Talks
  • Blogs
  • Docs
Customer Case Studies
  • Testimonials
  • Customers
Support
  • Schedule a Demo
  • Forum (Google Groups)
  • Tips
Company
  • Leadership
  • Partners
  • News
  • Events
  • Careers
Contact Us

  • EnglishChinese (Simplified)FrenchGermanItalianJapaneseKoreanPortugueseSpanish

  • Contact Us
  •  
  • Sitemap
  •  
  • Terms of Use
  •  
  • Privacy Policy
© Copyright Alachisoft 2002 - . All rights reserved. NCache is a registered trademark of Diyatech Corp.
Back to top