Files
document-haness/docs/keycloak-session-store/tech-log-studio/when-the-measurement-lies/reference/reference-never-subtract-values-from-two-clocks.md
T
DongHyeonkaandClaude Opus 5 2109f726fe feat(pipeline): keycloak-session-store 25편·virtualization 59편을 S3→S5→S6 으로 돌린다
기록 84편을 계약 에이전트로 다시 썼다. 기존 71편(kss 25 · virt 46)과, 계약에만
있고 안 쓰여 있던 새 글감 13편이다. 원장 84개를 열어 단계마다 스킬 영수증과 관문
종료 코드를 적었고 verify-pipeline-run.py 가 error 0 으로 닫는다.

SSOT 결함 둘을 고쳤다.

- kss 의 `약 58일` 이 반입 중 `약 59일` 로 바뀌어 있었다. 원 증거 파일이
  「남은 일수: 88일 … 실제 갱신까지 약 58일」로 산수를 직접 적는다. D-4a 쪽
  `약 59일` 은 강제 갱신 뒤(`VALID: 89 days`)라 맞는 값이라 그대로 뒀다.
- virt §198 의 `11.6GB` 는 §178 의 원 측정 `Mem: 11648`(MiB)과 어긋나는데
  원 가이드의 표기 그대로라 고치지 않고 쓰이는 자리에 대조를 적었다.

기록의 수치 오류 셋을 고쳤다 — CASE 요약의 「게스트 셋에 8240MB」(5120+3120 은
둘이다), k3s 편이 같은 것을 여섯·일곱·여덟로 세던 것, no-docker 편의 「셋을 더
든다」(§281 의 표는 네 행이고 디스크 행이 빠져 있었다).

계약을 셋 고쳤다.

- kss 의 sourceRepository 리비전이 cdac9b8 이었는데 그 커밋에는 docs/guides/**
  28개가 아예 없다. 9465582b 로 바꾸고, 반입한 바이트가 어느 커밋과도 같지 않다는
  것을 측정값과 함께 적었다 — 반입은 커밋이 아니라 그 시점의 작업 트리에서 떠 온
  것이다(kss 297/306 · virt 12/14 가 작업 트리와 같고, 200 커밋을 거슬러 전수
  대조했을 때 가장 가까운 커밋도 28개가 어긋났다).
- virt 계약이 「2026-09-11 재배분」이라고 적는데 SSOT 는 재배분 날짜를 적지 않고
  재배분 뒤 값은 이미 2026-09-10 측정에 찍혀 있다.
- kss 후보 대장이 지나친 절 아홉에 처분을 적었다(warn 9 → 0). 새 글감은 0건이고
  넷은 앵커가 h3 슬러그의 접두가 아니라 중간 토막이라 검사기가 못 본 것이었다.

style_profile.mjs 의 결함 둘을 고쳤다 — frontmatter 가 문장으로 세어져
(실측 398자짜리 「문장」 하나) 평균 길이를 기준 안으로 밀어 올리고 있었고,
engPerSent 의 분자는 목록을 포함한 글에서, 분모는 목록을 걷어낸 글에서 세고
있었다(Question 기록에서 11.94 → 3.86).

verify-pipeline.py 전 항목 PASS · error 0 · unittest 334건 OK.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 11:01:55 +09:00

104 lines
8.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
kind: REFERENCE
slug: never-subtract-values-from-two-clocks
title: 두 시계에서 온 값을 빼지 않는다
topic: when-the-measurement-lies
topicName: 주입이 걸렸는지 무엇으로 아는가
project: keycloak-session-store
status: 게시 전
source:
- final/document.md#결정이-지켜지는지-확인하는-방법-두-시계
sourceRevision: cdac9b8178391311d8eca1ebc6cac15bb62d79af
evidence:
- ../../../final/evidence/raw/d4a-deploy-hook__01-hook-verified.txt
- ../../../final/evidence/raw/followup__03-b4-role-propagation.txt
---
# 두 시계에서 온 값을 빼지 않는다
서로 다른 기계에서 온 타임스탬프는 빼기 전에 두 시계가 같은지 확인한다. D-4a 에서 test-server 는 dev 머신보다 106초 빨랐다. 보정하지 않고 계산한 D-4 의 공백은 106초 짧았고(2199 → 2305초), 1~2초를 재는 D-4a 에서는 그냥 뺀 값이 107초로 나와 참값보다 약 106초 어긋났다.
## 관계
- **실패 76 건이 서버 탓이 아니었다 — 대조군이 오보를 막았다**
같은 D-4 의 값이다. 그 실험이 적어 둔 공백 2199초가 이 보정으로 2305초가 됐다.
- **산문으로 적힌 측정 장치를 실행 가능하게 고쳤더니 한 건이 깨졌다**
측정값이 아니라 재는 쪽이 틀린 다른 경우다. 거기서는 적어 둔 명령이 실행되지 않았고, 여기서는 실행된 명령이 남긴 시각이 어긋나 있었다.
## 목적
보정하지 않고 뺀 값이 자릿수만 어긋난다고 읽는 것을 막는다.
D-4a 에서 갱신부터 서빙까지 1~2초를 재려다 걸렸다. test-server 는 NTP 동기가 꺼져 있고 dev 머신보다 106초 빠르다. 그 사실을 적지 않고 뺀 D-4 의 공백은 2199초였는데 보정하면 2305초, 38분 25초다. 틀린 값이 106초 짧게 나왔다.
같은 왜곡이 D-4a 에서는 결과를 통째로 바꾼다. 그냥 빼면 훅이 발급보다 107초 뒤로 보이는데 참값은 1~2초라 약 106초가 어긋나고, 보정을 반대쪽에 걸면 음수가 나와 훅이 갱신보다 먼저 돈 것이 된다. 자릿수만이 아니라 두 사건의 선후까지 뒤집힌다.
## 규칙
### 1. 빼기 전에 두 값이 같은 시계에서 왔는지 확인한다
D-4 의 2199초는 archive/cert2.pem 의 mtime 과 일련번호 관측 시각을 그대로 뺀 값이다. 앞은 test-server 시계이고 뒤는 dev 시계다. 양쪽 다 UTC 로 적혀 있어 뺄 때는 같은 자에서 읽은 것처럼 보인다. 갈리는 단위는 시간대가 아니라 기계다.
차를 셸이 대신 빼게 하지 않는다. 두 값을 각각 찍어 놓고 눈으로 빼면 어느 값이 어느 기계에서 왔는지가 출력에 남지만, 명령 안에서 빼 버리면 결과 한 줄만 남고 그 정보가 사라진다.
D-4 의 가이드에는 이 편에만 있는 시각 표기 규약이 하나 있다. 시각마다 어느 시계인지를 붙여 `08:58:52 (dev)``17:22:13 KST (ts)` 로 적고, 보정한 값은 `08:20:27 (실제)` 로 적는다. 두 기계의 시계를 섞어 빼는 바람에 숫자를 한 번 틀린 뒤에 붙은 규약이다.
### 2. 어느 시계가 밀렸는지는 제3의 기준으로 가른다
dev 머신은 Google 및 Let's Encrypt ACME 응답과 0초 차였고, test-server 는 Google 기준으로 -105초였다. 두 기계만 맞대면 차이만 나오고 어느 쪽이 맞는지는 안 나온다. test-server 의 timedatectl 은 NTP=no · NTPSynchronized=no 였다.
### 3. 왜곡값은 여러 번 재서 흔들리는지 본다
ssh 왕복으로 잰 왜곡은 3회 모두 +106.1초였다. 흔들리면 네트워크 지연이 섞인 것이고 안정적이면 왜곡이다. 이 실험대가 쓴 106초는 이 106.1초를 반올림한 값이다. Google 비교 쪽의 -105초와는 1초 넘게 갈렸다 — 둘 다 test-server 가 앞섰다는 것은 같지만, 보정에 넣은 것은 흔들림을 확인한 ssh 왕복 쪽이다.
### 4. 왜곡은 주입하기 전에 재 둔다
D-4 와 D-4a 의 재현 절차는 인증서를 강제 갱신하기 전에 시계부터 잰다. 주입이 끝난 뒤에는 그때 그 시계가 얼마나 어긋나 있었는지를 되짚을 방법이 없기 때문이다. D-4 는 시계를 재지 않고 2199초를 적었고, 나중에 D-4a 가 시계를 재면서 그 값이 2305초로 정정됐다.
강제 갱신은 진짜 인증서를 발급해 되돌릴 수 없으므로 이 실험 전체에서 한 번만 쓴다. 그 한 번을 헛되이 쓰지 않으려고 주입 전에 잴 것을 여덟 칸으로 적어 두었고, 시계는 그중 일곱 번째다.
관측도 한 기계에서만 한다. 이 실험은 전부 dev 머신에서 관측했고, 호스트에서만 알 수 있는 파일 mtime 과 훅 로그만 보정해서 썼다. 밖에서 본 것이 이 실험의 답이고, dev 가 이 실험대에서 유일하게 정확한 시계였기 때문이다.
### 5. 보정한 결과는 독립된 시계로 교차검증한다
새 인증서의 SCT 는 CT 로그가 자기 시계로 서명한 시각이라 test-server 것도 dev 머신 것도 아니다. 106초를 뺀 타임라인에서 훅의 nginx -t 는 발급 1초 뒤에 놓였다. 보정이 틀린 방향이었으면 훅이 발급보다 앞에 왔을 것이다.
다만 훅 시각은 초 단위로만 남은 로그 값이고 SCT 에는 밀리초가 붙어 있다. 이 실험대가 적은 「정확히 1초 앞」은 그 반올림을 거친 표현이라 차가 1.000초라는 뜻은 아니다.
인증서에 적힌 notBefore 는 발급 시각을 재는 기준으로 쓸 수 없다. Let's Encrypt 가 notBefore 를 정확히 한 시간 백데이트하므로 그대로 발급 시각으로 읽으면 한 시간을 잃고, 한 시간을 더한 값도 발급 시각이 아니다. 이 실험대의 두 인증서에서 SCT 는 그 값보다 약 89초 앞섰다.
### 6. 같은 왜곡이라도 다른 실험에서 잰 값을 섞어 쓰지 않는다
B-4 는 타임스탬프 둘의 차로 약 107초를 어림했고, D-4a 는 ACME 응답을 제3의 기준으로 두고 106초를 다시 쟀다. 둘 다 test-server 가 앞선 양이다. 보정에 쓸 값은 D-4a 의 106초이고, 107초는 B-4 의 12회를 읽기 위한 어림이다.
### 7. 음수 지연이 나오면 계산이 아니라 시계를 의심한다
D-4 와 D-4a 의 「막히면」 표는 공백이나 지연이 음수로 나오는 것을 시계 재는 절차로 돌아갈 신호로 적어 두었다. 선후가 뒤집힌 결과는 자릿수만 어긋난 값과 달리 바로 걸린다.
## 적용 조건
- 서로 다른 기계에서 온 타임스탬프의 차이를 잴 때
- 로그 시각과 외부 서비스가 찍은 시각을 견줄 때. D-4a 가 견준 것은 훅 로그와 CT 로그의 SCT 다
- NTP 동기를 확인하지 않은 호스트가 측정에 끼어 있을 때. test-server 가 그랬다
- 앞선 실험이 적어 둔 구간 값을 그대로 인용할 때. D-4 의 2199초가 D-4a 때문에 정정됐다
## 예외
- 같은 기계의 단조 시계로만 잰 구간에는 걸리지 않는다
- 재는 구간이 왜곡보다 훨씬 길면 값은 틀려도 결론은 남는다. D-4 에서는 오차가 106초여서 결론이 안 바뀌었고, 1~2초를 재는 D-4a 에서는 결과가 완전히 뒤집혔다
- 근거는 D-4·D-4a 한 쌍이다. B-4 에서도 같은 두 기계가 약 107초 어긋나 클레임 반영 시각이 변경 시각보다 앞서 보였는데, 그쪽은 어림으로 재고 넘어갔다. 이 실험대 밖의 측정으로 넓혀 확인한 적은 없다
## 예시
- test-server : NTP=no · NTPSynchronized=no · dev 머신보다 106초 빠름
- dev 머신 → Google : 0초 차. dev 머신 → Let's Encrypt ACME : 0초 차
- ssh 왕복 왜곡 3회 : +106.1 / +106.1 / +106.1초. 안정적
- test-server → Google : -105초. 같은 왜곡을 다른 방법으로 잰 값이다
- 보정식 : 실제 시각 = test-server 시계 106초. dev 시계는 보정하지 않는다
- D-4 의 공백 : 보정 전 2199초, 보정 후 2305초 = 38분 25초
- 보정하지 않은 D-4a : 그냥 빼면 107초, 참값 1~2초보다 약 106초 어긋난다. 보정을 반대쪽에 걸면 음수가 된다
- 보정한 D-4a 타임라인 : 발급 다음 초에 훅 nginx -t 와 새 워커 37252 기동, 그다음 초에 훅 nginx -s reload. 발급에서 서빙까지 1~2초
- SCT 는 밀리초까지 찍히고 훅 로그는 초 단위다. 「정확히 1초 앞」은 그 차이를 반올림한 표현이다
- D-4a 의 새 인증서 : notBefore 는 `Sep 4 11:29:18`, 한 시간을 더하면 `12:29:18`, SCT 는 `12:27:49.05` 다. 약 88.9초 앞선다
- B-4 의 약 107초 : 타임스탬프 둘의 차로 어림한 별개 값이다