144 lines
4.6 KiB
JSON
Executable File
144 lines
4.6 KiB
JSON
Executable File
{
|
|
"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": "/home/donghyeon/workspace/ai-tool/topic-arrange/n+1liner/n+1liner.md",
|
|
"document_sha256": "1bb38964ca2a849690f5355646c1aa988111b4cd4e1055c6cb05cf7e5fc35cde",
|
|
"anchor": {
|
|
"kind": "marker",
|
|
"value": "nplus1-query-fanout",
|
|
"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": 341,
|
|
"end_line": 341
|
|
},
|
|
{
|
|
"start_line": 398,
|
|
"end_line": 398
|
|
}
|
|
],
|
|
"assumption": false
|
|
},
|
|
{
|
|
"id": "collection-initializations",
|
|
"label": "Highlight 컬렉션 초기화 N회",
|
|
"kind": "operation",
|
|
"role": "service",
|
|
"shape": "box",
|
|
"details": [
|
|
"collectionFetches = N"
|
|
],
|
|
"emphasis": "primary",
|
|
"description": "각 FeedItem의 지연 컬렉션 접근이 부모마다 한 번의 초기화를 만든 결과.",
|
|
"evidence": [
|
|
{
|
|
"start_line": 337,
|
|
"end_line": 337
|
|
},
|
|
{
|
|
"start_line": 378,
|
|
"end_line": 384
|
|
}
|
|
],
|
|
"assumption": false
|
|
},
|
|
{
|
|
"id": "highlight-selects",
|
|
"label": "Highlight SELECT N회",
|
|
"kind": "query",
|
|
"role": "sink",
|
|
"shape": "box",
|
|
"details": [
|
|
"부모별 자식 행 전부 조회"
|
|
],
|
|
"description": "배치와 서브셀렉트가 없는 기준선에서 컬렉션 초기화마다 실행되는 자식 SELECT.",
|
|
"evidence": [
|
|
{
|
|
"start_line": 337,
|
|
"end_line": 337
|
|
},
|
|
{
|
|
"start_line": 384,
|
|
"end_line": 385
|
|
}
|
|
],
|
|
"assumption": false
|
|
}
|
|
],
|
|
"edges": [
|
|
{
|
|
"id": "parents-trigger-initialization",
|
|
"from": "feed-request",
|
|
"to": "collection-initializations",
|
|
"label": "아이템마다 컬렉션 접근",
|
|
"kind": "request",
|
|
"style": "solid",
|
|
"emphasis": "primary",
|
|
"evidence": [
|
|
{
|
|
"start_line": 384,
|
|
"end_line": 384
|
|
}
|
|
],
|
|
"assumption": false
|
|
},
|
|
{
|
|
"id": "initialization-runs-select",
|
|
"from": "collection-initializations",
|
|
"to": "highlight-selects",
|
|
"label": "초기화마다 SELECT 1회",
|
|
"kind": "request",
|
|
"style": "solid",
|
|
"evidence": [
|
|
{
|
|
"start_line": 337,
|
|
"end_line": 337
|
|
},
|
|
{
|
|
"start_line": 384,
|
|
"end_line": 384
|
|
}
|
|
],
|
|
"assumption": false
|
|
}
|
|
],
|
|
"legend": [],
|
|
"metadata": {
|
|
"rationale": "정량 표는 본문에 남기고, 그림은 부모 수 N이 초기화와 SELECT 횟수 N을 만드는 단일 인과 관계에 집중한다."
|
|
}
|
|
}
|