{ "version": "1.1", "id": "strategy-journey", "title": "두 형제 문제는 Fetch Join에서 합류한 뒤 단계별 해법으로 최종 구조가 된다", "question": "과제 요구사항에서 시작해 기준선의 N1·N2 분기와 실패·개선을 거쳐 최종 피드 조회 구조는 어떻게 발전하는가?", "type": "flow", "direction": "LR", "audience": [ "백엔드 엔지니어", "성능 개선 과정을 검토하는 독자" ], "summary": "요구사항과 모델을 거친 기준선에서 N1·N2가 동시에 갈라져 Fetch Join으로 합류하고, 실패 뒤 Batch Fetch·DTO Projection·Top-3·Keyset·가시성 인덱싱을 차례로 거쳐 최종 피드 조회 구조에 도달한다.", "alt": "요구사항과 모델에서 기준선으로 진행한 뒤 N1과 N2로 분기하고 Fetch Join에서 합류해, 실패와 다섯 개선 단계를 거쳐 최종 피드 조회 구조에 이르는 흐름도.", "long_description": "왼쪽에서 과제 요구사항, 도메인·데이터 모델, 최초 피드 조회 기준선 순으로 시작한다. 기준선에서 컬렉션 N+1(N1)과 User·Page 연관의 숨은 쿼리(N2)가 서로 앞뒤가 아닌 형제 문제로 동시에 갈라지고, 두 경로는 Fetch Join 시도에서 합류한다. 이 시도는 다중 컬렉션·페이징 실패로 이어진다. 마지막 노드는 Batch Fetch, DTO Projection, 아이템별 Top-3, Keyset Pagination, 가시성 조건 인덱싱을 거쳐 최종 피드 조회 구조에 도달하는 순서를 담는다.", "source_context": { "document": "/home/donghyeon/workspace/ai-tool/topic-arrange/n+1liner/n+1liner.md", "document_sha256": "1bb38964ca2a849690f5355646c1aa988111b4cd4e1055c6cb05cf7e5fc35cde", "anchor": { "kind": "marker", "value": "strategy-journey", "line": 27 } }, "composition": { "profile": "component-flow", "diagram_only": true, "reference_ids": [ "payment-event-flow" ], "rationale": "요구사항에서 최종 조회 구조까지 한 방향으로 발전하면서 기준선의 두 형제 문제가 분기했다가 Fetch Join 시도에서 합류하므로, 좌측 출발점·중앙의 분기와 합류·우측 종착점을 갖는 component-flow가 전체 여정을 가장 직접적으로 드러낸다.", "focus_node": "fetch-join-failure" }, "groups": [], "nodes": [ { "id": "requirements-model-baseline", "label": "요구사항·모델·기준선", "kind": "journey-stage", "role": "source", "description": "전체 여정은 과제 요구사항에서 도메인·데이터 모델을 거쳐 최초 피드 조회 기준선으로 시작한다.", "details": [ "1 과제 요구사항", "2 도메인·데이터 모델", "3 최초 피드 조회" ], "evidence": [ { "start_line": 25, "end_line": 25 } ], "assumption": false }, { "id": "collection-n-plus-one", "label": "컬렉션 N+1 (N1)", "kind": "problem", "role": "service", "description": "기준선에서 N2와 동시에 나타나는 컬렉션 조회 문제다.", "details": [ "유형: 컬렉션 N+1", "발생: 기준선과 동시에", "관계: N2와 형제 문제", "진행: Fetch Join으로 합류" ], "evidence": [ { "start_line": 25, "end_line": 25 } ], "assumption": false }, { "id": "hidden-to-one-queries", "label": "User·Page 숨은 쿼리 (N2)", "kind": "problem", "role": "service", "description": "기준선에서 N1과 동시에 나타나는 User·Page 연관의 숨은 쿼리 문제다.", "details": [ "유형: User·Page 숨은 쿼리", "발생: 기준선과 동시에", "관계: N1과 형제 문제", "진행: Fetch Join으로 합류" ], "evidence": [ { "start_line": 25, "end_line": 25 } ], "assumption": false }, { "id": "fetch-join-failure", "label": "Fetch Join → 실패", "kind": "query-strategy", "role": "service", "description": "N1과 N2가 Fetch Join 시도에서 합류한 뒤 다중 컬렉션·페이징 실패로 이어진다.", "details": [ "1 N1·N2 합류", "2 다중 컬렉션·페이징 실패" ], "emphasis": "warning", "evidence": [ { "start_line": 25, "end_line": 25 } ], "assumption": false }, { "id": "improvement-chain", "label": "후속 개선 → 최종 구조", "kind": "query-strategy", "role": "sink", "description": "실패 뒤 다섯 조회 전략 단계가 명시된 순서로 발전해 최종 피드 조회 구조를 만든다.", "details": [ "1 Batch Fetch", "2 DTO Projection", "3 아이템별 Top-3", "4 Keyset Pagination", "5 가시성 조건 인덱싱", "6 최종 피드 조회 구조" ], "emphasis": "primary", "evidence": [ { "start_line": 25, "end_line": 25 } ], "assumption": false } ], "edges": [ { "id": "baseline-reveals-n1", "from": "requirements-model-baseline", "to": "collection-n-plus-one", "label": "기준선에서 갈라짐", "kind": "problem", "evidence": [ { "start_line": 25, "end_line": 25 } ], "assumption": false }, { "id": "baseline-reveals-n2", "from": "requirements-model-baseline", "to": "hidden-to-one-queries", "label": "기준선에서 갈라짐", "kind": "problem", "evidence": [ { "start_line": 25, "end_line": 25 } ], "assumption": false }, { "id": "n1-joins-fetch-join", "from": "collection-n-plus-one", "to": "fetch-join-failure", "label": "Fetch Join으로 합류", "kind": "flow", "evidence": [ { "start_line": 25, "end_line": 25 } ], "assumption": false }, { "id": "n2-joins-fetch-join", "from": "hidden-to-one-queries", "to": "fetch-join-failure", "label": "Fetch Join으로 합류", "kind": "flow", "evidence": [ { "start_line": 25, "end_line": 25 } ], "assumption": false }, { "id": "failure-to-improvements", "from": "fetch-join-failure", "to": "improvement-chain", "label": "실패 뒤 단계별 개선", "kind": "improvement", "evidence": [ { "start_line": 25, "end_line": 25 } ], "assumption": false } ], "legend": [], "metadata": { "rationale": "line 25의 전체 순서를 유지하면서 문서 폭에 맞추기 위해 연속된 출발 세 단계, Fetch Join과 그 실패, 실패 뒤 다섯 개선 단계를 각각 하나의 단계 노드 안에 번호로 묶었다." } }