분산 시스템 · 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대가 동의하면 락을 얻어요

독립 마스터
5
복제 없이 서로 독립
쿼럼 (과반)
N/2+1
5대면 3대
자동 만료 TTL
10~30
데드락 방지
쿼럼(quorum): 결정을 성립시키는 최소 과반 정족수(N/2+1) · TTL(Time To Live): 지나면 자동 삭제되는 시간
왜 과반일까요

과반이면 두 명이 동시에 못 가져요

3 + 3 = 6 > 5

두 클라이언트가 각각 3대(과반)를 가지려면 최소 6대가 필요해요. 5대뿐이라 최소 한 대는 겹치고, SET NX가 그 노드에서 한쪽을 막아요 → 동시 점유 불가.

획득 절차

모든 노드에 순서대로 걸고, 과반과 시간을 확인해요

1
현재 시각 T1을 밀리초로 기록해요.
2
N대 모두에 같은 키·같은 랜덤값으로 SET NX PX순차 시도해요. 노드별 타임아웃은 TTL보다 훨씬 짧게(5~50ms) 둬 죽은 노드에서 막히지 않게 해요.
3
다시 시각 T2를 재서 경과시간 = T2 − T1을 구해요.
4
과반(3대) 획득 그리고 경과시간 < TTL, 두 조건을 모두 만족할 때만 락 성공이에요.
5
실패(과반 미달 또는 경과 ≥ TTL)하면 모든 노드에서 즉시 해제하고, 랜덤 지연 뒤 재시도해요.

노드는 클러스터가 아니라 서로 독립이에요. 순차로 걸고, 거는 데 든 시간은 유효시간에서 차감해요.

가장 놓치기 쉬운 지점

락을 쥐는 시간은 TTL보다 짧아요

유효시간 = TTL 획득에 걸린 시간 시계 오차

클라이언트는 이 짧아진 창 동안만 락을 신뢰해야 해요. TTL 전부를 믿으면 안 돼요.

보장하는 것

이 세 가지 속성을 지키려고 해요

🔒
안전성 (상호 배제)
어느 순간에도 락을 쥔 클라이언트는 최대 한 명이에요.
♻️
데드락 없음
소유자가 죽어도 TTL이 지나면 락이 저절로 풀려요.
🛡️
장애 내성
과반 노드만 살아 있으면 락을 걸고 풀 수 있어요.
상호 배제(mutual exclusion): 공유 자원을 한 번에 하나만 쓰게 하는 것 · 장애 내성(fault tolerance): 일부가 고장 나도 전체가 계속 동작하는 성질
파라미터

각 값은 이런 역할을 해요

파라미터역할
노드 수 N5과반으로 장애를 견딤 (복제 없는 독립 마스터)
쿼럼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): 중간에 끼어들 수 없이 한 번에 실행되는 것
숨은 위험

노드가 재시작하면 과반이 조용히 깨져요

1
A·B·C 세 노드에 락이 걸려 과반(3/5)을 확보했어요.
2
C가 재부팅하면 저장이 날아가 자기가 건 락을 잊어버려요.
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가 락을 얻고 작업을 시작해요.
2
A에서 긴 GC STW 일시정지가 발생해요 (수 초~수십 초).
3
멈춘 사이 TTL이 지나 락이 자동 만료돼요.
4
클라이언트 B가 같은 락을 정상적으로 획득해요.
5
A가 깨어나 락이 사라진 줄 모르고 리소스에 써버려요 💥
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씩 커지는 번호예요
  • 쓰기마다 토큰을 함께 보내요
  • 리소스가 낮은 토큰을 거부해 오래된 쓰기를 막아요
펜싱 토큰 동작

늦게 도착한 옛 쓰기를 토큰이 막아줘요

1
A가 락을 얻으며 토큰 33을 함께 받아요.
2
A가 멈춘 사이 B가 락을 얻어 토큰 34를 받아요.
3
B가 토큰 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 · 분산락

1 / 23
← → · Space · F 전체화면