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
@@ -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