In this lesson, you will learn how to use distributed locks to ensure that only one Node.js instance performs a heavy cache priming operation, preventing "cache stampedes."
The Problem: Thundering Herds
When a frequently requested cache key expires, multiple incoming requests may simultaneously detect the "cache miss." If your application triggers a background task to recompute and prime the cache every time this happens, your database will be hammered by redundant, identical heavy queries. In a distributed environment, you need a way to coordinate these instances so only the first one to arrive performs the work while the others wait or serve stale data.
Distributed Locking for Synchronization
A distributed mutex (Mutual Exclusion) allows you to gate-keep the cache priming logic. By using an atomic operation in a store like Redis (such as SET key value NX PX 5000), you can guarantee that only one instance acquires the "lock" for a specific resource.
Here is how you implement this pattern in Node.js:
async function getOrPrimeCache(key) {
let data = await cache.get(key);
if (!data) {
// Attempt to acquire a lock for this specific key
const lockAcquired = await redis.set(`lock:${key}`, 'locked', 'NX', 'PX', 10000);
if (lockAcquired) {
// I am the chosen one: perform the expensive operation
data = await db.fetchExpensiveData(key);
await cache.set(key, data, 'EX', 3600);
await redis.del(`lock:${key}`);
} else {
// Wait a moment and retry, or return a stale value/fallback
await sleep(500);
return getOrPrimeCache(key);
}
}
return data;
}
Why it works
The NX (Not Exists) flag ensures the lock is only created if it doesn't already exist. The PX (Expiration) ensures that if the process crashes mid-task, the lock will automatically expire rather than deadlocking your entire system. By holding the other requests at the gate, you protect your downstream services from redundant traffic and ensure your cache is primed efficiently by exactly one instance.
Key Takeaway: Use atomic distributed locks to serialize cache priming tasks, ensuring only one instance incurs the cost of a cache miss while others wait for the result.