The keycloak project ended with four open questions that design could not
settle. A two-VM lab was built to answer them by measurement, and this is
that material: 26 experiments, 125 raw command outputs, 22 browser captures.
Follows the import procedure in README.md.
source/ the originating repository verbatim — 78 documents, 28 SVGs,
8 manifests, plus .source-revision recording the commit
final/ the SSOT
document.md 729 lines written from the 29 experiment documents, not
concatenated: what was predicted, what was measured, and
where the measurement itself was wrong
evidence/raw 125 outputs, flattened to <experiment>__<file> because
the originals collided (01-baseline.txt appeared three
times) and the audit only globs the top level
evidence/meta one per raw file; command and exitCode are null and the
README says why rather than inventing them
evidence/browser 22 captures
assets/ three diagrams through techviz
.techviz/ their VizSpecs
A separate project rather than an addition to keycloak: the B-layer answers
that project's four questions, but the A, C and D layers are about cluster
failure, SSO and operations, and one document.md should hold one subject.
The four question records there can point here through 관계.
Recorded rather than papered over: only three of the 28 diagrams were
remade. The repository forbids hand-drawn SVG and forbids titles inside the
canvas; all 28 originals carry both, so converting them is redrawing, not
reformatting. They stay in source/ and the gap is written into the document.
verify-pipeline.py passes. audit-records.py reports no issues.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
67 lines
3.2 KiB
Plaintext
67 lines
3.2 KiB
Plaintext
D-4 — in-flight 감시에서 나온 76건 실패: 서버 탓이 아니다
|
||
============================================================
|
||
수집: 2026-09-04 · dev 머신에서 실행한 감시 로그
|
||
|
||
무엇이 보였나
|
||
-------------
|
||
42초짜리 in-flight 요청을 연달아 돌리는 감시에서, 91건 중 76건이 실패했다.
|
||
|
||
08:13:40 코드=200 바이트=845361 시간=41.041830 연결수=1
|
||
08:14:22 코드=200 바이트=845361 시간=42.338482 연결수=1
|
||
08:15:04 코드=000 바이트=0 시간=0.001148 연결수=0 ← 여기부터
|
||
08:15:04 코드=000 바이트=0 시간=0.001140 연결수=0
|
||
08:15:04 코드=000 바이트=0 시간=0.000055 연결수=0
|
||
... (76건, 전부 08:15:04)
|
||
08:15:04 코드=200 바이트=845361 시간=42.236496 연결수=1 ← 곧바로 복귀
|
||
08:15:47 코드=200 바이트=845361 시간=42.327129 연결수=1
|
||
|
||
이걸 "갱신 중 진행 중이던 요청이 끊겼다"로 읽으면 안 되는 이유
|
||
----------------------------------------------------------------
|
||
1. **같은 순간 폴링은 멀쩡했다.** 0.2초 간격 폴링의 08:15:00–08:15:20 구간
|
||
표본 49건이 전부 200이다. 같은 호스트, 같은 TLS, 같은 머신이다.
|
||
|
||
08:15:00.24 200 0.092491
|
||
08:15:01.15 200 0.099177
|
||
08:15:02.38 200 0.078729
|
||
... 49건 / 비200 0건
|
||
|
||
2. **연결수=0 이다.** curl 이 TCP 연결을 시도조차 못 했다는 뜻이다.
|
||
서버가 연결을 끊었다면 연결수는 1이고 바이트는 0보다 크다.
|
||
|
||
3. **시간이 0.00005초다.** 50마이크로초. DNS 조회조차 이보다 오래 걸린다.
|
||
요청이 나가기 전에 로컬에서 실패했다.
|
||
|
||
4. **76건이 1초 안에 몰렸다.** 실패가 즉시 반환되니 while 루프가 폭주한 것이다.
|
||
시간축에 퍼진 장애가 아니라 순간의 스파이크다.
|
||
|
||
5. **재현되지 않는다.**
|
||
같은 URL 100회 연속 → 실패 0/100
|
||
DNS 해석 200회 → 실패 0/200
|
||
URL 유효성 → 코드=200 크기=845361, 콘솔이 참조하는 경로와 동일
|
||
|
||
6. **nginx 는 그 시각에 아무 일도 하지 않았다.** 마스터·워커가 22.4시간째
|
||
같은 프로세스다(07 참조). reload 도 재시작도 없었다.
|
||
|
||
결론
|
||
----
|
||
**로컬(dev 머신) 쪽 일시적 원인이며, 서버 가용성 사건이 아니다.**
|
||
정확한 원인은 특정하지 못했다 — curl 을 `-s` 로 돌려 오류 메시지를 버렸고,
|
||
종료 코드도 기록하지 않았기 때문이다.
|
||
|
||
고친 것
|
||
-------
|
||
감시를 종료 코드까지 남기는 형태로 교체했다. 다음에 같은 일이 생기면
|
||
curl 의 종료 코드(6=DNS, 7=connect, 35=TLS ...)로 원인이 바로 나온다.
|
||
실패 시 1초 쉬게 해 루프 폭주도 막았다.
|
||
|
||
R=$(curl -s --limit-rate 20k -o /dev/null -w "..." "$URL" 2>/dev/null); E=$?
|
||
echo "$T $R curl종료=$E"
|
||
[ $E -ne 0 ] && sleep 1
|
||
|
||
남기는 교훈
|
||
-----------
|
||
측정 장치가 실패했을 때 **왜 실패했는지 남기지 않으면, 그 실패를 대상 탓으로
|
||
돌릴지 장치 탓으로 돌릴지 판단할 근거가 없다.** 여기서는 대조 폴링이 같은
|
||
시각에 멀쩡했다는 사실 하나로 겨우 갈랐다. 대조군이 없었다면 이 76건은
|
||
"갱신 중 대규모 요청 실패"라는 그럴듯한 오보가 됐을 것이다.
|