{ "version": "1.1", "id": "nplus1-query-fanout", "title": "반환 부모 수 N이 컬렉션 초기화와 자식 SELECT 횟수를 결정한다", "question": "왜 한 번의 피드 요청에서 반환한 FeedItem 수 N이 Highlight 추가 조회 N회로 이어지는가?", "type": "data-flow", "direction": "LR", "audience": [ "JPA 기반 피드 조회의 N+1 원인을 진단하는 개발자" ], "summary": "한 페이지에서 N개의 FeedItem을 반환하면 각 부모의 Highlight 컬렉션을 한 번씩 초기화해 추가 SELECT도 N회 발생한다.", "alt": "FeedItem N개를 반환하는 loadFeed 요청이 컬렉션 초기화 N회와 Highlight SELECT N회로 이어지는 인과 흐름도.", "long_description": "왼쪽의 loadFeed 요청은 한 페이지에서 N개의 FeedItem을 반환한다. 매핑이 각 부모의 Highlight 컬렉션에 접근하면 컬렉션 초기화가 부모마다 한 번씩 일어나 총 N회가 된다. 현재 기준선에서는 배치나 서브셀렉트가 없어 초기화 한 번마다 Highlight SELECT도 한 번 실행되므로 추가 조회가 N회 발생한다. 각 SELECT는 해당 부모의 Highlight 자식 행을 전부 읽는다.", "source_context": { "document": "docs/n+1liner/final/document.md", "document_sha256": "f9e048a68db0ab82078955bf611b06a71a0033539e0c87ff0aa0d620a3126e36", "anchor": { "kind": "heading", "value": "6.2 실측 — 조회량이 N에 정확히 비례한다", "line": 380 } }, "composition": { "profile": "component-flow", "diagram_only": true, "reference_ids": [ "payment-event-flow" ], "rationale": "원문은 서로 다른 저장소로 분산되는 라우팅이 아니라 반환 부모 수가 컬렉션 초기화와 반복 SELECT를 차례로 유발하는 인과 경로를 설명하므로 component-flow가 가장 정확하다.", "focus_node": "collection-initializations" }, "groups": [], "nodes": [ { "id": "feed-request", "label": "loadFeed(0, N) → FeedItem N개", "kind": "request", "role": "source", "shape": "box", "details": [ "page size = 반환 부모 수 N" ], "description": "한 요청에서 반환한 부모 수 N이 N+1 증가 계수가 되는 피드 조회.", "evidence": [ { "start_line": 382, "end_line": 385 }, { "start_line": 455, "end_line": 456 } ], "assumption": false }, { "id": "collection-initializations", "label": "Highlight 컬렉션 초기화 N회", "kind": "operation", "role": "service", "shape": "box", "details": [ "collectionFetches = N" ], "emphasis": "primary", "description": "각 FeedItem의 지연 컬렉션 접근이 부모마다 한 번의 초기화를 만든 결과.", "evidence": [ { "start_line": 374, "end_line": 375 }, { "start_line": 431, "end_line": 431 }, { "start_line": 437, "end_line": 439 } ], "assumption": false }, { "id": "highlight-selects", "label": "Highlight SELECT N회", "kind": "query", "role": "sink", "shape": "box", "details": [ "부모별 자식 행 전부 조회" ], "description": "배치와 서브셀렉트가 없는 기준선에서 컬렉션 초기화마다 실행되는 자식 SELECT.", "evidence": [ { "start_line": 374, "end_line": 375 }, { "start_line": 437, "end_line": 442 } ], "assumption": false } ], "edges": [ { "id": "parents-trigger-initialization", "from": "feed-request", "to": "collection-initializations", "label": "컬렉션 접근", "kind": "request", "style": "solid", "emphasis": "primary", "evidence": [ { "start_line": 437, "end_line": 439 } ], "assumption": false }, { "id": "initialization-runs-select", "from": "collection-initializations", "to": "highlight-selects", "label": "SELECT 실행", "kind": "request", "style": "solid", "evidence": [ { "start_line": 374, "end_line": 375 }, { "start_line": 437, "end_line": 439 } ], "assumption": false } ], "legend": [], "metadata": { "rationale": "정량 표는 본문에 남기고, 그림은 부모 수 N이 초기화와 SELECT 횟수 N을 만드는 단일 인과 관계에 집중한다." } }