Distributed Locks with Redis
When several app instances run the same scheduled job, you often want exactly one of them to do the work. A Redis lock is a simple way to coordinate.
Acquire with SET NX PX
import { randomUUID } from 'node:crypto';
async function acquireLock(name, ttlMs) {
const token = randomUUID();
const ok = await redis.set(`lock:${name}`, token, { NX: true, PX: ttlMs });
return ok ? token : null; // null = someone else holds it
}
NX- only set if the key doesn't exist.PX- expire automatically, so a crashed holder can't block everyone forever.- A random token identifies this holder.
Release only your own lock
Never just DEL the key: if your lock expired and someone else acquired it, you'd delete their lock. Check the token and delete atomically with a Lua script:
const RELEASE = `
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end`;
async function releaseLock(name, token) {
await redis.eval(RELEASE, { keys: [`lock:${name}`], arguments: [token] });
}
Putting it together
const token = await acquireLock('nightly-report', 60_000);
if (!token) return; // another instance is running it
try {
await generateNightlyReport();
} finally {
await releaseLock('nightly-report', token);
}
The hard part: expiry
If the work takes longer than the TTL (or the process pauses, for example in garbage collection), the lock expires and a second worker can start. Mitigations:
- Set a TTL comfortably longer than the expected run time, and extend it periodically while working.
- Make the work idempotent, so running it twice is harmless.
- For correctness-critical locks, use fencing tokens: an increasing number checked by the resource being written, or use a database row lock instead.
Single-instance Redis locks are great for efficiency ("avoid duplicate work"). For strict mutual exclusion where a mistake causes data corruption, prefer a system with stronger guarantees, such as SELECT ... FOR UPDATE in your database.
Was this article helpful?
Comments
No comments yet. Have a question or a tip? Be the first to share it.