テック技報最新号へ →
コード

Redis: 分散ロックはランダムなトークンで取り、Lua で「自分のトークンなら消す」で解放する

SET lock:x 1 NX PX 30000 で取って DEL lock:x で解放すると、処理が TTL より長引いたとき、期限切れ後に別のワーカーが取り直したロックを消してしまう。値に自分だけのトークンを入れ、照合と削除を Lua スクリプトでまとめて行う。

@amitsinha__07 · 投稿 2026-10-25
#redis#infra#lock#concurrency

壊れる流れ

  1. A がロックを取る(TTL 30 秒)
  2. A の処理が GC や遅い I/O で 35 秒かかる → 30 秒でロックが期限切れ
  3. B がロックを取る
  4. A が終わって DEL → B のロックを消す
  5. 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 の行ロックや一意制約で守るほうが確実。

出典

X の元の投稿を開く