--- id: db-distributed-locks title: Distributed Locks — Redis / DB / ZooKeeper category: Coding status: draft source_trust_level: B verification_status: conceptual created_at: 2026-05-09 updated_at: 2026-05-09 tags: [database, redis, lock, distributed, vibe-coding] tech_stack: { language: "TS / Redis / Postgres", applicable_to: ["Backend"] } applied_in: [] aliases: [distributed lock, Redlock, advisory lock, leader election, mutex] --- # Distributed Locks > N대 서버가 동시 처리 안 되도록 1개만 작업. **TTL + lease + idempotency**. Redis Redlock / Postgres advisory lock / ZooKeeper. 짧은 critical section + 멱등이 안전. ## 📖 핵심 개념 - TTL: 락 시간 제한 — crash 시 영원 X. - Lease: 보유자가 주기적 갱신. - Fencing token: 락 받을 때마다 증가하는 ID — old holder 차단. - Optimistic lock: 락 안 잡고 version check. ## 💻 코드 패턴 ### Redis SETNX (단순) ```ts async function acquire(key: string, ttlMs: number): Promise { const token = crypto.randomUUID(); const ok = await redis.set(`lock:${key}`, token, 'PX', ttlMs, 'NX'); return ok ? token : null; } // 안전 release (자기 거 만 풀기) const RELEASE_LUA = ` if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end `; async function release(key: string, token: string) { await redis.eval(RELEASE_LUA, 1, `lock:${key}`, token); } ``` ### Redlock (multi-node Redis) ```ts import Redlock from 'redlock'; const redlock = new Redlock([redisA, redisB, redisC], { retryCount: 5, retryDelay: 200, driftFactor: 0.01, }); const lock = await redlock.acquire(['locks:report'], 30_000); try { await runReport(); } finally { await lock.release(); } ``` ### Postgres advisory lock (transaction-scoped) ```sql -- 자동 release on tx end SELECT pg_advisory_xact_lock(hashtext('process-order:42')); -- 작업 COMMIT; ``` ```ts await db.transaction(async (tx) => { await tx.queryRaw`SELECT pg_advisory_xact_lock(hashtext(${`process-order:${id}`}))`; await processOrder(tx, id); }); ``` ### Postgres advisory lock (session-scoped) ```sql SELECT pg_try_advisory_lock(42); -- bigint key -- 작업 SELECT pg_advisory_unlock(42); ``` ### Lease + auto-renew ```ts class Lease { private timer?: NodeJS.Timeout; constructor(private key: string, private token: string, private ttlMs: number) { this.start(); } private start() { this.timer = setInterval(async () => { await redis.eval(EXTEND_LUA, 1, `lock:${this.key}`, this.token, this.ttlMs); }, this.ttlMs * 0.5); } async release() { clearInterval(this.timer); await release(this.key, this.token); } } ``` EXTEND_LUA: token 일치할 때만 PEXPIRE. ### Fencing token ```ts async function acquireWithToken(key: string, ttlMs: number) { const fenceLua = ` local cur = redis.call("get", KEYS[1]) if not cur or redis.call("ttl", KEYS[1]) < 0 then local fence = redis.call("incr", KEYS[2]) redis.call("set", KEYS[1], ARGV[1], "PX", ARGV[2]) return fence end return 0 `; const fence = await redis.eval(fenceLua, 2, `lock:${key}`, `fence:${key}`, token, ttlMs); return fence > 0 ? { token, fence } : null; } // 외부 시스템에 fence 같이 보내 — 더 큰 fence 만 accept ``` ### Optimistic — 락 없이 ```sql UPDATE orders SET status = 'shipped', version = version + 1 WHERE id = $1 AND version = $expectedVersion; -- affected rows = 0 → 다른 process 가 변경 → 재시도 ``` ### Election (단일 leader) ```ts async function tryBecomeLeader(): Promise { const ok = await redis.set('leader', hostname(), 'EX', 30, 'NX'); if (ok) { setInterval(() => redis.expire('leader', 30), 10_000); // refresh return true; } return false; } ``` ## 🤔 의사결정 기준 | 상황 | 추천 | |---|---| | 단일 DB 내 | Postgres advisory lock | | Redis 있음 + 단일 노드 | Redis SETNX | | Multi-Redis HA | Redlock | | 리더 선출 | etcd / ZooKeeper / Consul / Redis lease | | Idempotent 가능 | Optimistic version check | | Cron leader 1개만 | DB row lock 또는 Redis lease | ## ❌ 안티패턴 - **TTL 없음**: holder crash 시 영원 락. - **TTL < 작업 시간**: 다른 process 가 동시에 — race. - **Token 없는 release**: 다른 holder 의 락 풀어줌. - **Redlock 단일 Redis 가정**: HA 없으면 의미 없음. - **Lock + 외부 부수효과 + 락 만료**: fence 없으면 두 번 실행. - **Lock 잡고 길게 (DB query 포함)**: 다른 work 차단. - **Distributed lock 으로 정확성 보장**: 성능 / 실수 방지지, 정확성은 idempotency. ## 🤖 LLM 활용 힌트 - TTL + token + Lua 안전 release. - Postgres advisory = 가장 단순 + 안전 (단일 DB). - 정확성 = 락 + idempotency 양쪽. ## 🔗 관련 문서 - [[Backend_Cron_Patterns]] - [[Backend_Idempotency_Keys]] - [[Optimistic_Concurrency_Control]]