{ "version": "1.1", "title": "배치는 부모를 먼저 페이징하고 자식을 IN으로 따로 가져온다", "question": "배치 페치는 어떤 순서로 부모 페이징과 자식 조회를 나누는가?", "type": "sequence", "direction": "TB", "audience": [ "컬렉션 N+1과 페이징을 함께 다루는 백엔드 개발자" ], "summary": "부모만 LIMIT으로 페이징한 뒤 초기화되지 않은 프록시를 모아 자식을 IN으로 조회한다.", "alt": "조회 코드, Hibernate 세션, feed_items, highlights 네 참여자 사이에서 부모 페이징이 먼저 일어나고 그다음 자식 IN 배치 조회가 일어나는 순서도.", "long_description": "조회 코드가 부모 페이지를 요청하면 세션이 feed_items에 부모만 LIMIT으로 조회한다. 돌아온 부모에는 초기화되지 않은 컬렉션 프록시가 붙어 있다. 세션은 그 프록시를 batch fetch size만큼 모아 highlights를 WHERE fk IN 으로 조회하고, 받은 자식 행으로 컬렉션을 채워 조회 코드에 돌려준다.", "composition": { "profile": "sequence", "diagram_only": true, "reference_ids": [ "payment-approval-sequence" ], "rationale": "부모 페이징이 자식 조회보다 먼저 일어난다는 순서 자체가 배치가 페이징을 지키는 이유이므로, 정적 구성도가 아니라 시간 순서로 그린다." }, "groups": [], "nodes": [ { "id": "loader", "label": "조회 코드", "kind": "actor", "role": "participant", "shape": "actor", "description": "페이지를 요청하는 애플리케이션 코드", "evidence": [ { "start_line": 1009, "end_line": 1010 } ], "assumption": false }, { "id": "session", "label": "Hibernate 세션", "kind": "service", "role": "participant", "shape": "box", "description": "default_batch_fetch_size로 프록시를 모으는 주체", "evidence": [ { "start_line": 1010, "end_line": 1012 } ], "assumption": false }, { "id": "feed-items", "label": "feed_items", "kind": "store", "role": "participant", "shape": "cylinder", "description": "부모 테이블", "evidence": [ { "start_line": 1009, "end_line": 1010 } ], "assumption": false }, { "id": "highlights", "label": "highlights", "kind": "store", "role": "participant", "shape": "cylinder", "description": "자식 테이블", "evidence": [ { "start_line": 1010, "end_line": 1011 } ], "assumption": false } ], "edges": [ { "id": "m1", "from": "loader", "to": "session", "label": "부모 페이지 요청", "kind": "request", "style": "solid", "order": 1, "evidence": [ { "start_line": 1009, "end_line": 1010 } ], "assumption": false }, { "id": "m2", "from": "session", "to": "feed-items", "label": "부모 LIMIT 페이징", "kind": "query", "style": "solid", "order": 2, "evidence": [ { "start_line": 1009, "end_line": 1010 } ], "assumption": false }, { "id": "m3", "from": "feed-items", "to": "session", "label": "페이지 부모 + 미초기화 프록시", "kind": "response", "style": "dashed", "order": 3, "evidence": [ { "start_line": 1010, "end_line": 1012 } ], "assumption": false }, { "id": "m4", "from": "session", "to": "highlights", "label": "WHERE fk IN (?,…) 배치", "kind": "query", "style": "solid", "order": 4, "evidence": [ { "start_line": 1010, "end_line": 1012 } ], "assumption": false }, { "id": "m5", "from": "highlights", "to": "session", "label": "자식 행", "kind": "response", "style": "dashed", "order": 5, "evidence": [ { "start_line": 1010, "end_line": 1011 } ], "assumption": false }, { "id": "m6", "from": "session", "to": "loader", "label": "부모 + 컬렉션", "kind": "response", "style": "dashed", "order": 6, "evidence": [ { "start_line": 1012, "end_line": 1014 } ], "assumption": false } ], "legend": [], "metadata": { "rationale": "fetch join이 한 결과에 합쳐 LIMIT을 막았던 것과 달리 배치는 두 번에 나눈다. 그 나눔이 순서로만 드러나므로 sequence를 쓴다." }, "source_context": { "document": "docs/n+1liner/final/document.md", "document_sha256": "385ca44db5bf763cb1ff28a67d531d68876402b35d21dc54f2bc702e8bda0053", "anchor": { "kind": "heading", "value": "11.5 배치가 N+1과 페이징을 함께 해결하는 이유", "line": 1007 } }, "id": "batch-fetch-in-clause" }