docs(keycloak-session-store): import the session-storage lab as a new project

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>
This commit is contained in:
DongHyeonka
2026-09-04 22:51:59 +09:00
co-authored by Claude Opus 5
parent 43bccd08a8
commit b2963105a8
5017 changed files with 372751 additions and 4943 deletions
@@ -0,0 +1,135 @@
# 밀도 — 설명을 어디까지 하는가
순서는 [document-skeleton.md](document-skeleton.md)가 맡는다. 이 문서는 **한 절 안을 무엇으로
채우는가**를 맡는다. 순서가 맞는데도 얇게 읽히는 글이 있고, 그것이 이 저장소가 실제로 겪은 문제다.
기준이 되는 글은 [`../examples/`](../examples/) 여섯 편이다.
## 잰 값
`scripts/density.mjs`로 잰 것이다.
| | 여섯 편 | 이 저장소의 잘 쓴 기록 (AP4) |
|---|---|---|
| 낱말 | 1,480 ~ 3,164 (중앙값 2,508) | 1,726 |
| 문장 평균 낱말 | 17.8 | 17.3 |
| 코드블록 | 1 ~ 13 | 8 |
| **본문 속 수치** | **10 ~ 32개** | **1개** |
| **`##` 절** | **4 ~ 9개** | **18개** |
| 표 줄 | 0 ~ 15 | 37 |
문장 길이도 코드블록 수도 이미 맞다. 갈리는 것은 셋이다.
## 1. 수치 — 크기를 말하지 않으면 독자는 판단할 수 없다
여섯 편은 문단마다 잰 값을 댄다. 구조만 말하고 크기를 말하지 않는 문단이 거의 없다.
> 라이더에게 할당된 신규 배달이 N개라고 했을 때 … 간선: C(2N, 2)개
> n=2, C(4, 2) = 6 … 분당 20만 건의 경로를 계산해야 한다면, 시스템은 분당 120만 건의
> 실거리 계산을 수행해야 합니다. 이는 초당 약 2만 건으로 … (22396)
세는 과정을 보여 준다. 결과만 적지 않는다. 이 절이 없으면 뒤에 나오는 Redis 자료구조 선택이
과한 짓처럼 읽힌다.
**무엇을 세는가.** 자료에 있는 것 중에서 고른다 — 몇 개인가, 몇 번 도는가, 몇 바이트인가,
몇 밀리초인가, 몇 곳에서 부르는가, 그중 몇이 프로덕션인가, 전과 후가 각각 얼마인가.
**없으면 만들지 않는다.** 재지 않았으면 「재지 않았다」로 적고 그 자리를 비운다. 다만 자료에
숫자가 있는데 「여러 곳」·「대부분」·「크게」로 뭉갠 자리는 되돌린다. 그것이 이 저장소가 가장
자주 하는 실수다.
## 2. 절 — 잘게 자르면 설명이 자리를 못 잡는다
여섯 편의 `##` 절은 4~9개다. 절 하나가 600~900 낱말인 것도 있다.
한 절은 **주장 하나 + 그 근거**다. 근거는 코드·출력·수치·표 중 하나 이상이다. 절이 문단
두 개로 끝나면 자른 자리가 틀린 것이므로 앞뒤와 합친다.
절 제목은 그 절이 무엇을 하는지 말한다. 「현상」·「문제 원인 분석 및 해결」·「개선 결과」처럼
일하는 이름이 낫다. 「같은 이름의 헤더」처럼 명사구만 두면 무엇을 할 절인지 알 수 없다.
## 3. 표 — 산문을 대신하지 못한다
표는 **이미 산문으로 말한 것을 대조할 때** 쓴다. 22396의 TTL 표는 앞 세 문단이 설명한 것을 두
열로 정리한 것이고, 20161의 개선 전/후 표도 마찬가지다.
표로 설명을 시작하면 독자가 각 칸을 스스로 풀어야 한다. 표 줄이 산문 문단보다 많으면 그 글은
명세서다.
## 코드는 이름과 줄 번호로 대신하지 않는다
읽는 사람은 그 파일을 열 수 없다. `InboxCleanupJob:56`이라고만 적으면 독자에게는 아무 일도
일어나지 않는다.
13569는 「수정 전」과 「수정 후」 메서드를 통째로 싣는다. 7835는 같은 `login` 메서드를 두 번
싣는다 — 비관심사가 섞인 판과 분리한 판. 20161은 문제의 ES 쿼리 전문을 싣고 `1️⃣` `2️⃣`로 어디가
문제인지 표시한다.
코드블록 앞이나 뒤에 **무엇을 보라는 한 줄**을 붙인다. 코드만 던지지 않는다.
인용할 코드가 길면 그 절에 필요한 부분만 자른다. 자른 자리는 `// …`로 표시한다. 자르는 것과
이름만 대는 것은 다르다.
## 틀린 시도를 지우지 않는다
22396은 「시도 1. 데이터 압축」을 실패한 채로 남긴다.
> 하나의 지역에 1000개의 서로 다른 좌표 간 모든 실거리를 저장할 경우, JSON 데이터 원본
> 크기는 약 24MB였으며, 압축한 크기는 3MB였습니다. 만약 최대 대역폭이 10Gbps라고 했을 때,
> 3MB 크기로는 초당 3400회의 조회도 버티기 어렵습니다.
실패도 수로 닫는다. 24MB → 3MB는 성공한 압축률인데, 그것으로도 모자란다는 것을 3400회로 보인다.
자료에 실패한 시도가 있으면 살린다. 없는 실패를 지어내지 않는다.
## 감수한 것을 적고 닫는다
좋아진 것만 적고 닫는 글은 여섯 편에 없다. 23625는 「트레이드오프」를 목록으로 적고, 22396은
TTL 삭제와 명시적 삭제를 장단점 표로 나란히 놓고 **왜 단점이 있는 쪽을 골랐는지** 적는다.
13569는 빨라져서 생긴 문제와 그 대응 코드까지 싣는다.
## 독자를 어디서 채워 주나
**쓰기 직전에** 채운다. 앞에 몰아 두지 않고 뒤로 미루지도 않는다.
> 여기서 이야기하는 네트워크 대역폭은, ElastiCache 노드가 네트워크를 통해 초당 전송할 수
> 있는 최대 데이터 용량을 의미합니다. (22396 — 대역폭 이야기를 시작하는 자리)
> track_scores : ES 검색 쿼리에서 각 문서의 관련성 점수(`_score`)를 계산하고 저장할지 여부를
> 결정하는 설정 (20161 — 그 설정을 바꾼 절의 첫머리)
기준은 하나다. **이 낱말을 모르면 다음 문단을 못 읽는가.** 그러면 한 줄로 편다.
용어 절을 따로 두는 것은 그 글의 중심 개념일 때다(17386의 카프카 용어 일곱 개). 한두 개면
쓰는 자리에서 푼다.
## 검사기를 만족시키려고 문장을 넣지 않는다
이것은 겪은 일이다. `check_prose.mjs``monotone-endings`가 「물음(~할까요?)·권유(~봅시다)를
섞으라」고 안내했고, 그 글을 고친 사람이 절 제목을 물음으로 바꾸고 문단 가운데 수사 의문을 끼워
넣었다. 수치는 통과했고 글에는 없던 화자가 생겼다.
**종결어미 변화는 문장이 하는 일에서 나온다.** 한다체 안에도 어미는 여럿이다.
| 문장이 하는 일 | 어미 |
|---|---|
| 확인한 것 | ~였다 · ~했다 · ~됐다 |
| 지금 그러한 것 | ~한다 · ~된다 · ~넘긴다 |
| 아닌 것 | ~아니다 · ~없다 · ~않는다 |
| 이유 | ~때문이다 · ~뿐이다 · ~까지다 |
| 값·이름으로 끝나는 문장 | 명사 종결 |
물음은 그 절이 실제로 답할 때만 쓴다. 22396의 「왜 실거리가 중요할까?」·「어떤 내비게이션을
사용할까?」는 그 절이 답하는 물음이라 제목으로 맞다. 답이 예·아니오뿐인 물음
(「~인 걸까?」·「~되지 않을까?」)은 답하지 않고 분위기만 만든다. `rhetorical-question`이 잡는다.
마무리도 같다. 「지금까지 ~를 살펴봤다」로 끝나면 앞 내용을 한 번 더 읽힌 것뿐이다. 17386은 그
문장으로 마무리를 **열고** 회고와 참고 자료로 닫는다. 되풀이 뒤에 남은 일이 오면 두고, 되풀이가
마무리의 전부면 지운다. `closing-recap`이 경고로 알린다.
## 낱말 수는 목표가 아니다
설명을 다 하면 따라오는 값이다. 늘리려고 문단을 넣으면 검사기는 통과하고 글은 나빠진다.
반대로 1,700 낱말짜리 글이 코드도 수치도 없이 표만 서른 줄이라면, 그것은 짧아서가 아니라
**설명을 안 해서** 얇은 것이다.