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,410 @@
---
schemaVersion: 1
exampleOnly: true
generationAllowed: false
project: backend-clean-architecture
sourceDocument: final/document.md
sourceDocumentSha256: <example-only>
sourceRevision: <example-only>
generatedAt: <example-only>
---
# Root Tree Example
> 이 파일은 **구조 예시**다. 실제 `/shared/codebase/backend-clean-architecture` 분석을 수행해 만든 결과가 아니므로 downstream 문서 생성에 사용하지 않는다. 실제 프로젝트에서는 동일한 형식으로 source anchor와 evidence를 채우고 readiness를 판정한다.
PROJECT
backend-clean-architecture
TOPIC
JPA 피드 조회 성능
jpa-feed-query-performance
├── CASE
│ ├── DTO 변환 과정에서 발생한 Highlight 컬렉션 N+1
│ ├── 필드 접근 없이 발생한 EAGER ToOne N+1
│ ├── Fetch Join으로 N+1을 해결하다 만난 MultiBag과 행 폭증
│ ├── Collection Fetch Join Pagination의 In-memory Paging
│ ├── Projection 이후에도 1,509행을 읽은 Row Over-fetch
│ └── Visibility OR이 Keyset Index를 깨뜨린 문제
├── REFERENCE
│ ├── JPA N+1 정량 진단 기준
│ ├── Fetch Type과 Fetch Strategy 구분
│ ├── Fetch Join · Batch · Projection 선택 기준
│ ├── Top-N-per-group 선택 기준
│ ├── Keyset Pagination 설계 기준
│ ├── Feed Visibility Query Pattern
│ └── PostgreSQL Query Plan 측정 기준
├── OPEN QUESTION
│ ├── Highlight 없는 FeedItem을 허용할 것인가
│ ├── Round Trip과 Row Volume을 독립 측정할 것인가
│ ├── ANALYZE 이후 Cardinality Estimate는 어떻게 달라지는가
│ ├── feed_visible을 Production CQRS로 승격할 것인가
│ └── 실제 동시 트래픽에서도 이 구조가 안정적인가
└── DECISION
├── Query Plan은 실제 PostgreSQL에서 측정한다
├── Query Strategy는 FeedQueryPort 뒤에서 소유한다
├── Collection Fetch Join과 Pagination을 같이 사용하지 않는다
├── Entity Graph 조회에는 Batch Fetch를 사용한다
├── 화면 조회는 Read Projection을 사용한다
├── Feed Pagination은 Keyset을 사용한다
└── 현재 Read Model은 CQRS-lite로 유지한다
# Node Specifications
## CASE — DTO 변환 과정에서 발생한 Highlight 컬렉션 N+1
- slug: `highlight-collection-n-plus-one`
- readiness: `BLOCKED`
- source:
- `final/document.md#컬렉션-n1-정량화`
- code:
- `<actual source path/symbol from analyzed project>`
- evidence:
- `<actual raw/query-plan/test evidence path>`
- classification: `상세 분석에서 이 제목에 해당하는 구체적 발생 조건, 관측 결과, 진단 순서가 확인될 때 Case가 된다.`
- missing-verification: `example only — 실제 project source/evidence 확인 필요`
- relations:
- `<related Reference/Question/Decision slug and reason>`
## CASE — 필드 접근 없이 발생한 EAGER ToOne N+1
- slug: `eager-to-one-n-plus-one`
- readiness: `BLOCKED`
- source:
- `final/document.md#user-page-연관-숨은-추가-쿼리-정량화`
- code:
- `<actual source path/symbol from analyzed project>`
- evidence:
- `<actual raw/query-plan/test evidence path>`
- classification: `상세 분석에서 이 제목에 해당하는 구체적 발생 조건, 관측 결과, 진단 순서가 확인될 때 Case가 된다.`
- missing-verification: `example only — 실제 project source/evidence 확인 필요`
- relations:
- `<related Reference/Question/Decision slug and reason>`
## CASE — Fetch Join으로 N+1을 해결하다 만난 MultiBag과 행 폭증
- slug: `fetch-join-multibag-row-explosion`
- readiness: `BLOCKED`
- source:
- `final/document.md#fetch-join을-적용하며-확인한-두-가지-문제`
- code:
- `<actual source path/symbol from analyzed project>`
- evidence:
- `<actual raw/query-plan/test evidence path>`
- classification: `상세 분석에서 이 제목에 해당하는 구체적 발생 조건, 관측 결과, 진단 순서가 확인될 때 Case가 된다.`
- missing-verification: `example only — 실제 project source/evidence 확인 필요`
- relations:
- `<related Reference/Question/Decision slug and reason>`
## CASE — Collection Fetch Join Pagination의 In-memory Paging
- slug: `collection-fetch-join-in-memory-pagination`
- readiness: `BLOCKED`
- source:
- `final/document.md#컬렉션-fetch-join-페이징`
- code:
- `<actual source path/symbol from analyzed project>`
- evidence:
- `<actual raw/query-plan/test evidence path>`
- classification: `상세 분석에서 이 제목에 해당하는 구체적 발생 조건, 관측 결과, 진단 순서가 확인될 때 Case가 된다.`
- missing-verification: `example only — 실제 project source/evidence 확인 필요`
- relations:
- `<related Reference/Question/Decision slug and reason>`
## CASE — Projection 이후에도 1,509행을 읽은 Row Over-fetch
- slug: `projection-row-over-fetch`
- readiness: `BLOCKED`
- source:
- `final/document.md#dto-프로젝션`
- code:
- `<actual source path/symbol from analyzed project>`
- evidence:
- `<actual raw/query-plan/test evidence path>`
- classification: `상세 분석에서 이 제목에 해당하는 구체적 발생 조건, 관측 결과, 진단 순서가 확인될 때 Case가 된다.`
- missing-verification: `example only — 실제 project source/evidence 확인 필요`
- relations:
- `<related Reference/Question/Decision slug and reason>`
## CASE — Visibility OR이 Keyset Index를 깨뜨린 문제
- slug: `visibility-or-breaks-keyset-index`
- readiness: `BLOCKED`
- source:
- `final/document.md#가시성-조건`
- code:
- `<actual source path/symbol from analyzed project>`
- evidence:
- `<actual raw/query-plan/test evidence path>`
- classification: `상세 분석에서 이 제목에 해당하는 구체적 발생 조건, 관측 결과, 진단 순서가 확인될 때 Case가 된다.`
- missing-verification: `example only — 실제 project source/evidence 확인 필요`
- relations:
- `<related Reference/Question/Decision slug and reason>`
## REFERENCE — JPA N+1 정량 진단 기준
- slug: `jpa-n-plus-one-quantitative-diagnosis`
- readiness: `BLOCKED`
- source:
- `final/document.md#컬렉션-n1-정량화`
- classification: `관련 Case를 다시 서술하지 않고 다른 조회 문제에도 적용할 수 있는 판단 기준이 상세 분석에서 확인될 때 Reference가 된다.`
- scope: `<actual applicability derived from analysis>`
- exceptions: `<actual exceptions or none>`
- relations:
- `<originating Case/Decision and reason>`
## REFERENCE — Fetch Type과 Fetch Strategy 구분
- slug: `fetch-type-vs-fetch-strategy`
- readiness: `BLOCKED`
- source:
- `final/document.md#최초-구현과-첫-관찰`
- classification: `관련 Case를 다시 서술하지 않고 다른 조회 문제에도 적용할 수 있는 판단 기준이 상세 분석에서 확인될 때 Reference가 된다.`
- scope: `<actual applicability derived from analysis>`
- exceptions: `<actual exceptions or none>`
- relations:
- `<originating Case/Decision and reason>`
## REFERENCE — Fetch Join · Batch · Projection 선택 기준
- slug: `fetch-join-batch-projection-selection`
- readiness: `BLOCKED`
- source:
- `final/document.md#배치-페치`
- classification: `관련 Case를 다시 서술하지 않고 다른 조회 문제에도 적용할 수 있는 판단 기준이 상세 분석에서 확인될 때 Reference가 된다.`
- scope: `<actual applicability derived from analysis>`
- exceptions: `<actual exceptions or none>`
- relations:
- `<originating Case/Decision and reason>`
## REFERENCE — Top-N-per-group 선택 기준
- slug: `top-n-per-group-selection`
- readiness: `BLOCKED`
- source:
- `final/document.md#top-n-per-group`
- classification: `관련 Case를 다시 서술하지 않고 다른 조회 문제에도 적용할 수 있는 판단 기준이 상세 분석에서 확인될 때 Reference가 된다.`
- scope: `<actual applicability derived from analysis>`
- exceptions: `<actual exceptions or none>`
- relations:
- `<originating Case/Decision and reason>`
## REFERENCE — Keyset Pagination 설계 기준
- slug: `keyset-pagination-design`
- readiness: `BLOCKED`
- source:
- `final/document.md#keyset-vs-offset`
- classification: `관련 Case를 다시 서술하지 않고 다른 조회 문제에도 적용할 수 있는 판단 기준이 상세 분석에서 확인될 때 Reference가 된다.`
- scope: `<actual applicability derived from analysis>`
- exceptions: `<actual exceptions or none>`
- relations:
- `<originating Case/Decision and reason>`
## REFERENCE — Feed Visibility Query Pattern
- slug: `feed-visibility-query-pattern`
- readiness: `BLOCKED`
- source:
- `final/document.md#가시성-조건`
- classification: `관련 Case를 다시 서술하지 않고 다른 조회 문제에도 적용할 수 있는 판단 기준이 상세 분석에서 확인될 때 Reference가 된다.`
- scope: `<actual applicability derived from analysis>`
- exceptions: `<actual exceptions or none>`
- relations:
- `<originating Case/Decision and reason>`
## REFERENCE — PostgreSQL Query Plan 측정 기준
- slug: `postgresql-query-plan-measurement`
- readiness: `BLOCKED`
- source:
- `final/document.md#측정-환경과-데이터셋`
- classification: `관련 Case를 다시 서술하지 않고 다른 조회 문제에도 적용할 수 있는 판단 기준이 상세 분석에서 확인될 때 Reference가 된다.`
- scope: `<actual applicability derived from analysis>`
- exceptions: `<actual exceptions or none>`
- relations:
- `<originating Case/Decision and reason>`
## OPEN QUESTION — Highlight 없는 FeedItem을 허용할 것인가
- slug: `allow-feed-item-without-highlight`
- readiness: `BLOCKED`
- source:
- `final/document.md#확인된-문제와-이후-검증할-가설`
- known:
- `<grounded fact from detailed analysis>`
- unknown:
- `<specific unresolved uncertainty>`
- next-verification: `<concrete experiment/measurement/decision input>`
- decision-criterion: `<condition that would close the question>`
- relations:
- `<related Case/Reference/Decision and reason>`
## OPEN QUESTION — Round Trip과 Row Volume을 독립 측정할 것인가
- slug: `measure-round-trip-and-row-volume-separately`
- readiness: `BLOCKED`
- source:
- `final/document.md#측정-환경과-데이터셋`
- known:
- `<grounded fact from detailed analysis>`
- unknown:
- `<specific unresolved uncertainty>`
- next-verification: `<concrete experiment/measurement/decision input>`
- decision-criterion: `<condition that would close the question>`
- relations:
- `<related Case/Reference/Decision and reason>`
## OPEN QUESTION — ANALYZE 이후 Cardinality Estimate는 어떻게 달라지는가
- slug: `cardinality-estimate-after-analyze`
- readiness: `BLOCKED`
- source:
- `final/document.md#postgresql-query-plan-측정`
- known:
- `<grounded fact from detailed analysis>`
- unknown:
- `<specific unresolved uncertainty>`
- next-verification: `<concrete experiment/measurement/decision input>`
- decision-criterion: `<condition that would close the question>`
- relations:
- `<related Case/Reference/Decision and reason>`
## OPEN QUESTION — feed_visible을 Production CQRS로 승격할 것인가
- slug: `promote-feed-visible-to-production-cqrs`
- readiness: `BLOCKED`
- source:
- `final/document.md#cqrs-lite-읽기-모델`
- known:
- `<grounded fact from detailed analysis>`
- unknown:
- `<specific unresolved uncertainty>`
- next-verification: `<concrete experiment/measurement/decision input>`
- decision-criterion: `<condition that would close the question>`
- relations:
- `<related Case/Reference/Decision and reason>`
## OPEN QUESTION — 실제 동시 트래픽에서도 이 구조가 안정적인가
- slug: `stability-under-concurrent-traffic`
- readiness: `BLOCKED`
- source:
- `final/document.md#다음-단계`
- known:
- `<grounded fact from detailed analysis>`
- unknown:
- `<specific unresolved uncertainty>`
- next-verification: `<concrete experiment/measurement/decision input>`
- decision-criterion: `<condition that would close the question>`
- relations:
- `<related Case/Reference/Decision and reason>`
## DECISION — Query Plan은 실제 PostgreSQL에서 측정한다
- slug: `measure-query-plan-on-postgresql`
- readiness: `NEEDS_DECISION`
- decision-status: `NOT_DECIDED`
- source:
- `final/document.md#측정-환경과-데이터셋`
- decision-evidence:
- `<ADR/commit/PR/config + recorded rationale/user-supplied decision>`
- grounds:
- `<Case or Reference that supports the choice>`
- classification: `상세 분석에 실제 프로젝트 선택의 근거가 확인될 때만 READY로 바뀐다. 기술적으로 합리적인 권고만으로 Decision을 만들지 않는다.`
- relations:
- `<related nodes and reason>`
## DECISION — Query Strategy는 FeedQueryPort 뒤에서 소유한다
- slug: `query-strategy-behind-feed-query-port`
- readiness: `NEEDS_DECISION`
- decision-status: `NOT_DECIDED`
- source:
- `final/document.md#조회-전략은-포트-뒤-어댑터의-책임`
- decision-evidence:
- `<ADR/commit/PR/config + recorded rationale/user-supplied decision>`
- grounds:
- `<Case or Reference that supports the choice>`
- classification: `상세 분석에 실제 프로젝트 선택의 근거가 확인될 때만 READY로 바뀐다. 기술적으로 합리적인 권고만으로 Decision을 만들지 않는다.`
- relations:
- `<related nodes and reason>`
## DECISION — Collection Fetch Join과 Pagination을 같이 사용하지 않는다
- slug: `no-collection-fetch-join-with-pagination`
- readiness: `NEEDS_DECISION`
- decision-status: `NOT_DECIDED`
- source:
- `final/document.md#컬렉션-fetch-join-페이징`
- decision-evidence:
- `<ADR/commit/PR/config + recorded rationale/user-supplied decision>`
- grounds:
- `<Case or Reference that supports the choice>`
- classification: `상세 분석에 실제 프로젝트 선택의 근거가 확인될 때만 READY로 바뀐다. 기술적으로 합리적인 권고만으로 Decision을 만들지 않는다.`
- relations:
- `<related nodes and reason>`
## DECISION — Entity Graph 조회에는 Batch Fetch를 사용한다
- slug: `batch-fetch-for-entity-graph`
- readiness: `NEEDS_DECISION`
- decision-status: `NOT_DECIDED`
- source:
- `final/document.md#배치-페치`
- decision-evidence:
- `<ADR/commit/PR/config + recorded rationale/user-supplied decision>`
- grounds:
- `<Case or Reference that supports the choice>`
- classification: `상세 분석에 실제 프로젝트 선택의 근거가 확인될 때만 READY로 바뀐다. 기술적으로 합리적인 권고만으로 Decision을 만들지 않는다.`
- relations:
- `<related nodes and reason>`
## DECISION — 화면 조회는 Read Projection을 사용한다
- slug: `read-projection-for-screen-query`
- readiness: `NEEDS_DECISION`
- decision-status: `NOT_DECIDED`
- source:
- `final/document.md#dto-프로젝션`
- decision-evidence:
- `<ADR/commit/PR/config + recorded rationale/user-supplied decision>`
- grounds:
- `<Case or Reference that supports the choice>`
- classification: `상세 분석에 실제 프로젝트 선택의 근거가 확인될 때만 READY로 바뀐다. 기술적으로 합리적인 권고만으로 Decision을 만들지 않는다.`
- relations:
- `<related nodes and reason>`
## DECISION — Feed Pagination은 Keyset을 사용한다
- slug: `keyset-for-feed-pagination`
- readiness: `NEEDS_DECISION`
- decision-status: `NOT_DECIDED`
- source:
- `final/document.md#keyset-vs-offset`
- decision-evidence:
- `<ADR/commit/PR/config + recorded rationale/user-supplied decision>`
- grounds:
- `<Case or Reference that supports the choice>`
- classification: `상세 분석에 실제 프로젝트 선택의 근거가 확인될 때만 READY로 바뀐다. 기술적으로 합리적인 권고만으로 Decision을 만들지 않는다.`
- relations:
- `<related nodes and reason>`
## DECISION — 현재 Read Model은 CQRS-lite로 유지한다
- slug: `keep-cqrs-lite-read-model`
- readiness: `NEEDS_DECISION`
- decision-status: `NOT_DECIDED`
- source:
- `final/document.md#cqrs-lite-읽기-모델`
- decision-evidence:
- `<ADR/commit/PR/config + recorded rationale/user-supplied decision>`
- grounds:
- `<Case or Reference that supports the choice>`
- classification: `상세 분석에 실제 프로젝트 선택의 근거가 확인될 때만 READY로 바뀐다. 기술적으로 합리적인 권고만으로 Decision을 만들지 않는다.`
- relations:
- `<related nodes and reason>`