현재 테스트 중입니다: 완료되면 GitHub 코드를 오픈할 예정입니다.

동작 원리

이 페이지는 클라이언트와 서버(클러스터)가 실제로 주고받는 흐름을 따라 Ticketing의 원리를 설명합니다. 와이어에 오가는 바이트의 정확한 규격은 프로토콜 (API)프로토콜 (클러스터)에 있습니다.


전체 구조

사용자 요청을 받는 서비스(예: 티켓 오픈을 처리하는 이벤트 서버들)가 같은 키로 Ticketing 클러스터에 줄을 서고, 차례를 받은 서버만 재고 차감·좌석 배정 같은 작업을 진행하는 구조입니다.

groups
사용자
서비스 (예: 이벤트 서버)
dns 이벤트 서버 1
dns 이벤트 서버 2
dns 이벤트 서버 3
Ticketing 클러스터
confirmation_number ticketing-server 1
confirmation_number ticketing-server 2
confirmation_number ticketing-server 3

락의 획득과 반납

기본 흐름입니다. 비어 있는 키는 즉시 발급되고, 점유 중인 키는 FIFO 대기열에서 차례를 기다립니다. 보유자가 반납하면 대기열 맨 앞에게 재경쟁 없이 직접 인계됩니다 — 새 토큰과 함께 즉시 넘어가므로 락이 비어 있는 순간도, 새치기도 없습니다.

클라이언트 X
클라이언트 Y
서버
A · 획득 (order-1234)
A · 발급 (token 41)
A · 획득 (order-1234)
점유 중 → FIFO 대기열 등록, 응답 보류
R · 반납
R · 반납됨
A · 직접 인계 (token 42)
요청응답
  • lease는 안전망입니다. 클라이언트가 죽거나 반납을 깜빡해도 lease 시간이 지나면 서버가 키를 회수해 다음 대기자에게 넘깁니다. 임계 구역의 최악 소요 시간보다 넉넉하게 잡으세요.
  • wait는 대기 상한입니다. 시간 안에 차례가 오지 않으면 락 발급 없이 T(타임아웃)로 응답하므로 새는 락이 없습니다. 0이면 queue에 들어가지 않고 한 번만 즉시 시도합니다. 무한 대기는 없습니다.
  • 같은 클라이언트가 보유 중인 키를 다시 획득하려 하면 다른 대기자와 똑같이 줄을 섭니다 — 재진입(reentrant) 락이 아닙니다.

키 상태별 서버 동작의 전체 규칙은 요청 × 키 상태 매트릭스를, 필드의 값 범위는 필드 제약을 참고하세요.

펜싱 토큰

락 서버가 완벽해도 클라이언트의 시간은 통제할 수 없습니다. 락을 잡은 채 GC 멈춤·과부하로 한참 정지했다가 lease가 만료된 줄 모르고 깨어나 작업을 계속하는 옛 보유자는 락 서버 혼자서는 막을 수 없습니다. 그래서 획득 응답의 token은 새 소유권마다 증가하는 u64 정수입니다. 금융성·persistent DB에서는 key별 high-water 비교·갱신과 실제 business write를 같은 DB transaction 또는 하나의 조건부 write에서 원자적으로 수행해야 이 마지막 구멍을 막을 수 있습니다. fencing row만 먼저 갱신하고 실제 write를 나중에 하는 방식은 안전하지 않습니다.

클라이언트 X
클라이언트 Y
서버
보호 자원
A · 획득
A · 발급 (token 7)
A · 획득 → 대기
GC·과부하로 정지
lease 만료 → 회수
A · 직접 인계 (token 8)
쓰기 (token 8)
token 8 기록 → 허용
뒤늦게 깨어남
쓰기 (token 7)
7 < 8 → 거부
요청응답

클러스터에서도 단조성은 유지됩니다 — 토큰 발급 번호 자체가 합의로 복제되므로, 리더가 바뀌어도 새 리더는 항상 이전보다 큰 번호에서 이어갑니다. 토큰 필드의 정확한 규격은 펜싱 토큰 (스펙)을 참고하세요.

클러스터 — Raft 합의 기반 무정지

클러스터 모드에서는 노드들이 Raft 합의로 하나의 락 상태를 공유합니다. 리더만 클라이언트 요청을 처리하고, 모든 락 상태 변경(획득·반납·만료)은 과반 노드가 받아 적은 뒤에만 확정됩니다. 리더가 아닌 노드에 접속하면 M(이동) 응답으로 리더를 안내받습니다.

클라이언트
팔로워 B
리더 A
팔로워 C
A · 획득
M · 리더 안내
A · 획득
복제 제안
복제 제안
수락
수락
과반 수락 → 커밋
A · 발급 (token)
요청응답합의 RPC (Raft)
  • 리더가 죽으면 현재 기본 설정에서는 약 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를 차단합니다.