한양과학기술고등학교

에듀테크 프로젝트 대시보드

분산 락 (Distributed Lock)

여러 대의 서버(프로세스)가 동시에 같은 자원을 건드리면 안 될 때, 한 대만 작업할 수 있도록 "표"를 나눠주는 장치가 분산 락입니다. 한 컴퓨터 안의 뮤텍스(mutex)·세마포어와 달리, 락 자체가 네트워크 너머의 별도 저장소(Redis 등)에 있다는 점이 다릅니다.

🏁 데모 1 — 두 서버가 동시에 락을 요청하면?

Redis 명령 SET lock:order-42 <내ID> NX EX 5 를 흉내낸 시뮬레이션입니다. NX는 "키가 없을 때만 저장"(원자적 연산), EX 5는 5초 뒤 자동 만료(TTL)를 의미합니다.

현재 락 소유자 (비어 있음) 남은 TTL -
서버 A
대기 중
서버 B
대기 중
// 서버 A 또는 B로 락을 요청해 보세요
왜 TTL이 필수인가? 락을 쥔 서버가 갑자기 죽어버리면(크래시), 그 락은 영원히 안 풀립니다. TTL을 걸어두면 일정 시간 뒤 자동으로 락이 풀려 다른 서버가 이어받을 수 있습니다 — 대신 "작업이 TTL보다 오래 걸리면 락이 먼저 풀려버리는" 새로운 위험이 생깁니다 (아래 펜싱 토큰 참고).

⚠️ TTL 만료 후 두 서버가 동시에 쓰면?

서버 A가 락을 쥔 채 GC 정지(pause)로 오래 멈췄다가 TTL이 끊기고, 서버 B가 새로 락을 획득한 뒤 A가 뒤늦게 깨어나 저장소에 쓰기를 시도하는 상황입니다.

// 버튼을 눌러 시나리오를 재생하세요

🔢 펜싱 토큰 (Fencing Token) 원리

락을 줄 때마다 단조 증가하는 번호표(토큰)를 함께 발급합니다. 저장소(진짜 자원)는 "지금까지 본 토큰보다 작은 번호는 무조건 거부"하면, 뒤늦게 도착한 옛날 락 소유자의 쓰기를 막을 수 있습니다.

token = INCR("lock:order-42:fencing") # A는 token=33 발급받고 멈춤(pause) # TTL 만료 → B가 새로 획득, token=34 # B가 storage.write(token=34) 먼저 도착 → 저장소: last_token=34 # 뒤늦게 깨어난 A가 storage.write(token=33) 시도 if token < storage.last_token: raise RejectedError("오래된 토큰") # ✅ 차단!

🗳️ 데모 2 — Redlock: 여러 대의 Redis로 안전하게

Redis 한 대만 쓰면 그 한 대가 죽었을 때 락 정보 전체가 사라집니다. Redlock은 서로 독립된 Redis N대(보통 5대)에 같은 락을 동시에 요청하고, 과반수(N/2+1)에서 성공해야 락 전체를 획득한 것으로 인정합니다.

장애 노드 수 0 / 5
과반수 원칙: 5대 중 2대까지 죽어도(N/2 미만) 나머지 3대로 과반수를 채울 수 있어 락 시스템이 계속 동작합니다. 이는 Raft·Paxos 같은 합의 알고리즘의 쿼럼 개념과 같은 아이디어입니다.

📌 핵심 요약

SETNX + TTL — 원자적 "없을 때만 쓰기"로 락 획득, TTL로 좀비 락 방지

펜싱 토큰 — 단조 증가 번호로 "오래된 락 소유자"의 뒤늦은 쓰기를 차단

Redlock — 독립된 N개 저장소 + 과반수 합의로 단일 장애점 제거

split-brain — 네트워크 단절로 두 서버가 동시에 "내가 주인"이라 착각하는 상태. 분산 락이 막으려는 근본 문제

🆚 비교해서 이해하기

한 컴퓨터 안의 락 → mutex / semaphore (커널이 보장, 죽으면 OS가 자동 해제)
여러 서버 간의 락 → 이 페이지의 분산 락 (네트워크 지연·장애까지 고려해야 함)
리더 선출 합의 → Raft / Paxos (락보다 더 강력한 전체 순서 보장, 더 무거움)
실무 활용 → 재고 차감 중복 방지, 크론잡 중복 실행 방지, 좌석/티켓 동시 예매 방지