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

클러스터 시계 health 계약

Ticketing의 lease는 노드가 교체되거나 snapshot을 설치해도 기존 만료 시각을 늘리지 않아야 합니다. 클러스터 모드는 모든 노드 쌍의 wall-clock 차이가 설정한 Δ를 넘지 않음을 외부 clock agent로 검증합니다. 이 검증은 성능 옵션이 아니며, 클러스터에서는 생략할 수 없습니다.

clock agent는 Ticketing 프로세스와 독립된, 운영자가 신뢰하는 시간 검증 서비스입니다. 단순히 로컬 시계를 에코하는 서비스는 노드 간 offset을 증명하지 못하므로 사용하면 안 됩니다. agent는 신뢰할 수 있는 동기화 source를 확인하고 synced, 불확실성, 마지막 source sample age를 정직하게 보고해야 합니다.

요청·응답

TCP 연결당 한 번의 line을 교환하며 LF를 제외한 상한은 256바이트입니다. 숫자는 선행 0이 없는 정준 10진수만 허용합니다.

text
# Ticketing → clock agent
TCKCLK 2 <nonce>\n

# clock agent → Ticketing
TCKCLK 2 <nonce> <reference_unix_ms> <uncertainty_ms> <synced> <source_age_ms> <mac>\n
  • nonce: 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.
text
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로 쓰지 않습니다.