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:
co-authored by
Claude Opus 5
parent
43bccd08a8
commit
b2963105a8
@@ -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>`
|
||||
|
||||
Reference in New Issue
Block a user