Course contentsShow
← Node.js
Lesson 1388 of 1,98012. Caching Strategies and Application SpeedFree lesson

Resolving Cache Inconsistency in Race Conditions

You will implement optimistic locking within the cache layer.

In this lesson, you will learn how to prevent stale data updates in distributed systems by implementing optimistic locking directly within your cache layer.

When two processes read the same cache entry simultaneously and both attempt to update it, you face a "lost update" race condition. If process A reads value XX and process B reads the same value XX, they might both calculate X+1X+1 and try to set the value to YY, effectively overwriting each other’s progress.

The Optimistic Locking Approach

Optimistic locking assumes conflicts are rare. Instead of locking the record (which is slow and complex in distributed caches), we attach a version number to the cached data. Before writing an update back to the cache, we verify that the version hasn't changed since we last read it.

In Redis, we achieve this using the WATCH, MULTI, and EXEC command sequence. The WATCH command monitors the key; if another process modifies it before our EXEC, the transaction fails, allowing us to retry.

async function updateProductStock(productId, decrementBy) {
  const key = `product:${productId}`;
  
  // Start tracking the key for changes
  await redis.watch(key);
  
  const currentStock = await redis.get(key);
  const newStock = parseInt(currentStock) - decrementBy;

  // Use MULTI to execute update atomically
  const result = await redis
    .multi()
    .set(key, newStock)
    .exec();

  // result is null if another process changed the key during WATCH
  if (result === null) {
    console.log("Race condition detected, retrying...");
    return updateProductStock(productId, decrementBy); // Recursive retry
  }
}

Why This Works

By using the cache as a gatekeeper, we ensure that the write operation only succeeds if our view of the data remains consistent with the current state of the store. If the WATCH is triggered, it signals that our logic was based on "stale" information, and the EXEC will return null, preventing the corrupted update. This keeps your application logic clean while ensuring high data integrity under concurrent traffic.

Key Takeaway: Optimistic locking with versioning or watch-commit patterns prevents race conditions by ensuring writes only succeed if the underlying data has remained unchanged since it was read.