pipeline: make tech-log-tree.json the one decomposition contract and enforce it
리뷰 두 건을 반영했다. 계약 - tech-log-tree.json 하나가 분해 계약이자 색인이다. 사람이 읽는 트리·Node Specification· 후보 대장은 없어졌고, 문서에 남아 있던 그 개념을 걷어냈다 - candidateScope — 후보를 찾는 SSOT 범위. 접어 넣은 제2부·제3부는 근거이지 후보가 아니다 - sourceRepository — 분석한 저장소의 경로·리비전·판단 근거. 리비전을 모르면 null 로 두고 지어내지 않는다. 갈래가 여럿이면 revisions - 검사기: 계약 미채택·PENDING·PROMOTE↔글감 양방향·candidateScope·sourceRepository 를 error/warn 으로 센다. 옛 스키마도 검사를 피하지 못한다. 테스트 22 → 31 기록 쓰기 - 템플릿 5종에 source·sourceRevision·topicName, Question 에 닫는 조건, 본문 없는 종류에서 assets 제거. 고정 절 개수 삭제 - check_evidence.mjs — 인용한 코드가 SSOT 에 있는지, 앵커가 SSOT 를 가리키는지, 제목이 계약과 같은지, 리비전이 저장소에 있는지. 게시된 기록에서 SSOT 와 다른 URL 을 잡았다 문체 - 문체 규칙의 정본을 ai-tells.md 로. explaining.md 의 질문체 제목·절 끝 대조 반복·그림 예고 규칙을 삭제해 충돌을 없앴다. 첫 절 「설명 뒤에 평가를 붙이지 않는다」에 지우는 사례 네 유형 - voice 스킬의 「독자 쪽을 본다」를 자료에 오독 기록이 있을 때로 좁히고, 평가만 더한 예시를 교체 - check_prose: 안내 문장을 요구하던 경고 제거, 문장이 끝나지 않은 채 문단이 끝나는 조각 검사 추가 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
73026cada6
commit
9d2a3725c5
@@ -1,38 +1,142 @@
|
||||
---
|
||||
name: deriving-tech-log-root-tree
|
||||
description: Use when a completed or substantially completed docs project analysis must be decomposed into grounded Tech Log Topics and candidate Case, Reference, Open Question, and Decision records.
|
||||
description: Use when a completed or substantially completed docs project analysis must be decomposed into grounded Tech Log Topics and candidate Case, Concept, Reference, Open Question, and Decision records.
|
||||
---
|
||||
|
||||
# Deriving Tech Log Root Tree
|
||||
|
||||
## Core rule
|
||||
|
||||
**Discover record candidates from evidence already present in the detailed analysis. Do not brainstorm a content calendar.**
|
||||
**Select what is worth publishing. Do not emit everything the analysis found.**
|
||||
|
||||
Recall is the objective function of an analysis-coverage audit. It is not the objective
|
||||
function of an editorial decomposition. When the two are measured on one axis, every
|
||||
analysis by-product becomes a record. A decomposition that excludes nothing has not
|
||||
selected anything.
|
||||
|
||||
## SSOT hierarchy
|
||||
|
||||
Four layers, and only one of them is the input for finding candidates.
|
||||
|
||||
| Layer | Role |
|
||||
|---|---|
|
||||
| code · config · execution evidence | ground truth for facts |
|
||||
| `final/document.md` | **SSOT for the candidate scope** — the only input for discovering candidates |
|
||||
| `analysis/**/*.md` | supporting detail for a claim `final` already adopted — exists only while the analysis is running |
|
||||
| `tech-log-tree.json` | the decomposition contract and the index at once, and the source of truth. Written by hand; a script refreshes only the fields it can read back from the record files |
|
||||
|
||||
Do not open `analysis/**` to discover candidates. Open it to check the detail of a claim
|
||||
that is already in `final/document.md`. If the analysis holds material that `final` does
|
||||
not, **fix `final/document.md` first**, then decompose. Otherwise 61 module documents
|
||||
become 61 competing SSOTs and the tree grows to their combined section count.
|
||||
|
||||
## Candidate scope
|
||||
|
||||
A folded `final/document.md` is not uniformly candidate material. Part 1 is the integrated
|
||||
analysis and it is where candidates come from. Part 2 holds the module analyses that were
|
||||
folded in, and Part 3 holds the analysis material — both are supporting evidence, and
|
||||
reading them as candidate material recreates the failure the fold was meant to end: one
|
||||
candidate per module-analysis heading.
|
||||
|
||||
Declare the boundary in the contract so it is checkable rather than remembered.
|
||||
|
||||
```json
|
||||
"candidateScope": {
|
||||
"document": "final/document.md",
|
||||
"sections": ["§3", "§4", "§5", "§6", "§7", "§8", "§9", "§10", "§11"],
|
||||
"excluded": ["제2부 — 모듈 분석 전문", "제3부 — 분석 재료"]
|
||||
}
|
||||
```
|
||||
|
||||
An anchor outside that scope is a source anchor, not a candidate. Cite it from a node whose
|
||||
candidate came from Part 1.
|
||||
|
||||
## Which files exist, and when
|
||||
|
||||
Decomposition happens after the analysis has been folded in, and the folded project has
|
||||
fewer files than the one that was being analyzed. Read what is actually there.
|
||||
|
||||
| Phase | Files | Where candidates come from |
|
||||
|---|---|---|
|
||||
| analysis running | `state.json` · `source-index.md` · `analysis/**` · `final/document.md` | `final/document.md` |
|
||||
| analysis folded in | `final/document.md` only | `final/document.md`, candidate scope |
|
||||
| decomposition | `final/document.md` · `tech-log-tree.json` | candidate scope |
|
||||
|
||||
`state.json` and `source-index.md` say how far the analysis got and which code it covered.
|
||||
They do not hold candidates, and in a folded project they are gone.
|
||||
|
||||
## Required sequence
|
||||
|
||||
1. Read project `state.json`, `final/document.md`, and `source-index.md`.
|
||||
2. Read bounded analysis files when the final document's anchor is not enough to judge classification.
|
||||
3. Identify coherent Topics from shared engineering problem spaces, not merely folder/module names.
|
||||
4. Within each Topic, identify concrete incidents first (Case), then reusable rules (Reference), unresolved unknowns (Open Question), and explicit project choices (Decision).
|
||||
5. Write the human-readable PROJECT/TOPIC tree.
|
||||
6. Add a Node Specification for every title with source anchors, readiness, relations, and kind-specific metadata.
|
||||
7. Run `references/decomposition-checklist.md`.
|
||||
8. Hash the source detailed document and record the project revision so downstream generation can detect staleness.
|
||||
1. Read the candidate scope of `final/document.md` end to end.
|
||||
2. Pick representative **Cases** from the confirmed-problem and execution sections
|
||||
(in the standard layout, §3–§8).
|
||||
3. Pick **References** from the reusable-criteria section (§9).
|
||||
4. Pick **Decisions** from the explicit-decision section (§10).
|
||||
5. Pick **Questions** from the unresolved section (§11).
|
||||
6. Only now add the **Concepts** those four need in order to be understood. Concept is
|
||||
derived backwards from the records that require it, never by sweeping headings.
|
||||
7. Give every candidate a disposition — `references/candidate-disposition.md` — and set
|
||||
`dispositionReview` to `CONFIRMED` only for the ones a person actually re-read.
|
||||
8. Group `PROMOTE` candidates into Topics. Write one reader question per Topic.
|
||||
9. Write every promoted candidate into `tech-log-tree.json` as a node under its Topic,
|
||||
with the fields its kind requires. There is no second tree to keep in step.
|
||||
10. Run `references/decomposition-checklist.md`.
|
||||
11. Record `candidateScope`, the source document hash, and the project revision.
|
||||
12. `python3 scripts/verify-tech-log-tree.py <project>` — errors must be 0.
|
||||
|
||||
Use `.agents/skills/writing-tech-log-records/references/root-tree-contract.md` as the output contract.
|
||||
Use `.agents/skills/writing-tech-log-records/references/tech-log-tree-contract.md` as the
|
||||
output contract.
|
||||
|
||||
## Topic boundary
|
||||
|
||||
A Topic is a stable problem/decision area whose records share terminology, evidence, and relations. It should be broad enough to connect several records when the evidence supports them, but narrow enough that its References and Decisions remain coherent.
|
||||
**A Topic is one reader question.** Write it under the topic slug:
|
||||
|
||||
Do not create one Topic per source file. Do not force unrelated incidents into one Topic because they use the same framework.
|
||||
```text
|
||||
TOPIC
|
||||
OAuth 자격증명과 세션의 보관 경계
|
||||
oauth-oidc-auth-boundary
|
||||
독자 질문 — 자격증명과 세션을 누가 보관하고, 누가 API 요청을 만들며, 보호 자원은 무엇을 신뢰하는가?
|
||||
```
|
||||
|
||||
A node that does not help answer that question belongs to another Topic. If a Topic needs
|
||||
two questions, it is two Topics. If two Topics share one question, they are one Topic.
|
||||
|
||||
This is the test that catches both failures at once — splitting one problem space across
|
||||
`state-machines-and-ownership`, `state-ownership-and-concurrency`, and
|
||||
`owner-safe-state-machines`, and packing forwarded-header trust, fileserver mapping,
|
||||
Redis key APIs, and permission normalization into one `admission-budget-and-backpressure`.
|
||||
|
||||
Do not create one Topic per source file or module. A directory is not a Topic.
|
||||
|
||||
## Classification discipline
|
||||
|
||||
- Case title names the concrete engineering problem/verification, not a generic technology lesson.
|
||||
- Reference title names a reusable criterion/distinction.
|
||||
- Open Question title states an uncertainty that is still unresolved.
|
||||
- Decision title states an actual/proposed project direction evidenced in sources.
|
||||
- **Case** — one problem, an observation or reproduction, a diagnosis, a closed conclusion.
|
||||
- **Concept** — structure or behavior that must be explained from the beginning before a
|
||||
Case can be understood. Has a `basis-version`.
|
||||
- **Reference** — a rule that applies to the next project, with scope and exceptions.
|
||||
- **Open Question** — no answer yet, the design turns on the answer, and there is a next
|
||||
verification and a closing criterion.
|
||||
- **Decision** — the project actually chose a direction, with grounds and an accepted cost.
|
||||
|
||||
Branches may be empty. Symmetry is not a quality goal.
|
||||
The independence test decides all five:
|
||||
|
||||
> Delete this record and fold it into a related Case or Concept as one section. If
|
||||
> understanding, decisions, and reuse are unchanged, it is not an independent record.
|
||||
|
||||
Branches may be empty. Symmetry is not a quality goal. Neither is volume — a large
|
||||
denominator justifies a long `final/document.md`, not a long tree.
|
||||
|
||||
## Refreshing the derived fields
|
||||
|
||||
There is one file. `tech-log-tree.json` is written by hand, and the build refreshes only
|
||||
what it can read back from the record files — `file`, `publication`, `status`, `studioId`,
|
||||
`assets`, `evidenceFiles` — plus `counts`, `ssotSha256`, and the `unlisted` list.
|
||||
|
||||
```bash
|
||||
python3 scripts/build-tech-log-tree.py <project>
|
||||
```
|
||||
|
||||
`readiness`, `source`, `classification`, `relations`, and the rest of each kind's fields
|
||||
survive a rebuild untouched. The build never reads the directory listing for Topics: a
|
||||
folder left behind after a node is dropped from the contract shows up in `unlisted`, and it
|
||||
does not come back as a Topic.
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
# 후보의 처분 — 무엇을 독립 기록으로 만들고 무엇을 만들지 않는가
|
||||
|
||||
분석에서 나온 항목마다 처분을 하나 적는다. 처분은 `tech-log-tree.json` 의 `candidates` 에
|
||||
남고, `PROMOTE` 만 같은 파일의 `topics` 로 올라간다.
|
||||
|
||||
## 목표 함수
|
||||
|
||||
**빠짐없이 방출하는 것이 아니라 고르는 것이다.** 분석 누락을 검증할 때는 recall 100%
|
||||
가 맞다. 공개할 글을 정할 때는 아니다. 「분석에서 보존할 가치」와 「독립된 글로 읽을
|
||||
가치」는 다른 물음이고, 둘을 한 축으로 재면 분석 부산물이 전부 글이 된다.
|
||||
|
||||
제외가 0 건인 분해는 선별하지 않은 분해다.
|
||||
|
||||
## 여섯 가지 처분
|
||||
|
||||
| 처분 | 뜻 | 어디로 |
|
||||
|---|---|---|
|
||||
| `PROMOTE` | 독립 Tech Log 로 쓴다 | `tech-log-tree.json` 의 노드가 된다 |
|
||||
| `MERGE_INTO` | 다른 기록의 한 절·표 행으로 흡수한다 | 흡수한 기록의 slug 를 `target` 에 적는다 |
|
||||
| `KEEP_IN_SSOT` | 중요한 분석 결과지만 독립 기록은 아니다 | `final/document.md` 와 `analysis/**` 에 남는다 |
|
||||
| `NEEDS_EVIDENCE` | 주장에 아직 검증이 없다 | 측정한 뒤에 다시 판정한다 |
|
||||
| `NEEDS_DECISION` | 방향이 그럴듯하지만 프로젝트가 정하지 않았다 | 정해진 뒤에 다시 판정한다 |
|
||||
| `BLOCKED` | 원본이 불완전하거나 서로 어긋난다 | 원본을 고친 뒤에 다시 판정한다 |
|
||||
|
||||
**`KEEP_IN_SSOT` 은 실패가 아니다.** 정보를 버리지 않으면서 글로 과분류하지 않는
|
||||
상태다. 분석 범위, 호출자 수, 미배선 사실, 커버리지 원장, 재현에 쓴 레인 같은 것이
|
||||
여기 온다 — 분석에는 반드시 남아야 하고 공개 기록으로는 읽을 사람이 없다.
|
||||
|
||||
`REJECTED` 는 쓰지 않는다. 무엇을 버렸는지가 아니라 무엇이 어디에 남았는지를 적는다.
|
||||
|
||||
## 독립성 검사
|
||||
|
||||
처분을 정하는 물음은 하나다.
|
||||
|
||||
> **이 기록을 없애고 관련 Case 나 Concept 의 한 절로 넣어도 이해·결정·재사용성이
|
||||
> 그대로라면 독립 기록으로 만들지 않는다.**
|
||||
|
||||
그대로면 `MERGE_INTO`. 넣을 자리조차 없으면 `KEEP_IN_SSOT`.
|
||||
|
||||
## 종류마다 독립 기록이 되는 조건
|
||||
|
||||
| 종류 | 독립 기록이 되는 조건 | 되지 않는 것 |
|
||||
|---|---|---|
|
||||
| Case | 하나의 문제 · 관측·재현 · 진단 · 결론이 닫힌다 | 단순 정적 카운트, 문구 수정, 같은 원인의 부분 증상 |
|
||||
| Concept | 내부 구조나 동작을 처음부터 설명해야 Case 를 이해할 수 있다. 기준 버전이 있다 | 분석 범위, 호출자 수, 미배선 사실, 한두 문장으로 Case 안에 설명되는 것 |
|
||||
| Reference | 다음 프로젝트에도 적용할 규칙이며 적용 조건과 예외가 있다 | Case 결론을 선언문으로 바꾼 것 |
|
||||
| Question | 답이 아직 없고, 답에 따라 설계가 달라지며, 다음 검증과 종료 기준이 있다 | 실행하지 않은 테스트 목록, 막연한 "다른 방법은?" |
|
||||
| Decision | 대안 중 프로젝트가 실제 방향을 정했고 근거와 감수한 비용이 있다 | 기술이 존재한다는 사실, 권장사항, 아직 정하지 않은 방향 |
|
||||
|
||||
## Case 를 언제 합치나
|
||||
|
||||
**같은 질문에서 나와 같은 결론에 닿는 관측이면 한 Case 다.** 인과 단위·의미 단위·검증
|
||||
단위가 셋 다 같아야 합친다는 기준은 너무 좁다 — 그 기준에서는 같은 결함의 다섯 증상이
|
||||
다섯 편이 된다.
|
||||
|
||||
관측이 여럿이면 한 Case 안에 표나 하위 절로 넣는다. 표의 행 하나가 될 것을 기록
|
||||
하나로 만들지 않는다.
|
||||
|
||||
## Concept 을 언제 만드나
|
||||
|
||||
**Case·Decision·Question 을 먼저 고른 뒤 거꾸로 뽑는다.** "이 Case 를 읽는 사람이 미리
|
||||
알아야 하는 구조가 있는가"를 묻고, 있으면 그때 Concept 을 만든다. 메커니즘처럼 보이는
|
||||
절을 훑어 채우면 어느 Case 도 필요로 하지 않는 개념이 쌓인다.
|
||||
|
||||
Concept 에는 `basis-version` 이 있어야 한다. 무엇을 보고 쓴 글인지 없으면 언제 낡았는지
|
||||
읽는 사람이 알 방법이 없다.
|
||||
|
||||
제목이 이런 꼴이면 Concept 이 아니다.
|
||||
|
||||
```text
|
||||
호출자가 없다 → 부재는 Case 의 관측이다
|
||||
프로덕션에서 실행되지 않는다 → 같은 이유
|
||||
구현 클래스 51개를 전부 읽었다 → 분석 범위. KEEP_IN_SSOT
|
||||
보류한 항목과 보류한 이유 → 분석 진행 기록. KEEP_IN_SSOT
|
||||
(8.4) 문서/구현 드리프트 — … → 분석 문서의 절 제목을 그대로 옮긴 것
|
||||
Confirmed — … → 같은 것. finding 등급이 제목에 남아 있다
|
||||
```
|
||||
|
||||
## 대장에 적는 것
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "A05-F012",
|
||||
"kindCandidate": "CASE",
|
||||
"sourceRefs": ["final/document.md#8-3"],
|
||||
"summary": "…",
|
||||
"disposition": "MERGE_INTO",
|
||||
"dispositionReview": "CONFIRMED",
|
||||
"target": "case:two-owners-popped-the-evidence-frame",
|
||||
"reason": "같은 결함의 두 번째 증상이다. 그 Case 의 재현 절에 행으로 들어간다"
|
||||
}
|
||||
```
|
||||
|
||||
`dispositionReview` 는 `CONFIRMED` 와 `PENDING` 둘이다. 사람이 위 물음으로 판정했으면
|
||||
`CONFIRMED`, recall 로 자동 방출된 것이면 `PENDING` 이다. **`PENDING` 이 남아 있는
|
||||
프로젝트는 글감 선별이 끝나지 않은 것이다.**
|
||||
|
||||
`python3 scripts/verify-tech-log-tree.py <프로젝트>` 가 남은 건수를 error 로 센다. 경고가
|
||||
아니라 error 인 이유는 하나다 — 경고로 두면 재판정하지 않은 트리로 글을 쓰기 시작할 수 있다.
|
||||
@@ -1,16 +1,28 @@
|
||||
# Root Tree Decomposition Checklist
|
||||
# Tech Log Tree Decomposition Checklist
|
||||
|
||||
## Selection
|
||||
|
||||
- [ ] Every analysis candidate carries a disposition, and `KEEP_IN_SSOT` is used.
|
||||
- [ ] `dispositionReview: PENDING` is 0 — nothing reached the tree by recall alone.
|
||||
- [ ] Each `PROMOTE` node passes the independence test.
|
||||
- [ ] Candidates were discovered from `final/document.md`, not from `analysis/**`.
|
||||
- [ ] Material found only in `analysis/**` was added to `final/document.md` first.
|
||||
- [ ] `candidateScope` is declared, and no candidate came from outside it.
|
||||
|
||||
## Source integrity
|
||||
|
||||
- [ ] The tree records the detailed document hash and project revision/snapshot.
|
||||
- [ ] The tree records the source document hash, ledger hash, and project revision.
|
||||
- [ ] Every node has at least one source anchor.
|
||||
- [ ] Source anchors actually contain the material implied by the title.
|
||||
- [ ] Runtime-dependent claims name evidence or use `NEEDS_EVIDENCE`.
|
||||
- [ ] `readiness` states how well evidenced the node is. It does not state whether the
|
||||
record has been written or published.
|
||||
|
||||
## Topic quality
|
||||
|
||||
- [ ] Topic is a coherent engineering problem space rather than a directory name.
|
||||
- [ ] Two Topics do not merely split the same causal chain arbitrarily.
|
||||
- [ ] Every Topic has one reader question, and every node in it helps answer that question.
|
||||
- [ ] No two Topics share a reader question.
|
||||
- [ ] Topic is an engineering problem space rather than a directory name.
|
||||
- [ ] A large Topic is split when its records no longer share useful relations/criteria.
|
||||
|
||||
## Case
|
||||
@@ -18,6 +30,15 @@
|
||||
- [ ] There is a specific incident, experiment, failure, diagnosis, or verification sequence.
|
||||
- [ ] The title can be understood without inventing a historical story.
|
||||
- [ ] The conclusion is bounded by actual evidence.
|
||||
- [ ] Observations that answer the same question with the same conclusion are one Case,
|
||||
as a table or sub-sections — not several partial Cases.
|
||||
|
||||
## Concept
|
||||
|
||||
- [ ] It was added because a Case, Decision, or Question needs it, not by sweeping headings.
|
||||
- [ ] `basis-version` names what the explanation was written against.
|
||||
- [ ] The title names a mechanism, not an absence, a count, or an analysis-scope fact.
|
||||
- [ ] No analysis section number or finding grade survives in the title.
|
||||
|
||||
## Reference
|
||||
|
||||
@@ -29,15 +50,25 @@
|
||||
|
||||
- [ ] The answer is not already in the analysis.
|
||||
- [ ] Known/unknown/next verification are separable.
|
||||
- [ ] The design or a decision actually turns on the answer.
|
||||
- [ ] Candidate options are included only when sources really considered them.
|
||||
|
||||
## Decision
|
||||
|
||||
- [ ] A project choice is explicitly recorded or user-supplied.
|
||||
- [ ] `technology is present` is not being treated as rationale.
|
||||
- [ ] The accepted cost is stated, not only the benefit.
|
||||
- [ ] `NEEDS_DECISION` is used if the direction is only a recommendation.
|
||||
|
||||
## Duplication
|
||||
## Duplication and shape
|
||||
|
||||
- [ ] No two nodes have the same primary purpose.
|
||||
- [ ] Relations are used instead of copying one record's entire content into another.
|
||||
- [ ] Relations are used instead of copying one record into another.
|
||||
- [ ] Node count is bounded by what a reader would read, not by the analysis denominator.
|
||||
|
||||
## Parity
|
||||
|
||||
- [ ] Every node traces back to a `PROMOTE` candidate, and every `PROMOTE` candidate has a node.
|
||||
- [ ] `counts` matches what `build-tech-log-tree.py` produces.
|
||||
- [ ] `unlisted` is empty — no record file exists outside the contract.
|
||||
- [ ] `python3 scripts/verify-tech-log-tree.py <project>` reports 0 errors.
|
||||
|
||||
@@ -1,410 +0,0 @@
|
||||
---
|
||||
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>`
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
# `tech-log-tree.json` 예시
|
||||
|
||||
**구조 예시다.** 실제 분석을 수행해 만든 결과가 아니므로 이 값을 그대로 옮겨 쓰지 않는다.
|
||||
실제 프로젝트에서는 같은 모양에 진짜 source anchor 와 evidence 를 채우고 readiness 를 판정한다.
|
||||
|
||||
트리는 이 파일 하나다. 사람이 읽는 트리와 Node Specification 을 따로 쓰고 대조하던 절차는 없다 —
|
||||
계약과 색인이 같은 파일이라 어긋날 자리가 없다.
|
||||
|
||||
```json
|
||||
{
|
||||
"schemaVersion": 4,
|
||||
"project": "n+1liner",
|
||||
"ssot": "final/document.md",
|
||||
"ssotSha256": "<sha256>",
|
||||
"sourceRevision": "<git-revision>",
|
||||
"generatedAt": "<YYYY-MM-DD>",
|
||||
"candidateScope": {
|
||||
"document": "final/document.md",
|
||||
"sections": ["§3", "§4", "§5", "§6", "§7", "§8", "§9", "§10", "§11"],
|
||||
"excluded": ["제2부 — 모듈 분석 전문", "제3부 — 분석 재료"]
|
||||
},
|
||||
"contract": {
|
||||
"readinessValues": ["READY", "OPEN", "NEEDS_EVIDENCE", "NEEDS_DECISION", "BLOCKED"],
|
||||
"dispositionValues": {
|
||||
"PROMOTE": "독립 Tech Log 로 쓴다",
|
||||
"MERGE_INTO": "다른 기록의 한 절로 흡수한다",
|
||||
"KEEP_IN_SSOT": "분석에는 남기고 독립 기록으로 만들지 않는다",
|
||||
"NEEDS_EVIDENCE": "주장에 아직 검증이 없다",
|
||||
"NEEDS_DECISION": "방향이 그럴듯하지만 프로젝트가 정하지 않았다",
|
||||
"BLOCKED": "원본이 불완전하거나 서로 어긋난다"
|
||||
}
|
||||
},
|
||||
"topics": {
|
||||
"jpa-feed-query-performance": {
|
||||
"topic": "jpa-feed-query-performance",
|
||||
"title": "JPA 피드 조회 성능",
|
||||
"readerQuestion": "피드 한 화면을 그리는 데 쿼리가 몇 번 나가고, 조회 전략을 바꿀 때 무엇이 함께 바뀌는가?",
|
||||
"kinds": {
|
||||
"case": [
|
||||
{
|
||||
"title": "필드 접근 없이 발생한 EAGER ToOne N+1",
|
||||
"kind": "case",
|
||||
"slug": "eager-to-one-n-plus-one",
|
||||
"readiness": "READY",
|
||||
"source": ["final/document.md#user-page-연관-숨은-추가-쿼리-정량화"],
|
||||
"code": ["FeedQueryRepository.java:loadFeed"],
|
||||
"evidence": ["evidence/raw/explain/highlights-child-plan-A.txt"],
|
||||
"classification": "조회 한 번에 나간 쿼리 수를 세어 재현했고 실행계획으로 확인했다",
|
||||
"missing-verification": "동시 트래픽에서는 재지 않았다",
|
||||
"relations": ["reference:fetch-type-vs-fetch-strategy"]
|
||||
}
|
||||
],
|
||||
"concept": [
|
||||
{
|
||||
"title": "Fetch Type 과 Fetch Strategy 가 갈라지는 자리",
|
||||
"kind": "concept",
|
||||
"slug": "fetch-type-and-fetch-strategy",
|
||||
"readiness": "READY",
|
||||
"source": ["final/document.md#fetch-type과-fetch-strategy"],
|
||||
"basis-version": "Hibernate 6.4 · Spring Data JPA 3.2",
|
||||
"classification": "이 구분을 먼저 알아야 위 Case 의 관측을 읽을 수 있다",
|
||||
"relations": ["case:eager-to-one-n-plus-one"]
|
||||
}
|
||||
],
|
||||
"reference": [
|
||||
{
|
||||
"title": "Fetch Type 과 Fetch Strategy 를 구분한다",
|
||||
"kind": "reference",
|
||||
"slug": "fetch-type-vs-fetch-strategy",
|
||||
"readiness": "READY",
|
||||
"source": ["final/document.md#fetch-type과-fetch-strategy"],
|
||||
"classification": "다음 프로젝트에도 적용할 조회 기준이다",
|
||||
"scope": "JPA 연관을 하나라도 조회하는 모듈",
|
||||
"exceptions": "단건 조회만 있는 경로에는 걸리지 않는다",
|
||||
"relations": ["case:eager-to-one-n-plus-one"]
|
||||
}
|
||||
],
|
||||
"question": [
|
||||
{
|
||||
"title": "ANALYZE 이후 Cardinality Estimate 는 어떻게 달라지는가",
|
||||
"kind": "question",
|
||||
"slug": "cardinality-estimate-after-analyze",
|
||||
"readiness": "OPEN",
|
||||
"source": ["final/document.md#query-plan-측정"],
|
||||
"known": "현재 통계에서 Plan B 의 추정 행 수는 실제의 1/8 이다",
|
||||
"unknown": "통계를 갱신하면 플래너가 같은 계획을 고르는지",
|
||||
"next-verification": "seed(1000) 뒤 ANALYZE highlights 를 돌리고 Plan B 를 다시 잰다",
|
||||
"decision-criterion": "추정치가 실제의 2배 안이면 닫고, 벗어나면 통계 갱신 주기를 정하는 Decision 으로 넘긴다",
|
||||
"relations": ["case:eager-to-one-n-plus-one"]
|
||||
}
|
||||
],
|
||||
"decision": [
|
||||
{
|
||||
"title": "Collection Fetch Join 과 Pagination 을 같이 쓰지 않는다",
|
||||
"kind": "decision",
|
||||
"slug": "no-collection-fetch-join-with-pagination",
|
||||
"readiness": "READY",
|
||||
"decision-status": "ADOPTED",
|
||||
"source": ["final/document.md#컬렉션-fetch-join-페이징"],
|
||||
"decision-evidence": ["case:eager-to-one-n-plus-one"],
|
||||
"grounds": "메모리 페이징으로 떨어지는 것을 실행계획에서 확인했다",
|
||||
"classification": "대안을 두고 프로젝트가 실제로 고른 방향이다",
|
||||
"relations": ["case:eager-to-one-n-plus-one"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"candidates": [
|
||||
{
|
||||
"id": "F012",
|
||||
"kindCandidate": "CASE",
|
||||
"sourceRefs": ["final/document.md#user-page-연관-숨은-추가-쿼리-정량화"],
|
||||
"summary": "필드 접근 없이 EAGER ToOne 이 추가 쿼리를 냈다",
|
||||
"disposition": "PROMOTE",
|
||||
"dispositionReview": "CONFIRMED",
|
||||
"target": "case:eager-to-one-n-plus-one",
|
||||
"reason": "재현·진단·결론이 한 사건 안에서 닫힌다"
|
||||
},
|
||||
{
|
||||
"id": "F013",
|
||||
"kindCandidate": "CASE",
|
||||
"sourceRefs": ["final/document.md#컬렉션-n1-정량화"],
|
||||
"summary": "같은 원인으로 컬렉션 쪽에서도 추가 쿼리가 났다",
|
||||
"disposition": "MERGE_INTO",
|
||||
"dispositionReview": "CONFIRMED",
|
||||
"target": "case:eager-to-one-n-plus-one",
|
||||
"reason": "같은 결함의 두 번째 증상이다. 그 Case 의 표에 행으로 들어간다"
|
||||
},
|
||||
{
|
||||
"id": "F014",
|
||||
"kindCandidate": "CONCEPT",
|
||||
"sourceRefs": ["final/document.md#분석-범위"],
|
||||
"summary": "이번 분석에서 읽은 리포지터리 메서드는 41개다",
|
||||
"disposition": "KEEP_IN_SSOT",
|
||||
"dispositionReview": "CONFIRMED",
|
||||
"target": null,
|
||||
"reason": "분석 범위 계수다. 분석에는 남아야 하고 공개 기록으로는 읽을 사람이 없다"
|
||||
}
|
||||
],
|
||||
"counts": { "topics": 1, "nodes": 5, "written": 0, "unwritten": 5, "unlisted": 0, "candidates": 3 },
|
||||
"unlisted": [],
|
||||
"history": {}
|
||||
}
|
||||
```
|
||||
|
||||
## 이 예시가 보여 주는 것
|
||||
|
||||
- 후보 셋 중 하나만 글감이 됐다. `MERGE_INTO` 와 `KEEP_IN_SSOT` 이 없는 분해는 선별하지 않은 분해다.
|
||||
- Concept 은 Case 를 먼저 고른 뒤에 그것을 읽는 데 필요해서 더했다.
|
||||
- Question 에 `decision-criterion` 이 있다. 무엇이 나오면 닫는지를 적지 않으면 검증을 마쳐도 열려 있다.
|
||||
- 다섯 종류를 억지로 채우지 않아도 된다. 여기서 다섯이 다 있는 것은 실제로 다섯이 있었기 때문이다.
|
||||
Reference in New Issue
Block a user