Redis Distributed Lock Acquisition
Serialize critical sections across instances with a Redis lock using SET NX with expiry, token-checked release, and lease renewal.
Code
import { createClient } from 'redis';
const redis = createClient({ url: 'redis://127.0.0.1:6379' });
await redis.connect();
function randomToken() {
return crypto.randomUUID();
}
async function acquireLock(key: string, ttlMs: number) {
const token = randomToken();
const ok = await redis.set('lock:' + key, token, { NX: true, PX: ttlMs });
return ok ? token : null;
}
async function releaseLock(key: string, token: string) {
const script = `
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return 0
end
`;
return (await redis.eval(script, {
keys: ['lock:' + key],
arguments: [token],
})) === 1;
}
async function withLock<T>(
key: string, ttlMs: number, fn: () => Promise<T>,
): Promise<T | null> {
const token = await acquireLock(key, ttlMs);
if (token === null) return null;
const renew = setInterval(() => {
redis.expire('lock:' + key, ttlMs / 1000).catch(() => {});
}, ttlMs / 3);
try {
return await fn();
} finally {
clearInterval(renew);
await releaseLock(key, token);
}
}
const result = await withLock('billing:nightly', 30_000, async () => {
console.log('nightly billing started under lock');
return { invoices: 42 };
});
console.log('lock result:', result?.invoices ?? 'busy');Report a problem with this exampleOpens a pre-filled issue on GitHub.