분산 시스템 · Redis
Redlock — 여러 대의 Redis로 안전하게 락을 거는 법
기술백엔드redis분산락
목차
Redlock을 이 순서로 살펴봐요
1단일 노드 락이 왜 위험한지 짚어봐요
2Redlock이 어떻게 동작하는지 뜯어봐요
3정말 안전한지, 논쟁을 정리해요
01
단일 노드 락은 장애에 무너져요
Redis 하나로 거는 락은 빠르지만, 복제·failover에서 상호 배제가 깨져요.
기본 락
한 대짜리 락은 이렇게 걸어요
SET resource_name <random_value> NX PX 30000
NX = 없을 때만 SET (상호 배제) · PX 30000 = 30초 뒤 자동 만료 (데드락 방지)
해제는 단순 DEL이 아니라 "내 값이 맞을 때만 지우기"여야 해요.
NX(Not eXists): 키가 없을 때만 저장 · PX: 밀리초 단위 자동 만료 · 데드락: 락을 쥔 채 죽어 아무도 못 푸는 상태
약점
복제는 비동기라, 승격 순간 락이 사라져요
A가 마스터에
락 획득
→
마스터 다운
복제 전에 crash
→
복제본 승격
락 키 없음
→
B도 같은 락 획득
동시 점유 💥
→ Redlock은 복제에 기대지 않고, 독립된 여러 노드의 과반 합의로 이 문제를 피해요.
복제(replication): 데이터를 다른 노드로 복사 · failover: 장애 시 대기 노드로 자동 전환
02
독립된 여러 노드의 과반으로 락을 잡아요
서로 복제하지 않는 N대의 마스터에서 다수가 동의해야 락이 성립해요.
핵심 구성
5대 중 3대가 동의하면 락을 얻어요
쿼럼(quorum): 결정을 성립시키는 최소 과반 정족수(N/2+1) · TTL(Time To Live): 지나면 자동 삭제되는 시간
왜 과반일까요
과반이면 두 명이 동시에 못 가져요
두 클라이언트가 각각 3대(과반)를 가지려면 최소 6대가 필요해요. 5대뿐이라 최소 한 대는 겹치고, SET NX가 그 노드에서 한쪽을 막아요 → 동시 점유 불가.
획득 절차
모든 노드에 순서대로 걸고, 과반과 시간을 확인해요
2N대 모두에 같은 키·같은 랜덤값으로 SET NX PX를 순차 시도해요. 노드별 타임아웃은 TTL보다 훨씬 짧게(5~50ms) 둬 죽은 노드에서 막히지 않게 해요.
3다시 시각 T2를 재서 경과시간 = T2 − T1을 구해요.
4과반(3대) 획득 그리고 경과시간 < TTL, 두 조건을 모두 만족할 때만 락 성공이에요.
5실패(과반 미달 또는 경과 ≥ TTL)하면 모든 노드에서 즉시 해제하고, 랜덤 지연 뒤 재시도해요.
노드는 클러스터가 아니라 서로 독립이에요. 순차로 걸고, 거는 데 든 시간은 유효시간에서 차감해요.
가장 놓치기 쉬운 지점
락을 쥐는 시간은 TTL보다 짧아요
클라이언트는 이 짧아진 창 동안만 락을 신뢰해야 해요. TTL 전부를 믿으면 안 돼요.
보장하는 것
이 세 가지 속성을 지키려고 해요
🔒
안전성 (상호 배제)
어느 순간에도 락을 쥔 클라이언트는 최대 한 명이에요.
♻️
데드락 없음
소유자가 죽어도 TTL이 지나면 락이 저절로 풀려요.
🛡️
장애 내성
과반 노드만 살아 있으면 락을 걸고 풀 수 있어요.
상호 배제(mutual exclusion): 공유 자원을 한 번에 하나만 쓰게 하는 것 · 장애 내성(fault tolerance): 일부가 고장 나도 전체가 계속 동작하는 성질
파라미터
각 값은 이런 역할을 해요
| 파라미터 | 값 | 역할 |
| 노드 수 N | 5 | 과반으로 장애를 견딤 (복제 없는 독립 마스터) |
| 쿼럼 | N/2+1 = 3 | 노드 간 상호 배제 보장 |
| 노드별 타임아웃 | 5~50ms | 죽은 노드에서 막히지 않게 (TTL/2 아님 ⚠️) |
| 재시도 지연 | 랜덤 | 경쟁 클라이언트들의 충돌 회피 |
| 랜덤값 | ≥20 bytes | 내 락임을 증명 → 안전한 해제 |
안전한 해제
내 값일 때만 지워야 남의 락을 안 건드려요
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
랜덤값을 비교한 뒤 삭제 (Lua로 원자적 실행) — 이미 만료돼 남이 다시 잡은 락은 못 지워요.
단, 이 랜덤값은 "안전한 해제"용일 뿐 펜싱 토큰이 아니에요 (뒤에서 설명).
Lua: Redis에 내장된 스크립트 언어 · 원자적(atomic): 중간에 끼어들 수 없이 한 번에 실행되는 것
숨은 위험
노드가 재시작하면 과반이 조용히 깨져요
1A·B·C 세 노드에 락이 걸려 과반(3/5)을 확보했어요.
2C가 재부팅하면 저장이 날아가 자기가 건 락을 잊어버려요.
3이제 C가 비어 있어, 다른 클라이언트가 C·D·E로 또 과반을 얻어요 → 동시 점유 💥
4해결: crash한 노드는 최소 1 TTL 뒤에 복귀(지연 재시작)해 그 틈을 닫아요.
지연 재시작(delayed restart): 죽은 노드를 곧바로 넣지 않고 TTL이 지난 뒤 다시 합류시키는 것
흔한 오해
이 네 가지를 자주 틀려요
❌ 노드별 타임아웃 = TTL/2
아니에요. TTL 대비 아주 짧은 5~50ms예요.
❌ 5대가 한 클러스터
아니에요. 복제 없이 완전히 독립된 마스터예요.
❌ 노드에 동시에 요청
순차로 걸고, 걸린 시간을 유효시간에서 차감해요.
❌ crash 노드 즉시 재투입
최소 1 TTL 동안 지연 재시작해야 안전이 유지돼요.
03
"정말 안전한가"를 두고 논쟁이 있었어요
Kleppmann의 비판과 antirez(저자)의 반론을 균형 있게 봐요.
비판의 핵심 시나리오
긴 일시정지 한 번이면 락이 무너져요
1클라이언트 A가 락을 얻고 작업을 시작해요.
2A에서 긴 GC STW 일시정지가 발생해요 (수 초~수십 초).
3멈춘 사이 TTL이 지나 락이 자동 만료돼요.
4클라이언트 B가 같은 락을 정상적으로 획득해요.
5A가 깨어나 락이 사라진 줄 모르고 리소스에 써버려요 💥
GC(Garbage Collection): 안 쓰는 메모리 자동 회수 · STW(Stop-The-World): 그 사이 프로그램 실행이 잠깐 멈추는 현상. 네트워크 지연·시계 점프도 같은 결과를 낳아요.
Kleppmann ↔ antirez
쟁점마다 시각이 이렇게 갈려요
| 쟁점 | Kleppmann (비판) | antirez (반론) |
| 시계 의존 | NTP 점프로 안전이 깨짐 | 절대시각 아닌 상대 속도만 필요, monotonic clock 쓰면 됨 |
| GC·정지 | TTL 넘는 STW면 두 명이 락 점유 | 모든 락 시스템 공통 문제, Redlock만의 결함 아님 |
| 펜싱 토큰 | 정확성엔 필수인데 Redlock은 못 만듦 | 토큰 순서 ≠ 접근 순서, 없이도 실무엔 충분 |
| 시스템 모델 | 비동기 모델에서 안전하지 않음 → ZK 써라 | 이론적 합의가 아닌 실용적 준동기 설계 |
NTP(Network Time Protocol): 서버 시각 동기화 프로토콜 · monotonic clock: 되돌아가지 않고 일정하게 증가하는 시계 · ZK: ZooKeeper, 합의 기반 코디네이션 시스템
논쟁의 핵심
펜싱 토큰이 있으면 타이밍 가정이 필요 없어요
Redlock의 랜덤값
- 내 락임을 증명해 안전하게 해제해요
- 하지만 단조 증가하지 않아요
- 멈췄던 옛 소유자의 늦은 쓰기를 못 막아요
펜싱 토큰
- 락을 줄 때마다 1씩 커지는 번호예요
- 쓰기마다 토큰을 함께 보내요
- 리소스가 낮은 토큰을 거부해 오래된 쓰기를 막아요
펜싱 토큰 동작
늦게 도착한 옛 쓰기를 토큰이 막아줘요
1A가 락을 얻으며 토큰 33을 함께 받아요.
2A가 멈춘 사이 B가 락을 얻어 토큰 34를 받아요.
3B가 토큰 34로 스토리지에 써요. 스토리지는 "지금까지 최대 34"를 기억해요.
4뒤늦게 깨어난 A가 토큰 33으로 쓰려 하면, 33 ≤ 34라 거부돼요 ✅
토큰이 단조 증가(항상 커지기만 함)하기 때문에, 타이밍 가정 없이도 오래된 쓰기를 걸러낼 수 있어요.
선택 가이드
효율성 락엔 쓰고, 정확성엔 토큰을 더해요
Redlock으로 충분
- 중복 작업 방지 같은 효율성 목적
- 드물게 이중 획득돼도 괜찮은 경우
- 합의 시스템 없이 높은 처리량이 필요할 때
다른 선택이 필요
- 정확성·데이터 무결성이 걸린 경우
- → 리소스에서 검증하는 펜싱 토큰 추가
- → ZooKeeper·etcd·DB 락 (합의 기반)
대안 비교
정확성이 필요하면 이런 선택지가 있어요
| 선택지 | 방식 | 펜싱 토큰 | 적합한 경우 |
| 단일 Redis + 펜싱 | SET NX PX + 토큰 | 직접 구현 | 효율성 락, 단순함이 우선일 때 |
| ZooKeeper·etcd | 합의(ZAB·Raft) | 증가 순번으로 제공 | 정확성이 필요하고 타이밍 가정을 피하고 싶을 때 |
| DB 락 | SELECT … FOR UPDATE | 버전 컬럼 | 이미 DB 트랜잭션을 쓰고 있을 때 |
합의(consensus): 여러 노드가 하나의 값에 안전하게 동의하는 것 · Raft·ZAB: 대표적인 합의 프로토콜
Redlock은 효율성 락엔 좋지만,
정확성엔 펜싱 토큰을 더하세요
기술 · 백엔드 · redis · 분산락