Redis: 分散ロックはランダムなトークンで取り、Lua で「自分のトークンなら消す」で解放する
SET lock:x 1 NX PX 30000 で取って DEL lock:x で解放すると、処理が TTL より長引いたとき、期限切れ後に別のワーカーが取り直したロックを消してしまう。値に自分だけのトークンを入れ、照合と削除を Lua スクリプトでまとめて行う。
壊れる流れ
- A がロックを取る(TTL 30 秒)
- A の処理が GC や遅い I/O で 35 秒かかる → 30 秒でロックが期限切れ
- B がロックを取る
- A が終わって
DEL→ B のロックを消す - C が取れてしまい、B と C が同時に動く
書き方
js
import { randomUUID } from "node:crypto";
const token = randomUUID();
const ok = await redis.set("lock:invoice:42", token, { NX: true, PX: 30_000 });
if (!ok) return; // 他が処理中
try {
await doWork();
} finally {
// GET と DEL の間に他人が割り込まないよう、Lua でアトミックに
await redis.eval(
`if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return 0
end`,
{ keys: ["lock:invoice:42"], arguments: [token] },
);
}(クライアントライブラリによって set / eval の引数の形は違う。上は node-redis の例。)
注意点
- これで防げるのは「他人のロックを消す」ことだけ。TTL 切れのあと A と B が同時に動く問題は残る。長い処理は TTL を延長する(延長も同じくトークン照合付きで)か、書き込み先でフェンシングトークン(単調増加の番号)を検査する。
- 正しさが重要な排他(お金の処理など)は、DB の行ロックや一意制約で守るほうが確実。
出典
- @amitsinha__07 · 2026-10-25
- Redis ドキュメント「Distributed Locks with Redis」: redis.io