동작 원리
이 페이지는 클라이언트와 서버(클러스터)가 실제로 주고받는 흐름을 따라 Ticketing의 원리를 설명합니다. 와이어에 오가는 바이트의 정확한 규격은 프로토콜 (API)와 프로토콜 (클러스터)에 있습니다.
전체 구조
사용자 요청을 받는 서비스(예: 티켓 오픈을 처리하는 이벤트 서버들)가 같은 키로 Ticketing 클러스터에 줄을 서고, 차례를 받은 서버만 재고 차감·좌석 배정 같은 작업을 진행하는 구조입니다.
락의 획득과 반납
기본 흐름입니다. 비어 있는 키는 즉시 발급되고, 점유 중인 키는 FIFO 대기열에서 차례를 기다립니다. 보유자가 반납하면 대기열 맨 앞에게 재경쟁 없이 직접 인계됩니다 — 새 토큰과 함께 즉시 넘어가므로 락이 비어 있는 순간도, 새치기도 없습니다.
lease는 안전망입니다. 클라이언트가 죽거나 반납을 깜빡해도lease시간이 지나면 서버가 키를 회수해 다음 대기자에게 넘깁니다. 임계 구역의 최악 소요 시간보다 넉넉하게 잡으세요.wait는 대기 상한입니다. 시간 안에 차례가 오지 않으면 락 발급 없이T(타임아웃)로 응답하므로 새는 락이 없습니다.0이면 queue에 들어가지 않고 한 번만 즉시 시도합니다. 무한 대기는 없습니다.- 같은 클라이언트가 보유 중인 키를 다시 획득하려 하면 다른 대기자와 똑같이 줄을 섭니다 — 재진입(reentrant) 락이 아닙니다.
키 상태별 서버 동작의 전체 규칙은 요청 × 키 상태 매트릭스를, 필드의 값 범위는 필드 제약을 참고하세요.
펜싱 토큰
락 서버가 완벽해도 클라이언트의 시간은 통제할 수 없습니다. 락을 잡은 채 GC 멈춤·과부하로 한참 정지했다가 lease가 만료된 줄 모르고 깨어나 작업을 계속하는 옛 보유자는 락 서버 혼자서는 막을 수 없습니다. 그래서 획득 응답의 token은 새 소유권마다 증가하는 u64 정수입니다. 금융성·persistent DB에서는 key별 high-water 비교·갱신과 실제 business write를 같은 DB transaction 또는 하나의 조건부 write에서 원자적으로 수행해야 이 마지막 구멍을 막을 수 있습니다. fencing row만 먼저 갱신하고 실제 write를 나중에 하는 방식은 안전하지 않습니다.
클러스터에서도 단조성은 유지됩니다 — 토큰 발급 번호 자체가 합의로 복제되므로, 리더가 바뀌어도 새 리더는 항상 이전보다 큰 번호에서 이어갑니다. 토큰 필드의 정확한 규격은 펜싱 토큰 (스펙)을 참고하세요.
클러스터 — Raft 합의 기반 무정지
클러스터 모드에서는 노드들이 Raft 합의로 하나의 락 상태를 공유합니다. 리더만 클라이언트 요청을 처리하고, 모든 락 상태 변경(획득·반납·만료)은 과반 노드가 받아 적은 뒤에만 확정됩니다. 리더가 아닌 노드에 접속하면 M(이동) 응답으로 리더를 안내받습니다.
- 리더가 죽으면 현재 기본 설정에서는 약 2.3~2.5초 안에 새 리더를 선출합니다. 아직 바이트를 보내지 않은 요청은 남은 wait 예산 안에서 진행할 수 있지만, 전송 뒤 응답을 확인하지 못한 acquire는
Indeterminate이며 자동 재전송하지 않습니다. lease만료조차 합의를 거칩니다. 리더가 만료 명령을 커밋해야 락이 사라지므로, 노드마다 시계가 조금씩 달라도 락 상태가 갈라지지 않습니다. 노드 간 최대 차Δ는 인증된 clock-health source로 계속 검증하고, 범위를 벗어나면 새 락을 fail-closed합니다.- 과반을 잃으면(3대 중 2대 장애 등) 잘못된 락을 발급하느니 쓰기를 멈추는 쪽(안전 우선) 을 택합니다. process의 휘발 state까지 3대 중 2대(5대 중 3대) 이상 잃었다면 해당 cluster/fencing domain은 복구하지 않으며 같은 domain으로 자동 bootstrap하지 않습니다.
과반이 핵심이므로 노드 수는 3 또는 5만 허용됩니다. 짝수는 비용만 늘고 장애 허용은 그대로라 서버가 부팅을 거부합니다.
| 서버 수 | 무정지 | 동시 장애 허용 | 비고 |
|---|---|---|---|
| 1대 | ✗ | — | token은 process lifetime만 보장. 재시작 가능한 persistent DB 보호에는 미지원 |
| 3대 | ✓ | 1대 | 무정지의 표준 구성. 대부분 이걸로 충분 |
| 5대 | ✓ | 2대 | 한 대 점검 중에도 이중화 유지 |
피어 RPC의 프레임·포트 규칙은 프로토콜 (클러스터)를, 노드를 하나씩 교체하는 절차는 무정지 새 NodeId learner 교체를 참고하세요.
클라이언트의 동작
공식 클라이언트들은 공통으로 다음과 같이 동작합니다(언어별 API는 라이브러리 참고).
- 주소마다 영속 연결을 하나씩 백그라운드에서 유지합니다. 주소를 하나만 주면 같은 노드에 연결을 두 개 유지해, 소켓 하나가 잠깐 끊겨도 서비스가 이어집니다.
- 요청 보낼 연결은 리더 우선, 그다음 라운드로빈으로 고릅니다.
M(이동)으로 안내받은 리더를 기억해 두었다가 이후 요청을 바로 리더로 보냅니다. - 요청은 파이프라이닝됩니다. 응답을 기다리지 않고 다음 요청을 보낼 수 있습니다 — 짝을 맞추는 규칙은 응답 매칭 규칙에 있습니다.
- 끊긴 연결은 지수 백오프(0.1초 → 최대 3.2초)로 재접속을 계속 시도합니다. 연결이 죽었다고 브로커 객체가 망가지지 않습니다 — 알아서 회복됩니다.
- 요청 바이트를 전혀 보내지 못한 경우에만 같은 논리 acquire를 내부 재시도합니다. 상관 가능한
B는 확정 미획득 Busy로 caller에게 즉시 반환해 overload retry 폭주를 피합니다.M은 다음 연결의 leader hint로만 쓰며 owner/key가 없어 이미 송신된 pending을 자동 재전송하는 근거로 쓰지 않습니다. 전송 뒤 응답을 확인하지 못한 acquire는Indeterminate로 알리고 critical section에 들어가지 않습니다. - 반납과 알려진-token 보상은 bounded 전용 queue에서 요청·enqueue 시점부터 절대 5초까지만 재시도합니다. 이 상한을 남은 lease까지 늘리지 않으므로 호출·worker·queue가 오래 묶이지 않습니다. 성공 응답을 못 받은 explicit release는 확정 성공으로 보고하지 않으며, 최종 실패해도 server lease가 안전망으로 만료됩니다.
접속과 보안
모든 연결(클라이언트·피어 모두)은 TCP → (TLS) → challenge-response 인증 순서로 수립됩니다. 서버가 보낸 난수를 토큰과 함께 해시해 응답하므로 토큰이 와이어에 그대로 실리지 않고, 매 접속마다 난수가 달라 재사용(replay)도 안 됩니다. 정확한 규격은 인증 핸드셰이크를 참고하세요.
정합성 정리
- 상호배제는 합의로 보장됩니다. 모든 발급이 과반 커밋을 거치므로, 네트워크 분단이나 리더 교체 중에도 같은 키의 락이 두 곳에서 동시에 발급되지 않습니다.
- persistent DB에서는 펜싱 토큰 검증이 필수입니다.
lease만료 뒤 뒤늦게 깨어난 클라이언트는 락 서버가 아니라 보호 write와 같은 transaction의 high-water 조건만 막을 수 있습니다. 락은 차례를 정리하고, DB fencing이 최종 stale write를 차단합니다.