{ "schemaVersion": 2, "project": "n+1liner", "ssot": "final/document.md", "ssotSha256": "a32d9e8ba07129fc26deeef2ce07e3624ce6f34888d6cf503d0b7f226bbb4b6b", "generatedAt": "2026-09-04", "note": "글감 목록이다. file 이 있으면 이미 쓴 기록이고, 없으면 아직 쓰지 않은 글감이다. ssotSha256 이 지금 final/document.md 와 다르면 SSOT 가 바뀐 뒤 트리를 다시 보지 않은 것이다.", "readinessValues": [ "READY", "NEEDS_EVIDENCE", "BLOCKED", "REJECTED" ], "topics": { "jpa-feed-query-performance": { "topic": "jpa-feed-query-performance", "kinds": { "case": [ { "title": "Collection Fetch Join Pagination의 In-memory Paging", "slug": "collection-fetch-join-in-memory-paging", "file": "jpa-feed-query-performance/case/case-collection-fetch-join-in-memory-paging.md", "readiness": "READY", "status": "게시 전", "studioId": "c1158754-e3d2-47b8-bb41-81787c0ca84b", "assets": [ "in-memory-paging" ], "evidence": [ "../../../final/evidence/raw/explain/l4-entity-paging-limit.txt", "../../../final/evidence/raw/explain/l5-entity-paging-limit.txt" ], "relations": [ "Collection Fetch Join과 Pagination을 같이 사용하지 않는다", "Fetch Join으로 N+1을 해결하다 만난 MultiBag과 행 폭증", "Fetch Join · Batch · Projection 선택 기준" ] }, { "title": "Fetch 타입이 아니라 조회 방식이 만든 ToOne N+1", "slug": "eager-toone-nplus1-without-access", "file": "jpa-feed-query-performance/case/case-eager-toone-nplus1-without-access.md", "readiness": "READY", "status": "게시 전", "studioId": "32d0be7d-d88e-4760-8d91-35d3a233a99a", "assets": [ "eager-lazy-query-sequence" ], "evidence": [ "../../../final/evidence/raw/explain/highlights-child-plan-A.txt", "../../../final/evidence/raw/explain/toone-pages-plan.txt", "../../../final/evidence/raw/explain/toone-users-plan.txt" ], "relations": [ "Fetch Type과 Fetch Strategy 구분", "Projection 이후에도 1,509행을 읽은 Row Over-fetch", "JPA N+1 정량 진단 기준" ] }, { "title": "Fetch Join으로 N+1을 해결하다 만난 MultiBag과 행 폭증", "slug": "fetch-join-multibag-and-row-explosion", "file": "jpa-feed-query-performance/case/case-fetch-join-multibag-and-row-explosion.md", "readiness": "READY", "status": "게시 전", "studioId": "7ed75172-fd56-42bf-956a-8f9fc1cca235", "assets": [ "cartesian-row-multiplication" ], "evidence": [ "../../../final/evidence/raw/explain/l3-cartesian-join-plan.txt" ], "relations": [ "Fetch Join · Batch · Projection 선택 기준", "Fetch 타입이 아니라 조회 방식이 만든 ToOne N+1", "Collection Fetch Join Pagination의 In-memory Paging" ] }, { "title": "Projection 이후에도 1,509행을 읽은 Row Over-fetch", "slug": "projection-row-over-fetch", "file": "jpa-feed-query-performance/case/case-projection-row-over-fetch.md", "readiness": "READY", "status": "게시 전", "studioId": "4c9c3b90-bc89-4300-9334-088ea95d37d8", "assets": [ "projection-row-over-fetch" ], "evidence": [ "../../../final/evidence/raw/explain/l6-parent-projection.txt" ], "relations": [ "화면 조회는 Read Projection을 사용한다", "Top-N-per-group 선택 기준", "Fetch Join · Batch · Projection 선택 기준" ] }, { "title": "Visibility OR이 Keyset Index를 깨뜨린 문제", "slug": "visibility-or-breaks-keyset-index", "file": "jpa-feed-query-performance/case/case-visibility-or-breaks-keyset-index.md", "readiness": "READY", "status": "게시 전", "studioId": "e6715e81-6dbd-4287-8e19-946c334f38fb", "assets": [ "keyset-vs-offset" ], "evidence": [ "../../../final/evidence/raw/explain/l15-keyset-no-index.txt", "../../../final/evidence/raw/explain/l15-offset-deep-page.txt" ], "relations": [ "Feed Visibility Query Pattern", "Keyset Pagination 설계 기준", "feed_visible을 Production CQRS로 승격할 것인가" ] } ], "concept": [], "reference": [ { "title": "Feed Visibility Query Pattern", "slug": "feed-visibility-query-pattern", "file": "jpa-feed-query-performance/reference/reference-feed-visibility-query-pattern.md", "readiness": "READY", "status": "게시 전", "studioId": "635fcedd-d402-4297-bcf3-9fcdf4200d28", "assets": [], "evidence": [], "relations": [ "Visibility OR이 Keyset Index를 깨뜨린 문제", "feed_visible을 Production CQRS로 승격할 것인가", "Keyset Pagination 설계 기준" ] }, { "title": "Fetch Join · Batch · Projection 선택 기준", "slug": "fetch-strategy-selection", "file": "jpa-feed-query-performance/reference/reference-fetch-strategy-selection.md", "readiness": "READY", "status": "게시 전", "studioId": "db99cbc5-9123-4599-b368-39ff3170e81d", "assets": [], "evidence": [], "relations": [ "Fetch Join으로 N+1을 해결하다 만난 MultiBag과 행 폭증", "Collection Fetch Join Pagination의 In-memory Paging", "Projection 이후에도 1,509행을 읽은 Row Over-fetch" ] }, { "title": "Fetch Type과 Fetch Strategy 구분", "slug": "fetch-type-vs-fetch-strategy", "file": "jpa-feed-query-performance/reference/reference-fetch-type-vs-fetch-strategy.md", "readiness": "READY", "status": "게시 전", "studioId": "51095f6e-2cc8-439c-8648-065033614215", "assets": [], "evidence": [], "relations": [ "필드 접근 없이 발생한 EAGER ToOne N+1", "Fetch Join · Batch · Projection 선택 기준", "JPA N+1 정량 진단 기준" ] }, { "title": "Keyset Pagination 설계 기준", "slug": "keyset-pagination-design", "file": "jpa-feed-query-performance/reference/reference-keyset-pagination-design.md", "readiness": "READY", "status": "게시 전", "studioId": "06788903-3dfa-4f70-b159-f1224384fd0b", "assets": [], "evidence": [], "relations": [ "Visibility OR이 Keyset Index를 깨뜨린 문제", "Feed Pagination은 Keyset을 사용한다", "PostgreSQL Query Plan 측정 기준" ] }, { "title": "JPA N+1 정량 진단 기준", "slug": "nplus1-quantitative-diagnosis", "file": "jpa-feed-query-performance/reference/reference-nplus1-quantitative-diagnosis.md", "readiness": "READY", "status": "게시 전", "studioId": "b0b55ac9-c0a3-4c01-ba84-0aa478923ace", "assets": [], "evidence": [], "relations": [ "Fetch 타입이 아니라 조회 방식이 만든 ToOne N+1", "PostgreSQL Query Plan 측정 기준" ] }, { "title": "PostgreSQL Query Plan 측정 기준", "slug": "postgresql-query-plan-measurement", "file": "jpa-feed-query-performance/reference/reference-postgresql-query-plan-measurement.md", "readiness": "READY", "status": "게시 전", "studioId": "e8c2e9ea-cd87-46f8-9469-849dbd433d86", "assets": [], "evidence": [], "relations": [ "Query Plan은 실제 PostgreSQL에서 측정한다", "JPA N+1 정량 진단 기준", "ANALYZE 이후 Cardinality Estimate는 어떻게 달라지는가" ] }, { "title": "Top-N-per-group 선택 기준", "slug": "top-n-per-group-selection", "file": "jpa-feed-query-performance/reference/reference-top-n-per-group-selection.md", "readiness": "READY", "status": "게시 전", "studioId": "bf5f2462-0e94-4723-bdc8-f7dd709b2dbb", "assets": [], "evidence": [], "relations": [ "Projection 이후에도 1,509행을 읽은 Row Over-fetch", "PostgreSQL Query Plan 측정 기준", "Fetch Join · Batch · Projection 선택 기준" ] } ], "question": [ { "title": "ANALYZE 이후 Cardinality Estimate는 어떻게 달라지는가", "slug": "cardinality-estimate-after-analyze", "file": "jpa-feed-query-performance/question/question-cardinality-estimate-after-analyze.md", "readiness": "READY", "status": "게시 전", "studioId": "e1e0e2a0-c6b6-45bf-be42-f697ba5e2fff", "assets": [], "evidence": [], "relations": [ "PostgreSQL Query Plan 측정 기준", "Fetch 타입이 아니라 조회 방식이 만든 ToOne N+1" ] }, { "title": "실제 동시 트래픽에서도 이 구조가 안정적인가", "slug": "concurrency-stability", "file": "jpa-feed-query-performance/question/question-concurrency-stability.md", "readiness": "READY", "status": "게시 전", "studioId": "6cbe963f-86f8-4df6-be5a-900712970d01", "assets": [], "evidence": [], "relations": [ "PostgreSQL Query Plan 측정 기준", "feed_visible을 Production CQRS로 승격할 것인가" ] }, { "title": "Round Trip과 Row Volume을 독립 측정할 것인가", "slug": "isolate-round-trip-and-row-volume", "file": "jpa-feed-query-performance/question/question-isolate-round-trip-and-row-volume.md", "readiness": "READY", "status": "게시 전", "studioId": "5159c415-232d-424a-970a-b0db52746767", "assets": [], "evidence": [], "relations": [ "JPA N+1 정량 진단 기준", "Fetch 타입이 아니라 조회 방식이 만든 ToOne N+1" ] }, { "title": "Highlight 없는 FeedItem을 허용할 것인가", "slug": "nullable-first-highlighted-at", "file": "jpa-feed-query-performance/question/question-nullable-first-highlighted-at.md", "readiness": "READY", "status": "게시 전", "studioId": "b099ca65-bf9f-4d61-814c-74722453fa3c", "assets": [], "evidence": [], "relations": [ "Keyset Pagination 설계 기준", "Visibility OR이 Keyset Index를 깨뜨린 문제" ] }, { "title": "feed_visible을 Production CQRS로 승격할 것인가", "slug": "promote-feed-visible-to-cqrs", "file": "jpa-feed-query-performance/question/question-promote-feed-visible-to-cqrs.md", "readiness": "READY", "status": "게시 전", "studioId": "5088ce14-b096-41d3-abba-64b7afb48bb9", "assets": [], "evidence": [], "relations": [ "Feed Visibility Query Pattern", "현재 Read Model은 CQRS-lite로 유지한다", "Visibility OR이 Keyset Index를 깨뜨린 문제" ] } ], "decision": [ { "title": "Entity Graph 조회에는 Batch Fetch를 사용한다", "slug": "batch-fetch-for-entity-graph", "file": "jpa-feed-query-performance/decision/decision-batch-fetch-for-entity-graph.md", "readiness": "READY", "status": "게시 전", "studioId": "08a74b35-10c3-4874-8fbc-209b0b6e942e", "assets": [], "evidence": [], "relations": [ "Fetch Join · Batch · Projection 선택 기준", "Collection Fetch Join Pagination의 In-memory Paging", "Projection 이후에도 1,509행을 읽은 Row Over-fetch" ] }, { "title": "현재 Read Model은 CQRS-lite로 유지한다", "slug": "keep-read-model-as-cqrs-lite", "file": "jpa-feed-query-performance/decision/decision-keep-read-model-as-cqrs-lite.md", "readiness": "READY", "status": "게시 전", "studioId": "7f248f68-ce2b-43ec-94ce-82324d0bd1a7", "assets": [], "evidence": [], "relations": [ "feed_visible을 Production CQRS로 승격할 것인가", "화면 조회는 Read Projection을 사용한다", "Feed Visibility Query Pattern" ] }, { "title": "Feed Pagination은 Keyset을 사용한다", "slug": "keyset-for-feed-pagination", "file": "jpa-feed-query-performance/decision/decision-keyset-for-feed-pagination.md", "readiness": "READY", "status": "게시 전", "studioId": "1dbce381-f0dc-4d49-ad68-bd31d205677e", "assets": [], "evidence": [], "relations": [ "Keyset Pagination 설계 기준", "Visibility OR이 Keyset Index를 깨뜨린 문제", "Highlight 없는 FeedItem을 허용할 것인가" ] }, { "title": "Query Plan은 실제 PostgreSQL에서 측정한다", "slug": "measure-plan-on-real-postgresql", "file": "jpa-feed-query-performance/decision/decision-measure-plan-on-real-postgresql.md", "readiness": "READY", "status": "게시 전", "studioId": "ae6c9bea-d3a3-46e1-bbd4-8d580d336394", "assets": [], "evidence": [], "relations": [ "PostgreSQL Query Plan 측정 기준", "Fetch 타입이 아니라 조회 방식이 만든 ToOne N+1", "Visibility OR이 Keyset Index를 깨뜨린 문제" ] }, { "title": "Collection Fetch Join과 Pagination을 같이 사용하지 않는다", "slug": "no-collection-fetch-join-with-pagination", "file": "jpa-feed-query-performance/decision/decision-no-collection-fetch-join-with-pagination.md", "readiness": "READY", "status": "게시 전", "studioId": "5e4d033c-d6fe-4257-a4dc-1ade44473c72", "assets": [], "evidence": [], "relations": [ "Collection Fetch Join Pagination의 In-memory Paging", "Fetch Join으로 N+1을 해결하다 만난 MultiBag과 행 폭증", "Fetch Join · Batch · Projection 선택 기준" ] }, { "title": "Query Strategy는 FeedQueryPort 뒤에서 소유한다", "slug": "query-strategy-behind-port", "file": "jpa-feed-query-performance/decision/decision-query-strategy-behind-port.md", "readiness": "READY", "status": "게시 전", "studioId": "4e3200c8-eff5-4442-ae84-ae7b7fa92c8b", "assets": [], "evidence": [], "relations": [ "Fetch Join · Batch · Projection 선택 기준", "Collection Fetch Join Pagination의 In-memory Paging", "화면 조회는 Read Projection을 사용한다" ] }, { "title": "화면 조회는 Read Projection을 사용한다", "slug": "read-projection-for-screen-query", "file": "jpa-feed-query-performance/decision/decision-read-projection-for-screen-query.md", "readiness": "READY", "status": "게시 전", "studioId": "30a37f34-b406-4061-b924-e22e0be0c3bf", "assets": [], "evidence": [], "relations": [ "Projection 이후에도 1,509행을 읽은 Row Over-fetch", "Fetch Join · Batch · Projection 선택 기준", "Query Strategy는 FeedQueryPort 뒤에서 소유한다" ] } ] } } } }