클러스터 시계 health 계약
Ticketing의 lease는 노드가 교체되거나 snapshot을 설치해도 기존 만료 시각을 늘리지 않아야 합니다. 클러스터 모드는 모든 노드 쌍의 wall-clock 차이가 설정한 Δ를 넘지 않음을 외부 clock agent로 검증합니다. 이 검증은 성능 옵션이 아니며, 클러스터에서는 생략할 수 없습니다.
clock agent는 Ticketing 프로세스와 독립된, 운영자가 신뢰하는 시간 검증 서비스입니다. 단순히 로컬 시계를 에코하는 서비스는 노드 간 offset을 증명하지 못하므로 사용하면 안 됩니다. agent는 신뢰할 수 있는 동기화 source를 확인하고 synced, 불확실성, 마지막 source sample age를 정직하게 보고해야 합니다.
요청·응답
TCP 연결당 한 번의 line을 교환하며 LF를 제외한 상한은 256바이트입니다. 숫자는 선행 0이 없는 정준 10진수만 허용합니다.
# Ticketing → clock agent
TCKCLK 2 <nonce>\n
# clock agent → Ticketing
TCKCLK 2 <nonce> <reference_unix_ms> <uncertainty_ms> <synced> <source_age_ms> <mac>\nnonce: 16바이트 난수의 base64url-no-pad 문자열. 응답은 요청과 정확히 같아야 합니다.reference_unix_ms: 신뢰하는 기준 시각의 Unix millisecond.uncertainty_ms: 기준 시각의 보수적 오차 상한.synced: 동기화 source가 정상이면1, 아니면0.1외에는 노드가 즉시 fail-closed합니다.source_age_ms: agent가 확인한 마지막 유효 시간 sample의 age.mac: 아래 바이트열의 HMAC-SHA-256, base64url-no-pad.
HMAC-SHA-256(secret,
"TCKCLK\0" || version:u16 BE || nonce_raw:16B ||
reference_unix_ms:u64 BE || uncertainty_ms:u64 BE ||
synced:u8 || source_age_ms:u64 BE)CLUSTER_CLOCK_AGENT_SECRET은 32바이트 이상이어야 하며 클러스터·관리·부트스트랩 토큰과 분리해 저장합니다. HMAC은 응답의 출처와 무결성을 검증하지만 기밀성을 제공하지 않으므로 agent 포트도 신뢰할 수 있는 private network로 제한합니다.
검증과 Δ
노드는 왕복 시간, agent 불확실성과 로컬 wall-clock의 기준 시각 차이를 모두 보수적으로 합한 error_bound 상한을 계산합니다. 각 노드의 상한이 Δ/2 이하이면 임의의 두 노드 사이 차이는 최대 Δ입니다. Δ는 2..=60000ms만 허용하며 이 범위를 벗어나면 시작·snapshot 복원을 fail-closed합니다. 이 hard ceiling은 u8 lease의 최대 250초를 무제한으로 늘리는 설정을 막습니다.
성공한 Grant의 server-side 만료에는 client lease에 Δ를 정확히 한 번 더합니다. 이는 시계가 앞선 노드가 조기 Expire를 제안하지 못하게 하는 server-side guard입니다. client가 보는 lease와 conservative_valid_before는 원래 client lease를 기준으로 하므로 사용자의 critical section이 늘어나지는 않습니다. snapshot·replay·learner 교체에서 Δ를 다시 더하지 않습니다.
fail-closed 수명주기
다음 중 하나라도 발생하면 해당 process는 새 Grant와 Expire를 중단하고 client readiness를 내립니다.
- agent 접속·응답 timeout, I/O 실패
- 잘못된 version·nonce·MAC·형식·크기
synced=0, 너무 오래된 source sample, 불확실성·RTT·offset 상한 초과- 성공 sample 이후 staleness 상한 초과
- sample 사이 wall-clock rollback 또는
Δ범위를 넘는 step
현재 프로세스는 불건전 판정 후 자동으로 다시 건전 상태로 돌아가지 않습니다. 현재 리더라면 가능한 다른 voter로 leadership을 이양한 뒤 Raft를 종료하고, 운영자는 종료된 NodeId를 재사용하지 않고 새 NodeId learner 교체를 수행합니다.
배포 순서는 clock agent 건전 확인 → Raft/control listener 시작 → bootstrap/join → uniform voter·clock health 확인 → client readiness입니다. 일반 TCP connect 성공만을 readiness로 쓰지 않습니다.