feat: 가상화 문서들 추가
@@ -0,0 +1,22 @@
|
||||
# 기준선 스키마의 관계
|
||||
|
||||
## Alternative text
|
||||
|
||||
users와 pages에서 feed_items로 모이고 highlights로 이어지는 기준선 관계도.
|
||||
|
||||
## Long description
|
||||
|
||||
왼쪽의 users와 pages가 각각 중앙의 feed_items에 연결된다. feed_items는 오른쪽의 highlights로 이어진다. 간선은 user와 page 각각에 여러 feed_item이 연결되고, 한 feed_item에 여러 highlight가 연결되는 관계를 나타낸다.
|
||||
|
||||
## Elements and evidence
|
||||
|
||||
- **users** (entity): No additional description. Evidence: L46–L46.
|
||||
- **pages** (entity): No additional description. Evidence: L47–L47.
|
||||
- **feed_items** (entity): No additional description. Evidence: L46–L46.
|
||||
- **highlights** (entity): No additional description. Evidence: L48–L48.
|
||||
|
||||
## Relationships
|
||||
|
||||
- **feed_items → highlights:** 1:N. Evidence: L48–L48.
|
||||
- **pages → feed_items:** 1:N. Evidence: L47–L47.
|
||||
- **users → feed_items:** 1:N. Evidence: L46–L46.
|
||||
@@ -0,0 +1,18 @@
|
||||
# 기준선 스키마의 관계
|
||||
# Question: 현재 기준선에서 users, pages, feed_items, highlights는 어떻게 연결되는가?
|
||||
direction: right
|
||||
n0: "users" {
|
||||
shape: rectangle
|
||||
}
|
||||
n1: "pages" {
|
||||
shape: rectangle
|
||||
}
|
||||
n2: "feed_items" {
|
||||
shape: rectangle
|
||||
}
|
||||
n3: "highlights" {
|
||||
shape: rectangle
|
||||
}
|
||||
n0 -> n2: "1:N"
|
||||
n1 -> n2: "1:N"
|
||||
n2 -> n3: "1:N"
|
||||
@@ -0,0 +1,12 @@
|
||||
digraph techviz {
|
||||
graph [rankdir=LR, splines=ortho, nodesep=0.55, ranksep=0.85];
|
||||
node [fontname=Helvetica, fontsize=11, margin="0.18,0.12", style="rounded,filled", fillcolor=white, color="#2d4357", penwidth=1.5];
|
||||
edge [fontname=Helvetica, fontsize=10, color="#364b5f", penwidth=1.4, arrowsize=0.75];
|
||||
n0 [label="users", shape=box, style="rounded,filled"];
|
||||
n1 [label="pages", shape=box, style="rounded,filled"];
|
||||
n2 [label="feed_items", shape=box, style="rounded,filled"];
|
||||
n3 [label="highlights", shape=box, style="rounded,filled"];
|
||||
n0 -> n2 [label="1:N", style=solid];
|
||||
n1 -> n2 [label="1:N", style=solid];
|
||||
n2 -> n3 [label="1:N", style=solid];
|
||||
}
|
||||
@@ -5,29 +5,29 @@
|
||||
<root>
|
||||
<mxCell id="0"/>
|
||||
<mxCell id="1" parent="0"/>
|
||||
<mxCell id="n_users" value="users" tooltip="entity | Evidence: L35-L35" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxCell id="n_users" value="users" tooltip="entity | Evidence: L46-L46" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="70.0" y="60.0" width="150.0" height="64.0" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="n_pages" value="pages" tooltip="entity | Evidence: L36-L36" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxCell id="n_pages" value="pages" tooltip="entity | Evidence: L47-L47" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="70.0" y="196.0" width="150.0" height="64.0" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="n_feed-items" value="feed_items" tooltip="entity | Evidence: L35-L35" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxCell id="n_feed-items" value="feed_items" tooltip="entity | Evidence: L46-L46" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="380.0" y="128.0" width="150.0" height="64.0" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="n_highlights" value="highlights" tooltip="entity | Evidence: L37-L37" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxCell id="n_highlights" value="highlights" tooltip="entity | Evidence: L48-L48" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="690.0" y="128.0" width="150.0" height="64.0" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="e_feed-items-have-highlights" value="여러 highlights를 가짐" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_feed-items" target="n_highlights">
|
||||
<mxCell id="e_feed-items-have-highlights" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_feed-items" target="n_highlights">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="610.0" y="132.0" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="e_pages-have-feed-items" value="여러 feed_item이 딸림" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_pages" target="n_feed-items">
|
||||
<mxCell id="e_pages-have-feed-items" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_pages" target="n_feed-items">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="324.0" y="198.5" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="e_users-have-feed-items" value="여러 feed_item을 가짐" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_users" target="n_feed-items">
|
||||
<mxCell id="e_users-have-feed-items" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_users" target="n_feed-items">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="324.0" y="121.5" as="offset"/>
|
||||
</mxGeometry>
|
||||
|
||||
@@ -0,0 +1,586 @@
|
||||
{
|
||||
"type": "excalidraw",
|
||||
"version": 2,
|
||||
"source": "techviz-harness",
|
||||
"elements": [
|
||||
{
|
||||
"id": "edge-feed-items-have-highlights",
|
||||
"type": "arrow",
|
||||
"x": 530.0,
|
||||
"y": 160.0,
|
||||
"width": 160.0,
|
||||
"height": 0.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": null,
|
||||
"seed": 1670845141,
|
||||
"version": 1,
|
||||
"versionNonce": 1130181305,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
80.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
80.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
160.0,
|
||||
0.0
|
||||
]
|
||||
],
|
||||
"lastCommittedPoint": null,
|
||||
"startBinding": {
|
||||
"elementId": "node-feed-items",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"endBinding": {
|
||||
"elementId": "node-highlights",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow",
|
||||
"elbowed": true
|
||||
},
|
||||
{
|
||||
"id": "edge-label-feed-items-have-highlights",
|
||||
"type": "text",
|
||||
"x": 565.0,
|
||||
"y": 120.0,
|
||||
"width": 90,
|
||||
"height": 24,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1739141275,
|
||||
"version": 1,
|
||||
"versionNonce": 1481418449,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 13,
|
||||
"fontFamily": 5,
|
||||
"text": "1:N",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "1:N",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "edge-pages-have-feed-items",
|
||||
"type": "arrow",
|
||||
"x": 220.0,
|
||||
"y": 169.0,
|
||||
"width": 160.0,
|
||||
"height": 59.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": null,
|
||||
"seed": 1351293053,
|
||||
"version": 1,
|
||||
"versionNonce": 766527047,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[
|
||||
0.0,
|
||||
59.0
|
||||
],
|
||||
[
|
||||
80.0,
|
||||
59.0
|
||||
],
|
||||
[
|
||||
80.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
160.0,
|
||||
0.0
|
||||
]
|
||||
],
|
||||
"lastCommittedPoint": null,
|
||||
"startBinding": {
|
||||
"elementId": "node-pages",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"endBinding": {
|
||||
"elementId": "node-feed-items",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow",
|
||||
"elbowed": true
|
||||
},
|
||||
{
|
||||
"id": "edge-label-pages-have-feed-items",
|
||||
"type": "text",
|
||||
"x": 279.0,
|
||||
"y": 186.5,
|
||||
"width": 90,
|
||||
"height": 24,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 453319515,
|
||||
"version": 1,
|
||||
"versionNonce": 1432469046,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 13,
|
||||
"fontFamily": 5,
|
||||
"text": "1:N",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "1:N",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "edge-users-have-feed-items",
|
||||
"type": "arrow",
|
||||
"x": 220.0,
|
||||
"y": 92.0,
|
||||
"width": 160.0,
|
||||
"height": 59.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": null,
|
||||
"seed": 568432588,
|
||||
"version": 1,
|
||||
"versionNonce": 1091126773,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
80.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
80.0,
|
||||
59.0
|
||||
],
|
||||
[
|
||||
160.0,
|
||||
59.0
|
||||
]
|
||||
],
|
||||
"lastCommittedPoint": null,
|
||||
"startBinding": {
|
||||
"elementId": "node-users",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"endBinding": {
|
||||
"elementId": "node-feed-items",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow",
|
||||
"elbowed": true
|
||||
},
|
||||
{
|
||||
"id": "edge-label-users-have-feed-items",
|
||||
"type": "text",
|
||||
"x": 279.0,
|
||||
"y": 109.5,
|
||||
"width": 90,
|
||||
"height": 24,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 776017595,
|
||||
"version": 1,
|
||||
"versionNonce": 515332551,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 13,
|
||||
"fontFamily": 5,
|
||||
"text": "1:N",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "1:N",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "node-users",
|
||||
"type": "rectangle",
|
||||
"x": 70.0,
|
||||
"y": 60.0,
|
||||
"width": 150.0,
|
||||
"height": 64.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#ffffff",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1127380143,
|
||||
"version": 1,
|
||||
"versionNonce": 471339421,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "node-label-users",
|
||||
"type": "text",
|
||||
"x": 80.0,
|
||||
"y": 70.0,
|
||||
"width": 130.0,
|
||||
"height": 44.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 154326264,
|
||||
"version": 1,
|
||||
"versionNonce": 531211316,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 15,
|
||||
"fontFamily": 5,
|
||||
"text": "users",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "users",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "node-pages",
|
||||
"type": "rectangle",
|
||||
"x": 70.0,
|
||||
"y": 196.0,
|
||||
"width": 150.0,
|
||||
"height": 64.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#ffffff",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1932513815,
|
||||
"version": 1,
|
||||
"versionNonce": 1597034816,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "node-label-pages",
|
||||
"type": "text",
|
||||
"x": 80.0,
|
||||
"y": 206.0,
|
||||
"width": 130.0,
|
||||
"height": 44.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1992702022,
|
||||
"version": 1,
|
||||
"versionNonce": 1805895229,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 15,
|
||||
"fontFamily": 5,
|
||||
"text": "pages",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "pages",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "node-feed-items",
|
||||
"type": "rectangle",
|
||||
"x": 380.0,
|
||||
"y": 128.0,
|
||||
"width": 150.0,
|
||||
"height": 64.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#ffffff",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1553680117,
|
||||
"version": 1,
|
||||
"versionNonce": 1528008093,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "node-label-feed-items",
|
||||
"type": "text",
|
||||
"x": 390.0,
|
||||
"y": 138.0,
|
||||
"width": 130.0,
|
||||
"height": 44.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1561637253,
|
||||
"version": 1,
|
||||
"versionNonce": 189981097,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 15,
|
||||
"fontFamily": 5,
|
||||
"text": "feed_items",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "feed_items",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "node-highlights",
|
||||
"type": "rectangle",
|
||||
"x": 690.0,
|
||||
"y": 128.0,
|
||||
"width": 150.0,
|
||||
"height": 64.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#ffffff",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 281310605,
|
||||
"version": 1,
|
||||
"versionNonce": 933280984,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "node-label-highlights",
|
||||
"type": "text",
|
||||
"x": 700.0,
|
||||
"y": 138.0,
|
||||
"width": 130.0,
|
||||
"height": 44.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1591294741,
|
||||
"version": 1,
|
||||
"versionNonce": 457414640,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 15,
|
||||
"fontFamily": 5,
|
||||
"text": "highlights",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "highlights",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
}
|
||||
],
|
||||
"appState": {
|
||||
"gridSize": 10,
|
||||
"viewBackgroundColor": "#ffffff",
|
||||
"currentItemFontFamily": 5
|
||||
},
|
||||
"files": {}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"harness_version": "0.2.0",
|
||||
"spec_id": "baseline-schema",
|
||||
"spec_version": "1.1",
|
||||
"spec_sha256": "e57815c079fe775793a34a0a4e404941638781e25501b684c2b93853ad7b18ce",
|
||||
"source_context": {
|
||||
"document": "docs/n+1liner/final/document.md",
|
||||
"document_sha256": "f9e048a68db0ab82078955bf611b06a71a0033539e0c87ff0aa0d620a3126e36",
|
||||
"anchor": {
|
||||
"kind": "heading",
|
||||
"value": "3.1 관계와 스키마",
|
||||
"line": 44
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
"baseline-schema.svg",
|
||||
"baseline-schema.drawio",
|
||||
"baseline-schema.mmd",
|
||||
"baseline-schema.d2",
|
||||
"baseline-schema.dot",
|
||||
"baseline-schema.excalidraw",
|
||||
"baseline-schema.alt.md"
|
||||
],
|
||||
"lint_issue_count": 0,
|
||||
"assumption_count": 0,
|
||||
"assumptions_allowed": false,
|
||||
"composition_profile": "component-flow",
|
||||
"reference_ids": [
|
||||
"payment-event-flow"
|
||||
],
|
||||
"diagram_only": true
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
%% 기준선 스키마의 관계
|
||||
%% question: 현재 기준선에서 users, pages, feed_items, highlights는 어떻게 연결되는가?
|
||||
flowchart LR
|
||||
n0["users"]
|
||||
n1["pages"]
|
||||
n2["feed_items"]
|
||||
n3["highlights"]
|
||||
n0 -->|"1:N"| n2
|
||||
n1 -->|"1:N"| n2
|
||||
n2 -->|"1:N"| n3
|
||||
@@ -2,7 +2,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="885" height="305" viewBox="0 0 885 305" role="img" aria-labelledby="diagram-title diagram-description">
|
||||
<title id="diagram-title">기준선 스키마의 관계</title>
|
||||
<desc id="diagram-description">왼쪽의 users와 pages가 각각 중앙의 feed_items에 연결된다. feed_items는 오른쪽의 highlights로 이어진다. 간선은 user와 page 각각에 여러 feed_item이 연결되고, 한 feed_item에 여러 highlight가 연결되는 관계를 나타낸다.</desc>
|
||||
<metadata>{"techviz":{"spec_version":"1.1","id":"baseline-schema","profile":"component-flow"},"source_context":{"document":"/home/donghyeon/workspace/ai-tool/topic-arrange/n+1liner/n+1liner.md","document_sha256":"1bb38964ca2a849690f5355646c1aa988111b4cd4e1055c6cb05cf7e5fc35cde","anchor":{"kind":"marker","value":"baseline-schema","line":39}},"evidence_policy":"Each factual element cites source lines or is marked assumption.","diagram_only":true}</metadata>
|
||||
<metadata>{"techviz":{"spec_version":"1.1","id":"baseline-schema","profile":"component-flow"},"source_context":{"document":"docs/n+1liner/final/document.md","document_sha256":"f9e048a68db0ab82078955bf611b06a71a0033539e0c87ff0aa0d620a3126e36","anchor":{"kind":"heading","value":"3.1 관계와 스키마","line":44}},"evidence_policy":"Each factual element cites source lines or is marked assumption.","diagram_only":true}</metadata>
|
||||
<defs>
|
||||
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" />
|
||||
@@ -50,29 +50,29 @@
|
||||
</style>
|
||||
</defs>
|
||||
<rect class="canvas" width="885" height="305" />
|
||||
<polyline class="edge kind-relationship style-solid emphasis-normal" points="530.0,160.0 610.0,160.0 610.0,160.0 690.0,160.0" data-evidence="37-37" />
|
||||
<rect class="edge-label-bg" x="544.0" y="118.0" width="131.9" height="22" rx="3" />
|
||||
<text class="edge-label" x="610.0" y="133.0">여러 highlights를 가짐</text>
|
||||
<polyline class="edge kind-relationship style-solid emphasis-normal" points="220.0,228.0 300.0,228.0 300.0,169.0 380.0,169.0" data-evidence="36-36" />
|
||||
<rect class="edge-label-bg" x="261.4" y="184.5" width="125.2" height="22" rx="3" />
|
||||
<text class="edge-label" x="324.0" y="199.5">여러 feed_item이 딸림</text>
|
||||
<polyline class="edge kind-relationship style-solid emphasis-normal" points="220.0,92.0 300.0,92.0 300.0,151.0 380.0,151.0" data-evidence="35-35" />
|
||||
<rect class="edge-label-bg" x="261.4" y="107.5" width="125.2" height="22" rx="3" />
|
||||
<text class="edge-label" x="324.0" y="122.5">여러 feed_item을 가짐</text>
|
||||
<polyline class="edge kind-relationship style-solid emphasis-normal" points="530.0,160.0 610.0,160.0 610.0,160.0 690.0,160.0" data-evidence="48-48" />
|
||||
<rect class="edge-label-bg" x="588.0" y="118.0" width="44.0" height="22" rx="3" />
|
||||
<text class="edge-label" x="610.0" y="133.0">1:N</text>
|
||||
<polyline class="edge kind-relationship style-solid emphasis-normal" points="220.0,228.0 300.0,228.0 300.0,169.0 380.0,169.0" data-evidence="47-47" />
|
||||
<rect class="edge-label-bg" x="302.0" y="184.5" width="44.0" height="22" rx="3" />
|
||||
<text class="edge-label" x="324.0" y="199.5">1:N</text>
|
||||
<polyline class="edge kind-relationship style-solid emphasis-normal" points="220.0,92.0 300.0,92.0 300.0,151.0 380.0,151.0" data-evidence="46-46" />
|
||||
<rect class="edge-label-bg" x="302.0" y="107.5" width="44.0" height="22" rx="3" />
|
||||
<text class="edge-label" x="324.0" y="122.5">1:N</text>
|
||||
<g id="node-users">
|
||||
<rect class="node-shape kind-entity emphasis-normal role-source" data-evidence="35-35" x="70.0" y="60.0" width="150.0" height="64.0" rx="7" />
|
||||
<rect class="node-shape kind-entity emphasis-normal role-source" data-evidence="46-46" x="70.0" y="60.0" width="150.0" height="64.0" rx="7" />
|
||||
<text class="node-label" x="145.0" y="90.0">users</text>
|
||||
</g>
|
||||
<g id="node-pages">
|
||||
<rect class="node-shape kind-entity emphasis-normal role-source" data-evidence="36-36" x="70.0" y="196.0" width="150.0" height="64.0" rx="7" />
|
||||
<rect class="node-shape kind-entity emphasis-normal role-source" data-evidence="47-47" x="70.0" y="196.0" width="150.0" height="64.0" rx="7" />
|
||||
<text class="node-label" x="145.0" y="226.0">pages</text>
|
||||
</g>
|
||||
<g id="node-feed-items">
|
||||
<rect class="node-shape kind-entity emphasis-normal role-store" data-evidence="35-35" x="380.0" y="128.0" width="150.0" height="64.0" rx="7" />
|
||||
<rect class="node-shape kind-entity emphasis-normal role-store" data-evidence="46-46" x="380.0" y="128.0" width="150.0" height="64.0" rx="7" />
|
||||
<text class="node-label" x="455.0" y="158.0">feed_items</text>
|
||||
</g>
|
||||
<g id="node-highlights">
|
||||
<rect class="node-shape kind-entity emphasis-normal role-sink" data-evidence="37-37" x="690.0" y="128.0" width="150.0" height="64.0" rx="7" />
|
||||
<rect class="node-shape kind-entity emphasis-normal role-sink" data-evidence="48-48" x="690.0" y="128.0" width="150.0" height="64.0" rx="7" />
|
||||
<text class="node-label" x="765.0" y="158.0">highlights</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.0 KiB |
@@ -0,0 +1,25 @@
|
||||
# 배치는 부모를 먼저 페이징하고 자식을 IN으로 따로 가져온다
|
||||
|
||||
## Alternative text
|
||||
|
||||
조회 코드, Hibernate 세션, feed_items, highlights 네 참여자 사이에서 부모 페이징이 먼저 일어나고 그다음 자식 IN 배치 조회가 일어나는 순서도.
|
||||
|
||||
## Long description
|
||||
|
||||
조회 코드가 부모 페이지를 요청하면 세션이 feed_items에 부모만 LIMIT으로 조회한다. 돌아온 부모에는 초기화되지 않은 컬렉션 프록시가 붙어 있다. 세션은 그 프록시를 batch fetch size만큼 모아 highlights를 WHERE fk IN 으로 조회하고, 받은 자식 행으로 컬렉션을 채워 조회 코드에 돌려준다.
|
||||
|
||||
## Elements and evidence
|
||||
|
||||
- **조회 코드** (actor): 페이지를 요청하는 애플리케이션 코드 Evidence: L1009–L1010.
|
||||
- **Hibernate 세션** (service): default_batch_fetch_size로 프록시를 모으는 주체 Evidence: L1010–L1012.
|
||||
- **feed_items** (store): 부모 테이블 Evidence: L1009–L1010.
|
||||
- **highlights** (store): 자식 테이블 Evidence: L1010–L1011.
|
||||
|
||||
## Relationships
|
||||
|
||||
- **조회 코드 → Hibernate 세션:** 부모 페이지 요청. Evidence: L1009–L1010.
|
||||
- **Hibernate 세션 → feed_items:** 부모 LIMIT 페이징. Evidence: L1009–L1010.
|
||||
- **feed_items → Hibernate 세션:** 페이지 부모 + 미초기화 프록시. Evidence: L1010–L1012.
|
||||
- **Hibernate 세션 → highlights:** WHERE fk IN (?,…) 배치. Evidence: L1010–L1012.
|
||||
- **highlights → Hibernate 세션:** 자식 행. Evidence: L1010–L1011.
|
||||
- **Hibernate 세션 → 조회 코드:** 부모 + 컬렉션. Evidence: L1012–L1014.
|
||||
@@ -0,0 +1,21 @@
|
||||
# 배치는 부모를 먼저 페이징하고 자식을 IN으로 따로 가져온다
|
||||
# Question: 배치 페치는 어떤 순서로 부모 페이징과 자식 조회를 나누는가?
|
||||
direction: down
|
||||
n0: "조회 코드" {
|
||||
shape: person
|
||||
}
|
||||
n1: "Hibernate 세션" {
|
||||
shape: rectangle
|
||||
}
|
||||
n2: "feed_items" {
|
||||
shape: rectangle
|
||||
}
|
||||
n3: "highlights" {
|
||||
shape: rectangle
|
||||
}
|
||||
n0 -> n1: "부모 페이지 요청"
|
||||
n1 -> n2: "부모 LIMIT 페이징"
|
||||
n2 -> n1: "페이지 부모 + 미초기화 프록시"
|
||||
n1 -> n3: "WHERE fk IN (?,…) 배치"
|
||||
n3 -> n1: "자식 행"
|
||||
n1 -> n0: "부모 + 컬렉션"
|
||||
@@ -0,0 +1,15 @@
|
||||
digraph techviz {
|
||||
graph [rankdir=TB, splines=ortho, nodesep=0.55, ranksep=0.85];
|
||||
node [fontname=Helvetica, fontsize=11, margin="0.18,0.12", style="rounded,filled", fillcolor=white, color="#2d4357", penwidth=1.5];
|
||||
edge [fontname=Helvetica, fontsize=10, color="#364b5f", penwidth=1.4, arrowsize=0.75];
|
||||
n0 [label="조회 코드", shape=box, style="rounded,dashed,filled"];
|
||||
n1 [label="Hibernate 세션", shape=box, style="rounded,filled"];
|
||||
n2 [label="feed_items", shape=box, style="rounded,filled"];
|
||||
n3 [label="highlights", shape=box, style="rounded,filled"];
|
||||
n0 -> n1 [label="부모 페이지 요청", style=solid];
|
||||
n1 -> n2 [label="부모 LIMIT 페이징", style=solid];
|
||||
n2 -> n1 [label="페이지 부모 + 미초기화 프록시", style=solid];
|
||||
n1 -> n3 [label="WHERE fk IN (?,…) 배치", style=solid];
|
||||
n3 -> n1 [label="자식 행", style=solid];
|
||||
n1 -> n0 [label="부모 + 컬렉션", style=solid];
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<mxfile host="app.diagrams.net" modified="2026-07-23T00:00:00.000Z" agent="techviz-harness" version="24.7.17" type="device">
|
||||
<diagram id="batch-fetch-in-clause" name="배치는 부모를 먼저 페이징하고 자식을 IN으로 따로 가져온다">
|
||||
<mxGraphModel dx="870" dy="542" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="870" pageHeight="1169" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0"/>
|
||||
<mxCell id="1" parent="0"/>
|
||||
<mxCell id="n_loader" value="조회 코드" tooltip="페이지를 요청하는 애플리케이션 코드 | Evidence: L1009-L1010" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;dashed=1;fillColor=#f5f7fa;" vertex="1" parent="1">
|
||||
<mxGeometry x="45.0" y="35.0" width="150.0" height="78.0" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="n_session" value="Hibernate 세션" tooltip="default_batch_fetch_size로 프록시를 모으는 주체 | Evidence: L1010-L1012" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="255.0" y="35.0" width="150.0" height="64.0" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="n_feed-items" value="feed_items" tooltip="부모 테이블 | Evidence: L1009-L1010" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;shape=cylinder3;boundedLbl=1;backgroundOutline=1;fillColor=#eef6fb;" vertex="1" parent="1">
|
||||
<mxGeometry x="465.0" y="35.0" width="150.0" height="78.0" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="n_highlights" value="highlights" tooltip="자식 테이블 | Evidence: L1010-L1011" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;shape=cylinder3;boundedLbl=1;backgroundOutline=1;fillColor=#eef6fb;" vertex="1" parent="1">
|
||||
<mxGeometry x="675.0" y="35.0" width="150.0" height="78.0" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="e_m1" value="부모 페이지 요청" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_loader" target="n_session">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="225.0" y="128.0" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="e_m2" value="부모 LIMIT 페이징" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_session" target="n_feed-items">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="435.0" y="190.0" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="e_m3" value="페이지 부모 + 미초기화 프록시" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_feed-items" target="n_session">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="435.0" y="252.0" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="e_m4" value="WHERE fk IN (?,…) 배치" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_session" target="n_highlights">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="540.0" y="314.0" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="e_m5" value="자식 행" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_highlights" target="n_session">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="540.0" y="376.0" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="e_m6" value="부모 + 컬렉션" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_session" target="n_loader">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="225.0" y="438.0" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
@@ -0,0 +1,835 @@
|
||||
{
|
||||
"type": "excalidraw",
|
||||
"version": 2,
|
||||
"source": "techviz-harness",
|
||||
"elements": [
|
||||
{
|
||||
"id": "edge-m1",
|
||||
"type": "arrow",
|
||||
"x": 120.0,
|
||||
"y": 140.0,
|
||||
"width": 210.0,
|
||||
"height": 0.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": null,
|
||||
"seed": 1391175952,
|
||||
"version": 1,
|
||||
"versionNonce": 1697606636,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
210.0,
|
||||
0.0
|
||||
]
|
||||
],
|
||||
"lastCommittedPoint": null,
|
||||
"startBinding": {
|
||||
"elementId": "node-loader",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"endBinding": {
|
||||
"elementId": "node-session",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow",
|
||||
"elbowed": true
|
||||
},
|
||||
{
|
||||
"id": "edge-label-m1",
|
||||
"type": "text",
|
||||
"x": 180.0,
|
||||
"y": 116.0,
|
||||
"width": 90,
|
||||
"height": 24,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 205439938,
|
||||
"version": 1,
|
||||
"versionNonce": 479762556,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 13,
|
||||
"fontFamily": 5,
|
||||
"text": "부모 페이지 요청",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "부모 페이지 요청",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "edge-m2",
|
||||
"type": "arrow",
|
||||
"x": 330.0,
|
||||
"y": 202.0,
|
||||
"width": 210.0,
|
||||
"height": 0.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": null,
|
||||
"seed": 1592612909,
|
||||
"version": 1,
|
||||
"versionNonce": 300451842,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
210.0,
|
||||
0.0
|
||||
]
|
||||
],
|
||||
"lastCommittedPoint": null,
|
||||
"startBinding": {
|
||||
"elementId": "node-session",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"endBinding": {
|
||||
"elementId": "node-feed-items",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow",
|
||||
"elbowed": true
|
||||
},
|
||||
{
|
||||
"id": "edge-label-m2",
|
||||
"type": "text",
|
||||
"x": 387.0,
|
||||
"y": 178.0,
|
||||
"width": 96,
|
||||
"height": 24,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 774335790,
|
||||
"version": 1,
|
||||
"versionNonce": 203462744,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 13,
|
||||
"fontFamily": 5,
|
||||
"text": "부모 LIMIT 페이징",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "부모 LIMIT 페이징",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "edge-m3",
|
||||
"type": "arrow",
|
||||
"x": 330.0,
|
||||
"y": 264.0,
|
||||
"width": 210.0,
|
||||
"height": 0.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": null,
|
||||
"seed": 986781738,
|
||||
"version": 1,
|
||||
"versionNonce": 1446276182,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[
|
||||
210.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
0.0,
|
||||
0.0
|
||||
]
|
||||
],
|
||||
"lastCommittedPoint": null,
|
||||
"startBinding": {
|
||||
"elementId": "node-feed-items",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"endBinding": {
|
||||
"elementId": "node-session",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow",
|
||||
"elbowed": true
|
||||
},
|
||||
{
|
||||
"id": "edge-label-m3",
|
||||
"type": "text",
|
||||
"x": 367.0,
|
||||
"y": 240.0,
|
||||
"width": 136,
|
||||
"height": 24,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 79282802,
|
||||
"version": 1,
|
||||
"versionNonce": 1904842631,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 13,
|
||||
"fontFamily": 5,
|
||||
"text": "페이지 부모 + 미초기화 프록시",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "페이지 부모 + 미초기화 프록시",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "edge-m4",
|
||||
"type": "arrow",
|
||||
"x": 330.0,
|
||||
"y": 326.0,
|
||||
"width": 420.0,
|
||||
"height": 0.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": null,
|
||||
"seed": 522855009,
|
||||
"version": 1,
|
||||
"versionNonce": 870155517,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
420.0,
|
||||
0.0
|
||||
]
|
||||
],
|
||||
"lastCommittedPoint": null,
|
||||
"startBinding": {
|
||||
"elementId": "node-session",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"endBinding": {
|
||||
"elementId": "node-highlights",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow",
|
||||
"elbowed": true
|
||||
},
|
||||
{
|
||||
"id": "edge-label-m4",
|
||||
"type": "text",
|
||||
"x": 460.0,
|
||||
"y": 302.0,
|
||||
"width": 160,
|
||||
"height": 24,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1255410992,
|
||||
"version": 1,
|
||||
"versionNonce": 1365345158,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 13,
|
||||
"fontFamily": 5,
|
||||
"text": "WHERE fk IN (?,…) 배치",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "WHERE fk IN (?,…) 배치",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "edge-m5",
|
||||
"type": "arrow",
|
||||
"x": 330.0,
|
||||
"y": 388.0,
|
||||
"width": 420.0,
|
||||
"height": 0.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": null,
|
||||
"seed": 1867262898,
|
||||
"version": 1,
|
||||
"versionNonce": 135793406,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[
|
||||
420.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
0.0,
|
||||
0.0
|
||||
]
|
||||
],
|
||||
"lastCommittedPoint": null,
|
||||
"startBinding": {
|
||||
"elementId": "node-highlights",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"endBinding": {
|
||||
"elementId": "node-session",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow",
|
||||
"elbowed": true
|
||||
},
|
||||
{
|
||||
"id": "edge-label-m5",
|
||||
"type": "text",
|
||||
"x": 495.0,
|
||||
"y": 364.0,
|
||||
"width": 90,
|
||||
"height": 24,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1226396936,
|
||||
"version": 1,
|
||||
"versionNonce": 1389084094,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 13,
|
||||
"fontFamily": 5,
|
||||
"text": "자식 행",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "자식 행",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "edge-m6",
|
||||
"type": "arrow",
|
||||
"x": 120.0,
|
||||
"y": 450.0,
|
||||
"width": 210.0,
|
||||
"height": 0.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": null,
|
||||
"seed": 70953370,
|
||||
"version": 1,
|
||||
"versionNonce": 201368227,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[
|
||||
210.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
0.0,
|
||||
0.0
|
||||
]
|
||||
],
|
||||
"lastCommittedPoint": null,
|
||||
"startBinding": {
|
||||
"elementId": "node-session",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"endBinding": {
|
||||
"elementId": "node-loader",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow",
|
||||
"elbowed": true
|
||||
},
|
||||
{
|
||||
"id": "edge-label-m6",
|
||||
"type": "text",
|
||||
"x": 180.0,
|
||||
"y": 426.0,
|
||||
"width": 90,
|
||||
"height": 24,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 74056664,
|
||||
"version": 1,
|
||||
"versionNonce": 1569763366,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 13,
|
||||
"fontFamily": 5,
|
||||
"text": "부모 + 컬렉션",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "부모 + 컬렉션",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "node-loader",
|
||||
"type": "rectangle",
|
||||
"x": 45.0,
|
||||
"y": 35.0,
|
||||
"width": 150.0,
|
||||
"height": 78.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#ffffff",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "dashed",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1218388377,
|
||||
"version": 1,
|
||||
"versionNonce": 115862452,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "node-label-loader",
|
||||
"type": "text",
|
||||
"x": 55.0,
|
||||
"y": 45.0,
|
||||
"width": 130.0,
|
||||
"height": 58.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1574424730,
|
||||
"version": 1,
|
||||
"versionNonce": 1971124067,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 15,
|
||||
"fontFamily": 5,
|
||||
"text": "조회 코드",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "조회 코드",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "node-session",
|
||||
"type": "rectangle",
|
||||
"x": 255.0,
|
||||
"y": 35.0,
|
||||
"width": 150.0,
|
||||
"height": 64.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#ffffff",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 112382159,
|
||||
"version": 1,
|
||||
"versionNonce": 1956470051,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "node-label-session",
|
||||
"type": "text",
|
||||
"x": 265.0,
|
||||
"y": 45.0,
|
||||
"width": 130.0,
|
||||
"height": 44.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 648629018,
|
||||
"version": 1,
|
||||
"versionNonce": 1505062559,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 15,
|
||||
"fontFamily": 5,
|
||||
"text": "Hibernate 세션",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "Hibernate 세션",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "node-feed-items",
|
||||
"type": "rectangle",
|
||||
"x": 465.0,
|
||||
"y": 35.0,
|
||||
"width": 150.0,
|
||||
"height": 78.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#ffffff",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1553680117,
|
||||
"version": 1,
|
||||
"versionNonce": 1528008093,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "node-label-feed-items",
|
||||
"type": "text",
|
||||
"x": 475.0,
|
||||
"y": 45.0,
|
||||
"width": 130.0,
|
||||
"height": 58.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1561637253,
|
||||
"version": 1,
|
||||
"versionNonce": 189981097,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 15,
|
||||
"fontFamily": 5,
|
||||
"text": "feed_items",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "feed_items",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "node-highlights",
|
||||
"type": "rectangle",
|
||||
"x": 675.0,
|
||||
"y": 35.0,
|
||||
"width": 150.0,
|
||||
"height": 78.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#ffffff",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 281310605,
|
||||
"version": 1,
|
||||
"versionNonce": 933280984,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "node-label-highlights",
|
||||
"type": "text",
|
||||
"x": 685.0,
|
||||
"y": 45.0,
|
||||
"width": 130.0,
|
||||
"height": 58.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1591294741,
|
||||
"version": 1,
|
||||
"versionNonce": 457414640,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 15,
|
||||
"fontFamily": 5,
|
||||
"text": "highlights",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "highlights",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
}
|
||||
],
|
||||
"appState": {
|
||||
"gridSize": 10,
|
||||
"viewBackgroundColor": "#ffffff",
|
||||
"currentItemFontFamily": 5
|
||||
},
|
||||
"files": {}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"harness_version": "0.2.0",
|
||||
"spec_id": "batch-fetch-in-clause",
|
||||
"spec_version": "1.1",
|
||||
"spec_sha256": "57f3a3e4984bdd31ec2458d19757d8ed3d34209ece4c649ab6fe1dd993198161",
|
||||
"source_context": {
|
||||
"document": "docs/n+1liner/final/document.md",
|
||||
"document_sha256": "385ca44db5bf763cb1ff28a67d531d68876402b35d21dc54f2bc702e8bda0053",
|
||||
"anchor": {
|
||||
"kind": "heading",
|
||||
"value": "11.5 배치가 N+1과 페이징을 함께 해결하는 이유",
|
||||
"line": 1007
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
"batch-fetch-in-clause.svg",
|
||||
"batch-fetch-in-clause.drawio",
|
||||
"batch-fetch-in-clause.mmd",
|
||||
"batch-fetch-in-clause.d2",
|
||||
"batch-fetch-in-clause.dot",
|
||||
"batch-fetch-in-clause.excalidraw",
|
||||
"batch-fetch-in-clause.alt.md"
|
||||
],
|
||||
"lint_issue_count": 0,
|
||||
"assumption_count": 0,
|
||||
"assumptions_allowed": false,
|
||||
"composition_profile": "sequence",
|
||||
"reference_ids": [
|
||||
"payment-approval-sequence"
|
||||
],
|
||||
"diagram_only": true
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
%% 배치는 부모를 먼저 페이징하고 자식을 IN으로 따로 가져온다
|
||||
%% question: 배치 페치는 어떤 순서로 부모 페이징과 자식 조회를 나누는가?
|
||||
sequenceDiagram
|
||||
participant n0 as 조회 코드
|
||||
participant n1 as Hibernate 세션
|
||||
participant n2 as feed_items
|
||||
participant n3 as highlights
|
||||
n0->>n1: 부모 페이지 요청
|
||||
n1->>n2: 부모 LIMIT 페이징
|
||||
n2->>n1: 페이지 부모 + 미초기화 프록시
|
||||
n1->>n3: WHERE fk IN (?,…) 배치
|
||||
n3->>n1: 자식 행
|
||||
n1->>n0: 부모 + 컬렉션
|
||||
@@ -0,0 +1,83 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="870" height="542" viewBox="0 0 870 542" role="img" aria-labelledby="diagram-title diagram-description">
|
||||
<title id="diagram-title">배치는 부모를 먼저 페이징하고 자식을 IN으로 따로 가져온다</title>
|
||||
<desc id="diagram-description">조회 코드가 부모 페이지를 요청하면 세션이 feed_items에 부모만 LIMIT으로 조회한다. 돌아온 부모에는 초기화되지 않은 컬렉션 프록시가 붙어 있다. 세션은 그 프록시를 batch fetch size만큼 모아 highlights를 WHERE fk IN 으로 조회하고, 받은 자식 행으로 컬렉션을 채워 조회 코드에 돌려준다.</desc>
|
||||
<metadata>{"techviz":{"spec_version":"1.1","id":"batch-fetch-in-clause","profile":"sequence"},"source_context":{"document":"docs/n+1liner/final/document.md","document_sha256":"385ca44db5bf763cb1ff28a67d531d68876402b35d21dc54f2bc702e8bda0053","anchor":{"kind":"heading","value":"11.5 배치가 N+1과 페이징을 함께 해결하는 이유","line":1007}},"evidence_policy":"Each factual element cites source lines or is marked assumption.","diagram_only":true}</metadata>
|
||||
<defs>
|
||||
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" />
|
||||
</marker>
|
||||
<style>
|
||||
:root { color-scheme: light; }
|
||||
text { font-family: Inter, Pretendard, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #111827; }
|
||||
.canvas { fill: #ffffff; }
|
||||
.group-box { fill: #ffffff; stroke: #9ca3af; stroke-width: 1.4; stroke-dasharray: 7 5; }
|
||||
.group-label-bg { fill: #ffffff; }
|
||||
.group-label { font-size: 13px; font-weight: 650; fill: #374151; }
|
||||
.edge { fill: none; stroke: #374151; stroke-width: 1.8; stroke-linejoin: round; stroke-linecap: round; marker-end: url(#arrow); }
|
||||
.edge.style-dashed, .edge.semantic-dashed, .edge.assumption { stroke-dasharray: 7 5; }
|
||||
.edge.style-dotted { stroke-dasharray: 2 5; }
|
||||
.edge.emphasis-primary { stroke: #2563eb; stroke-width: 2.2; }
|
||||
.edge.emphasis-muted { stroke: #9ca3af; }
|
||||
.edge.emphasis-warning, .edge.kind-failure, .edge.kind-error { stroke: #dc2626; stroke-width: 2.2; }
|
||||
.edge-label-bg { fill: #ffffff; }
|
||||
.edge-label { font-size: 12px; font-weight: 560; text-anchor: middle; }
|
||||
.node-shape { fill: #ffffff; stroke: #4b5563; stroke-width: 1.7; }
|
||||
.node-shape.emphasis-primary { stroke: #2563eb; stroke-width: 2.2; }
|
||||
.node-shape.emphasis-muted { stroke: #9ca3af; fill: #f9fafb; }
|
||||
.node-shape.emphasis-warning { stroke: #d97706; stroke-width: 2; fill: #fffdf5; }
|
||||
.node-shape.kind-database, .node-shape.kind-datastore, .node-shape.kind-storage { fill: #f8fafc; }
|
||||
.node-shape.kind-queue, .node-shape.kind-event, .node-shape.kind-topic { fill: #fafafa; }
|
||||
.node-shape.assumption { stroke-dasharray: 4 4; }
|
||||
.storage-bottom, .controller-divider { fill: none; stroke: #4b5563; stroke-width: 1.4; }
|
||||
.controller-led { fill: #4b5563; }
|
||||
.actor-symbol { fill: none; stroke: #4b5563; stroke-width: 1.8; stroke-linecap: round; }
|
||||
.actor-symbol.emphasis-primary { stroke: #2563eb; stroke-width: 2.2; }
|
||||
.node-label { font-size: 14px; font-weight: 650; text-anchor: middle; }
|
||||
.node-role { font-size: 10px; letter-spacing: 0.04em; text-anchor: middle; fill: #6b7280; }
|
||||
.node-detail-divider { stroke: #d1d5db; stroke-width: 1; }
|
||||
.node-detail { font-size: 11px; fill: #374151; }
|
||||
.assumption-badge { font-size: 9px; font-weight: 700; fill: #92400e; }
|
||||
.failure-mark { stroke: #dc2626; stroke-width: 4; stroke-linecap: round; }
|
||||
.lifeline { stroke: #9ca3af; stroke-width: 1.2; stroke-dasharray: 5 5; }
|
||||
.timeline-axis { stroke: #374151; stroke-width: 1.8; marker-end: url(#arrow); }
|
||||
.timeline-stem { stroke: #6b7280; stroke-width: 1.3; }
|
||||
.timeline-marker { fill: #ffffff; stroke: #374151; stroke-width: 1.7; }
|
||||
.timeline-marker.primary { fill: #2563eb; stroke: #2563eb; }
|
||||
.timeline-marker.warning { fill: #dc2626; stroke: #dc2626; }
|
||||
.timeline-label { font-size: 13px; font-weight: 650; text-anchor: middle; }
|
||||
.timeline-detail { font-size: 11px; fill: #4b5563; text-anchor: middle; }
|
||||
</style>
|
||||
</defs>
|
||||
<rect class="canvas" width="870" height="542" />
|
||||
<g class="actor-symbol emphasis-normal" data-evidence="1009-1010"><circle cx="120.0" cy="55.0" r="11.0" /><line x1="120.0" y1="71.0" x2="120.0" y2="74.0" /><line x1="102.0" y1="81.0" x2="138.0" y2="81.0" /><line x1="120.0" y1="74.0" x2="105.0" y2="91.0" /><line x1="120.0" y1="74.0" x2="135.0" y2="91.0" /></g>
|
||||
<text class="node-label" x="120.0" y="106.0">조회 코드</text>
|
||||
<line class="lifeline" x1="120.0" y1="113.0" x2="120.0" y2="512.0" />
|
||||
<rect class="node-shape kind-service emphasis-normal role-participant" data-evidence="1010-1012" x="255.0" y="35.0" width="150.0" height="64.0" rx="7" />
|
||||
<text class="node-label" x="330.0" y="65.0">Hibernate 세션</text>
|
||||
<line class="lifeline" x1="330.0" y1="99.0" x2="330.0" y2="512.0" />
|
||||
<rect class="node-shape kind-store emphasis-normal role-participant" data-evidence="1009-1010" x="465.0" y="48.0" width="150.0" height="52.0" /><ellipse class="node-shape kind-store emphasis-normal role-participant" cx="540.0" cy="48.0" rx="75.0" ry="13.0" /><path class="storage-bottom" d="M 465.0 100.0 A 75.0 13.0 0 0 0 615.0 100.0" />
|
||||
<text class="node-label" x="540.0" y="75.0">feed_items</text>
|
||||
<line class="lifeline" x1="540.0" y1="113.0" x2="540.0" y2="512.0" />
|
||||
<rect class="node-shape kind-store emphasis-normal role-participant" data-evidence="1010-1011" x="675.0" y="48.0" width="150.0" height="52.0" /><ellipse class="node-shape kind-store emphasis-normal role-participant" cx="750.0" cy="48.0" rx="75.0" ry="13.0" /><path class="storage-bottom" d="M 675.0 100.0 A 75.0 13.0 0 0 0 825.0 100.0" />
|
||||
<text class="node-label" x="750.0" y="75.0">highlights</text>
|
||||
<line class="lifeline" x1="750.0" y1="113.0" x2="750.0" y2="512.0" />
|
||||
<polyline class="edge kind-request style-solid emphasis-normal" points="120.0,140.0 330.0,140.0" data-evidence="1009-1010" />
|
||||
<rect class="edge-label-bg" x="175.8" y="114.0" width="98.4" height="22" rx="3" />
|
||||
<text class="edge-label" x="225.0" y="129.0">1. 부모 페이지 요청</text>
|
||||
<polyline class="edge kind-query style-solid emphasis-normal" points="330.0,202.0 540.0,202.0" data-evidence="1009-1010" />
|
||||
<rect class="edge-label-bg" x="375.8" y="176.0" width="118.5" height="22" rx="3" />
|
||||
<text class="edge-label" x="435.0" y="191.0">2. 부모 LIMIT 페이징</text>
|
||||
<polyline class="edge kind-response style-dashed emphasis-normal semantic-dashed" points="540.0,264.0 330.0,264.0" data-evidence="1010-1012" />
|
||||
<rect class="edge-label-bg" x="359.0" y="238.0" width="152.0" height="22" rx="3" />
|
||||
<text class="edge-label" x="435.0" y="253.0">3. 페이지 부모 + 미초기화 프록시</text>
|
||||
<polyline class="edge kind-query style-solid emphasis-normal" points="330.0,326.0 750.0,326.0" data-evidence="1010-1012" />
|
||||
<rect class="edge-label-bg" x="453.9" y="300.0" width="172.1" height="22" rx="3" />
|
||||
<text class="edge-label" x="540.0" y="315.0">4. WHERE fk IN (?,…) 배치</text>
|
||||
<polyline class="edge kind-response style-dashed emphasis-normal semantic-dashed" points="750.0,388.0 330.0,388.0" data-evidence="1010-1011" />
|
||||
<rect class="edge-label-bg" x="507.6" y="362.0" width="64.9" height="22" rx="3" />
|
||||
<text class="edge-label" x="540.0" y="377.0">5. 자식 행</text>
|
||||
<polyline class="edge kind-response style-dashed emphasis-normal semantic-dashed" points="330.0,450.0 120.0,450.0" data-evidence="1012-1014" />
|
||||
<rect class="edge-label-bg" x="179.2" y="424.0" width="91.7" height="22" rx="3" />
|
||||
<text class="edge-label" x="225.0" y="439.0">6. 부모 + 컬렉션</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.8 KiB |
@@ -0,0 +1,24 @@
|
||||
# 컬렉션 하나만 fetch join하면 전송 행이 자식 수만큼 늘어난다
|
||||
|
||||
## Alternative text
|
||||
|
||||
feed_items와 highlights가 fetch join으로 합쳐져 전송 조인 행이 되고, 그 행이 결과 리스트로 갈 때만 루트 엔티티가 중복 제거되는 흐름.
|
||||
|
||||
## Long description
|
||||
|
||||
왼쪽에서 feed_items와 highlights 두 테이블이 highlights fetch join으로 들어간다. 조인은 부모 행을 자식 수만큼 반복한 전송 조인 행을 만든다. 그 행이 결과 리스트로 넘어갈 때 Hibernate가 루트 엔티티를 중복 제거하므로 리스트 크기만 줄고 전송 단계의 카테시안은 남는다.
|
||||
|
||||
## Elements and evidence
|
||||
|
||||
- **feed_items** (store): 부모 테이블 Evidence: L713–L714.
|
||||
- **highlights** (store): 자식 테이블 Evidence: L713–L714.
|
||||
- **highlights fetch join** (service): 컬렉션 하나만 fetch join한 루트 쿼리 Evidence: L713–L715.
|
||||
- **전송 조인 행** (queue): DB가 애플리케이션에 전달한 조인 카디널리티 Evidence: L714–L715, L732–L736.
|
||||
- **결과 리스트** (sink): Hibernate 6+ 가 루트를 dedup한 뒤의 목록 Evidence: L717–L719.
|
||||
|
||||
## Relationships
|
||||
|
||||
- **highlights → highlights fetch join:** 자식 행. Evidence: L713–L714.
|
||||
- **전송 조인 행 → 결과 리스트:** 루트 dedup. Evidence: L717–L719.
|
||||
- **highlights fetch join → 전송 조인 행:** 카테시안 곱. Evidence: L713–L715, L732–L734.
|
||||
- **feed_items → highlights fetch join:** 부모 행. Evidence: L713–L714.
|
||||
@@ -0,0 +1,22 @@
|
||||
# 컬렉션 하나만 fetch join하면 전송 행이 자식 수만큼 늘어난다
|
||||
# Question: 컬렉션 하나만 fetch join했을 때 DB가 전달하는 행은 왜 늘어나는가?
|
||||
direction: right
|
||||
n0: "feed_items" {
|
||||
shape: rectangle
|
||||
}
|
||||
n1: "highlights" {
|
||||
shape: rectangle
|
||||
}
|
||||
n2: "highlights fetch join" {
|
||||
shape: rectangle
|
||||
}
|
||||
n3: "전송 조인 행" {
|
||||
shape: queue
|
||||
}
|
||||
n4: "결과 리스트" {
|
||||
shape: rectangle
|
||||
}
|
||||
n0 -> n2: "부모 행"
|
||||
n1 -> n2: "자식 행"
|
||||
n2 -> n3: "카테시안 곱"
|
||||
n3 -> n4: "루트 dedup"
|
||||
@@ -0,0 +1,14 @@
|
||||
digraph techviz {
|
||||
graph [rankdir=LR, splines=ortho, nodesep=0.55, ranksep=0.85];
|
||||
node [fontname=Helvetica, fontsize=11, margin="0.18,0.12", style="rounded,filled", fillcolor=white, color="#2d4357", penwidth=1.5];
|
||||
edge [fontname=Helvetica, fontsize=10, color="#364b5f", penwidth=1.4, arrowsize=0.75];
|
||||
n0 [label="feed_items", shape=box, style="rounded,filled"];
|
||||
n1 [label="highlights", shape=box, style="rounded,filled"];
|
||||
n2 [label="highlights fetch join", shape=box, style="rounded,filled"];
|
||||
n3 [label="전송 조인 행", shape=oval, style="rounded,filled"];
|
||||
n4 [label="결과 리스트", shape=box, style="rounded,filled"];
|
||||
n0 -> n2 [label="부모 행", style=solid];
|
||||
n1 -> n2 [label="자식 행", style=solid];
|
||||
n2 -> n3 [label="카테시안 곱", style=solid];
|
||||
n3 -> n4 [label="루트 dedup", style=solid];
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<mxfile host="app.diagrams.net" modified="2026-07-23T00:00:00.000Z" agent="techviz-harness" version="24.7.17" type="device">
|
||||
<diagram id="cartesian-row-multiplication" name="컬렉션 하나만 fetch join하면 전송 행이 자식 수만큼 늘어난다">
|
||||
<mxGraphModel dx="1226" dy="357" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1226" pageHeight="1169" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0"/>
|
||||
<mxCell id="1" parent="0"/>
|
||||
<mxCell id="n_feed-items" value="feed_items" tooltip="부모 테이블 | Evidence: L713-L714" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;shape=cylinder3;boundedLbl=1;backgroundOutline=1;fillColor=#eef6fb;" vertex="1" parent="1">
|
||||
<mxGeometry x="70.0" y="60.0" width="150.0" height="90.0" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="n_highlights" value="highlights" tooltip="자식 테이블 | Evidence: L713-L714" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;shape=cylinder3;boundedLbl=1;backgroundOutline=1;fillColor=#eef6fb;" vertex="1" parent="1">
|
||||
<mxGeometry x="70.0" y="222.0" width="150.0" height="90.0" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="n_fetch-join" value="highlights fetch join" tooltip="컬렉션 하나만 fetch join한 루트 쿼리 | Evidence: L713-L715" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="380.0" y="154.0" width="181.0" height="64.0" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="n_transferred-rows" value="전송 조인 행<br/>Σ highlights" tooltip="DB가 애플리케이션에 전달한 조인 카디널리티 | Evidence: L714-L715, L732-L736" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;strokeColor=#2563eb;strokeWidth=2;" vertex="1" parent="1">
|
||||
<mxGeometry x="721.0" y="150.5" width="150.0" height="71.0" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="n_result-list" value="결과 리스트<br/>루트 엔티티 중복 제거" tooltip="Hibernate 6+ 가 루트를 dedup한 뒤의 목록 | Evidence: L717-L719" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="1031.0" y="150.5" width="150.0" height="71.0" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="e_child-into-join" value="자식 행" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_highlights" target="n_fetch-join">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="324.0" y="231.0" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="e_dedup-to-list" value="루트 dedup" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_transferred-rows" target="n_result-list">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="951.0" y="158.0" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="e_join-multiplies" value="카테시안 곱" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_fetch-join" target="n_transferred-rows">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="641.0" y="158.0" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="e_parent-into-join" value="부모 행" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_feed-items" target="n_fetch-join">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="324.0" y="141.0" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
@@ -0,0 +1,754 @@
|
||||
{
|
||||
"type": "excalidraw",
|
||||
"version": 2,
|
||||
"source": "techviz-harness",
|
||||
"elements": [
|
||||
{
|
||||
"id": "edge-child-into-join",
|
||||
"type": "arrow",
|
||||
"x": 220.0,
|
||||
"y": 195.0,
|
||||
"width": 160.0,
|
||||
"height": 72.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": null,
|
||||
"seed": 1216206905,
|
||||
"version": 1,
|
||||
"versionNonce": 1597740270,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[
|
||||
0.0,
|
||||
72.0
|
||||
],
|
||||
[
|
||||
80.0,
|
||||
72.0
|
||||
],
|
||||
[
|
||||
80.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
160.0,
|
||||
0.0
|
||||
]
|
||||
],
|
||||
"lastCommittedPoint": null,
|
||||
"startBinding": {
|
||||
"elementId": "node-highlights",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"endBinding": {
|
||||
"elementId": "node-fetch-join",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow",
|
||||
"elbowed": true
|
||||
},
|
||||
{
|
||||
"id": "edge-label-child-into-join",
|
||||
"type": "text",
|
||||
"x": 279.0,
|
||||
"y": 219.0,
|
||||
"width": 90,
|
||||
"height": 24,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 676370709,
|
||||
"version": 1,
|
||||
"versionNonce": 468176308,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 13,
|
||||
"fontFamily": 5,
|
||||
"text": "자식 행",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "자식 행",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "edge-dedup-to-list",
|
||||
"type": "arrow",
|
||||
"x": 871.0,
|
||||
"y": 186.0,
|
||||
"width": 160.0,
|
||||
"height": 0.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": null,
|
||||
"seed": 357695880,
|
||||
"version": 1,
|
||||
"versionNonce": 567689188,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
80.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
80.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
160.0,
|
||||
0.0
|
||||
]
|
||||
],
|
||||
"lastCommittedPoint": null,
|
||||
"startBinding": {
|
||||
"elementId": "node-transferred-rows",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"endBinding": {
|
||||
"elementId": "node-result-list",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow",
|
||||
"elbowed": true
|
||||
},
|
||||
{
|
||||
"id": "edge-label-dedup-to-list",
|
||||
"type": "text",
|
||||
"x": 906.0,
|
||||
"y": 146.0,
|
||||
"width": 90,
|
||||
"height": 24,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 51077536,
|
||||
"version": 1,
|
||||
"versionNonce": 881831970,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 13,
|
||||
"fontFamily": 5,
|
||||
"text": "루트 dedup",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "루트 dedup",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "edge-join-multiplies",
|
||||
"type": "arrow",
|
||||
"x": 561.0,
|
||||
"y": 186.0,
|
||||
"width": 160.0,
|
||||
"height": 0.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": null,
|
||||
"seed": 1212346453,
|
||||
"version": 1,
|
||||
"versionNonce": 1834884240,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
80.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
80.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
160.0,
|
||||
0.0
|
||||
]
|
||||
],
|
||||
"lastCommittedPoint": null,
|
||||
"startBinding": {
|
||||
"elementId": "node-fetch-join",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"endBinding": {
|
||||
"elementId": "node-transferred-rows",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow",
|
||||
"elbowed": true
|
||||
},
|
||||
{
|
||||
"id": "edge-label-join-multiplies",
|
||||
"type": "text",
|
||||
"x": 596.0,
|
||||
"y": 146.0,
|
||||
"width": 90,
|
||||
"height": 24,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1408036219,
|
||||
"version": 1,
|
||||
"versionNonce": 1674724136,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 13,
|
||||
"fontFamily": 5,
|
||||
"text": "카테시안 곱",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "카테시안 곱",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "edge-parent-into-join",
|
||||
"type": "arrow",
|
||||
"x": 220.0,
|
||||
"y": 105.0,
|
||||
"width": 160.0,
|
||||
"height": 72.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": null,
|
||||
"seed": 677525442,
|
||||
"version": 1,
|
||||
"versionNonce": 968142792,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
80.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
80.0,
|
||||
72.0
|
||||
],
|
||||
[
|
||||
160.0,
|
||||
72.0
|
||||
]
|
||||
],
|
||||
"lastCommittedPoint": null,
|
||||
"startBinding": {
|
||||
"elementId": "node-feed-items",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"endBinding": {
|
||||
"elementId": "node-fetch-join",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow",
|
||||
"elbowed": true
|
||||
},
|
||||
{
|
||||
"id": "edge-label-parent-into-join",
|
||||
"type": "text",
|
||||
"x": 279.0,
|
||||
"y": 129.0,
|
||||
"width": 90,
|
||||
"height": 24,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 640150198,
|
||||
"version": 1,
|
||||
"versionNonce": 77180616,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 13,
|
||||
"fontFamily": 5,
|
||||
"text": "부모 행",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "부모 행",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "node-feed-items",
|
||||
"type": "rectangle",
|
||||
"x": 70.0,
|
||||
"y": 60.0,
|
||||
"width": 150.0,
|
||||
"height": 90.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#ffffff",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1553680117,
|
||||
"version": 1,
|
||||
"versionNonce": 1528008093,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "node-label-feed-items",
|
||||
"type": "text",
|
||||
"x": 80.0,
|
||||
"y": 70.0,
|
||||
"width": 130.0,
|
||||
"height": 70.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1561637253,
|
||||
"version": 1,
|
||||
"versionNonce": 189981097,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 15,
|
||||
"fontFamily": 5,
|
||||
"text": "feed_items",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "feed_items",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "node-highlights",
|
||||
"type": "rectangle",
|
||||
"x": 70.0,
|
||||
"y": 222.0,
|
||||
"width": 150.0,
|
||||
"height": 90.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#ffffff",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 281310605,
|
||||
"version": 1,
|
||||
"versionNonce": 933280984,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "node-label-highlights",
|
||||
"type": "text",
|
||||
"x": 80.0,
|
||||
"y": 232.0,
|
||||
"width": 130.0,
|
||||
"height": 70.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1591294741,
|
||||
"version": 1,
|
||||
"versionNonce": 457414640,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 15,
|
||||
"fontFamily": 5,
|
||||
"text": "highlights",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "highlights",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "node-fetch-join",
|
||||
"type": "rectangle",
|
||||
"x": 380.0,
|
||||
"y": 154.0,
|
||||
"width": 181.0,
|
||||
"height": 64.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#ffffff",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 483696978,
|
||||
"version": 1,
|
||||
"versionNonce": 1765028494,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "node-label-fetch-join",
|
||||
"type": "text",
|
||||
"x": 390.0,
|
||||
"y": 164.0,
|
||||
"width": 161.0,
|
||||
"height": 44.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1579442861,
|
||||
"version": 1,
|
||||
"versionNonce": 1503566175,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 15,
|
||||
"fontFamily": 5,
|
||||
"text": "highlights fetch join",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "highlights fetch join",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "node-transferred-rows",
|
||||
"type": "rectangle",
|
||||
"x": 721.0,
|
||||
"y": 150.5,
|
||||
"width": 150.0,
|
||||
"height": 71.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#f3f0ff",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 879888391,
|
||||
"version": 1,
|
||||
"versionNonce": 281248584,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "node-label-transferred-rows",
|
||||
"type": "text",
|
||||
"x": 731.0,
|
||||
"y": 160.5,
|
||||
"width": 130.0,
|
||||
"height": 51.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 206593575,
|
||||
"version": 1,
|
||||
"versionNonce": 1527179395,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 15,
|
||||
"fontFamily": 5,
|
||||
"text": "전송 조인 행\nΣ highlights",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "전송 조인 행\nΣ highlights",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "node-result-list",
|
||||
"type": "rectangle",
|
||||
"x": 1031.0,
|
||||
"y": 150.5,
|
||||
"width": 150.0,
|
||||
"height": 71.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#ffffff",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1585402317,
|
||||
"version": 1,
|
||||
"versionNonce": 1496949922,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "node-label-result-list",
|
||||
"type": "text",
|
||||
"x": 1041.0,
|
||||
"y": 160.5,
|
||||
"width": 130.0,
|
||||
"height": 51.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 661544278,
|
||||
"version": 1,
|
||||
"versionNonce": 768805970,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 15,
|
||||
"fontFamily": 5,
|
||||
"text": "결과 리스트\n루트 엔티티 중복 제거",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "결과 리스트\n루트 엔티티 중복 제거",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
}
|
||||
],
|
||||
"appState": {
|
||||
"gridSize": 10,
|
||||
"viewBackgroundColor": "#ffffff",
|
||||
"currentItemFontFamily": 5
|
||||
},
|
||||
"files": {}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"harness_version": "0.2.0",
|
||||
"spec_id": "cartesian-row-multiplication",
|
||||
"spec_version": "1.1",
|
||||
"spec_sha256": "9561f8e6385ab7730a18c900c3977fbeb0e319e62195620afdadef2be0107bb6",
|
||||
"source_context": {
|
||||
"document": "docs/n+1liner/final/document.md",
|
||||
"document_sha256": "385ca44db5bf763cb1ff28a67d531d68876402b35d21dc54f2bc702e8bda0053",
|
||||
"anchor": {
|
||||
"kind": "heading",
|
||||
"value": "9.3 실패 ② 컬렉션 하나만 fetch join → 카테시안으로 전송 행수 증가",
|
||||
"line": 711
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
"cartesian-row-multiplication.svg",
|
||||
"cartesian-row-multiplication.drawio",
|
||||
"cartesian-row-multiplication.mmd",
|
||||
"cartesian-row-multiplication.d2",
|
||||
"cartesian-row-multiplication.dot",
|
||||
"cartesian-row-multiplication.excalidraw",
|
||||
"cartesian-row-multiplication.alt.md"
|
||||
],
|
||||
"lint_issue_count": 0,
|
||||
"assumption_count": 0,
|
||||
"assumptions_allowed": false,
|
||||
"composition_profile": "component-flow",
|
||||
"reference_ids": [
|
||||
"payment-event-flow"
|
||||
],
|
||||
"diagram_only": true
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
%% 컬렉션 하나만 fetch join하면 전송 행이 자식 수만큼 늘어난다
|
||||
%% question: 컬렉션 하나만 fetch join했을 때 DB가 전달하는 행은 왜 늘어나는가?
|
||||
flowchart LR
|
||||
n0["feed_items"]
|
||||
n1["highlights"]
|
||||
n2["highlights fetch join"]
|
||||
n3(["전송 조인 행"])
|
||||
n4["결과 리스트"]
|
||||
n0 -->|"부모 행"| n2
|
||||
n1 -->|"자식 행"| n2
|
||||
n2 -->|"카테시안 곱"| n3
|
||||
n3 -->|"루트 dedup"| n4
|
||||
@@ -0,0 +1,89 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1226" height="357" viewBox="0 0 1226 357" role="img" aria-labelledby="diagram-title diagram-description">
|
||||
<title id="diagram-title">컬렉션 하나만 fetch join하면 전송 행이 자식 수만큼 늘어난다</title>
|
||||
<desc id="diagram-description">왼쪽에서 feed_items와 highlights 두 테이블이 highlights fetch join으로 들어간다. 조인은 부모 행을 자식 수만큼 반복한 전송 조인 행을 만든다. 그 행이 결과 리스트로 넘어갈 때 Hibernate가 루트 엔티티를 중복 제거하므로 리스트 크기만 줄고 전송 단계의 카테시안은 남는다.</desc>
|
||||
<metadata>{"techviz":{"spec_version":"1.1","id":"cartesian-row-multiplication","profile":"component-flow"},"source_context":{"document":"docs/n+1liner/final/document.md","document_sha256":"385ca44db5bf763cb1ff28a67d531d68876402b35d21dc54f2bc702e8bda0053","anchor":{"kind":"heading","value":"9.3 실패 ② 컬렉션 하나만 fetch join → 카테시안으로 전송 행수 증가","line":711}},"evidence_policy":"Each factual element cites source lines or is marked assumption.","diagram_only":true}</metadata>
|
||||
<defs>
|
||||
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" />
|
||||
</marker>
|
||||
<style>
|
||||
:root { color-scheme: light; }
|
||||
text { font-family: Inter, Pretendard, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #111827; }
|
||||
.canvas { fill: #ffffff; }
|
||||
.group-box { fill: #ffffff; stroke: #9ca3af; stroke-width: 1.4; stroke-dasharray: 7 5; }
|
||||
.group-label-bg { fill: #ffffff; }
|
||||
.group-label { font-size: 13px; font-weight: 650; fill: #374151; }
|
||||
.edge { fill: none; stroke: #374151; stroke-width: 1.8; stroke-linejoin: round; stroke-linecap: round; marker-end: url(#arrow); }
|
||||
.edge.style-dashed, .edge.semantic-dashed, .edge.assumption { stroke-dasharray: 7 5; }
|
||||
.edge.style-dotted { stroke-dasharray: 2 5; }
|
||||
.edge.emphasis-primary { stroke: #2563eb; stroke-width: 2.2; }
|
||||
.edge.emphasis-muted { stroke: #9ca3af; }
|
||||
.edge.emphasis-warning, .edge.kind-failure, .edge.kind-error { stroke: #dc2626; stroke-width: 2.2; }
|
||||
.edge-label-bg { fill: #ffffff; }
|
||||
.edge-label { font-size: 12px; font-weight: 560; text-anchor: middle; }
|
||||
.node-shape { fill: #ffffff; stroke: #4b5563; stroke-width: 1.7; }
|
||||
.node-shape.emphasis-primary { stroke: #2563eb; stroke-width: 2.2; }
|
||||
.node-shape.emphasis-muted { stroke: #9ca3af; fill: #f9fafb; }
|
||||
.node-shape.emphasis-warning { stroke: #d97706; stroke-width: 2; fill: #fffdf5; }
|
||||
.node-shape.kind-database, .node-shape.kind-datastore, .node-shape.kind-storage { fill: #f8fafc; }
|
||||
.node-shape.kind-queue, .node-shape.kind-event, .node-shape.kind-topic { fill: #fafafa; }
|
||||
.node-shape.assumption { stroke-dasharray: 4 4; }
|
||||
.storage-bottom, .controller-divider { fill: none; stroke: #4b5563; stroke-width: 1.4; }
|
||||
.controller-led { fill: #4b5563; }
|
||||
.actor-symbol { fill: none; stroke: #4b5563; stroke-width: 1.8; stroke-linecap: round; }
|
||||
.actor-symbol.emphasis-primary { stroke: #2563eb; stroke-width: 2.2; }
|
||||
.node-label { font-size: 14px; font-weight: 650; text-anchor: middle; }
|
||||
.node-role { font-size: 10px; letter-spacing: 0.04em; text-anchor: middle; fill: #6b7280; }
|
||||
.node-detail-divider { stroke: #d1d5db; stroke-width: 1; }
|
||||
.node-detail { font-size: 11px; fill: #374151; }
|
||||
.assumption-badge { font-size: 9px; font-weight: 700; fill: #92400e; }
|
||||
.failure-mark { stroke: #dc2626; stroke-width: 4; stroke-linecap: round; }
|
||||
.lifeline { stroke: #9ca3af; stroke-width: 1.2; stroke-dasharray: 5 5; }
|
||||
.timeline-axis { stroke: #374151; stroke-width: 1.8; marker-end: url(#arrow); }
|
||||
.timeline-stem { stroke: #6b7280; stroke-width: 1.3; }
|
||||
.timeline-marker { fill: #ffffff; stroke: #374151; stroke-width: 1.7; }
|
||||
.timeline-marker.primary { fill: #2563eb; stroke: #2563eb; }
|
||||
.timeline-marker.warning { fill: #dc2626; stroke: #dc2626; }
|
||||
.timeline-label { font-size: 13px; font-weight: 650; text-anchor: middle; }
|
||||
.timeline-detail { font-size: 11px; fill: #4b5563; text-anchor: middle; }
|
||||
</style>
|
||||
</defs>
|
||||
<rect class="canvas" width="1226" height="357" />
|
||||
<polyline class="edge kind-read style-solid emphasis-normal" points="220.0,267.0 300.0,267.0 300.0,195.0 380.0,195.0" data-evidence="713-714" />
|
||||
<rect class="edge-label-bg" x="301.6" y="217.0" width="44.8" height="22" rx="3" />
|
||||
<text class="edge-label" x="324.0" y="232.0">자식 행</text>
|
||||
<polyline class="edge kind-transform style-dashed emphasis-normal" points="871.0,186.0 951.0,186.0 951.0,186.0 1031.0,186.0" data-evidence="717-719" />
|
||||
<rect class="edge-label-bg" x="915.2" y="144.0" width="71.6" height="22" rx="3" />
|
||||
<text class="edge-label" x="951.0" y="159.0">루트 dedup</text>
|
||||
<polyline class="edge kind-transfer style-solid emphasis-normal" points="561.0,186.0 641.0,186.0 641.0,186.0 721.0,186.0" data-evidence="713-715,732-734" />
|
||||
<rect class="edge-label-bg" x="611.9" y="144.0" width="58.2" height="22" rx="3" />
|
||||
<text class="edge-label" x="641.0" y="159.0">카테시안 곱</text>
|
||||
<polyline class="edge kind-read style-solid emphasis-normal" points="220.0,105.0 300.0,105.0 300.0,177.0 380.0,177.0" data-evidence="713-714" />
|
||||
<rect class="edge-label-bg" x="301.6" y="127.0" width="44.8" height="22" rx="3" />
|
||||
<text class="edge-label" x="324.0" y="142.0">부모 행</text>
|
||||
<g id="node-feed-items">
|
||||
<rect class="node-shape kind-store emphasis-normal role-store" data-evidence="713-714" x="70.0" y="73.0" width="150.0" height="64.0" /><ellipse class="node-shape kind-store emphasis-normal role-store" cx="145.0" cy="73.0" rx="75.0" ry="13.0" /><path class="storage-bottom" d="M 70.0 137.0 A 75.0 13.0 0 0 0 220.0 137.0" />
|
||||
<text class="node-label" x="145.0" y="103.0">feed_items</text>
|
||||
</g>
|
||||
<g id="node-highlights">
|
||||
<rect class="node-shape kind-store emphasis-normal role-store" data-evidence="713-714" x="70.0" y="235.0" width="150.0" height="64.0" /><ellipse class="node-shape kind-store emphasis-normal role-store" cx="145.0" cy="235.0" rx="75.0" ry="13.0" /><path class="storage-bottom" d="M 70.0 299.0 A 75.0 13.0 0 0 0 220.0 299.0" />
|
||||
<text class="node-label" x="145.0" y="265.0">highlights</text>
|
||||
</g>
|
||||
<g id="node-fetch-join">
|
||||
<rect class="node-shape kind-service emphasis-normal role-service" data-evidence="713-715" x="380.0" y="154.0" width="181.0" height="64.0" rx="7" />
|
||||
<text class="node-label" x="470.5" y="184.0">highlights fetch join</text>
|
||||
</g>
|
||||
<g id="node-transferred-rows">
|
||||
<rect class="node-shape kind-queue emphasis-primary role-queue" data-evidence="714-715,732-736" x="721.0" y="150.5" width="150.0" height="71.0" rx="7" />
|
||||
<text class="node-label" x="796.0" y="177.5">전송 조인 행</text>
|
||||
<line class="node-detail-divider" x1="735.0" y1="198.5" x2="857.0" y2="198.5" />
|
||||
<text class="node-detail" x="737.0" y="215.5">Σ highlights</text>
|
||||
</g>
|
||||
<g id="node-result-list">
|
||||
<rect class="node-shape kind-sink emphasis-normal role-sink" data-evidence="717-719" x="1031.0" y="150.5" width="150.0" height="71.0" rx="7" />
|
||||
<text class="node-label" x="1106.0" y="177.5">결과 리스트</text>
|
||||
<line class="node-detail-divider" x1="1045.0" y1="198.5" x2="1167.0" y2="198.5" />
|
||||
<text class="node-detail" x="1047.0" y="215.5">루트 엔티티 중복 제거</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.5 KiB |
@@ -0,0 +1,24 @@
|
||||
# EAGER 2차 조회는 반환 전에, LAZY highlights 조회는 매핑 접근 뒤에 실행된다
|
||||
|
||||
## Alternative text
|
||||
|
||||
loadFeed 매핑, Hibernate, PostgreSQL 사이에서 루트 SELECT, EAGER user·page 2차 SELECT, getHighlights 접근, LAZY highlights SELECT가 차례로 일어나는 시퀀스.
|
||||
|
||||
## Long description
|
||||
|
||||
세 참가자를 왼쪽부터 loadFeed DTO 매핑, Hibernate, PostgreSQL 순으로 읽는다. loadFeed가 findAllBy 파생 쿼리를 호출하면 Hibernate가 PostgreSQL에서 feed_items를 먼저 조회한다. 이어 fetch join되지 않은 EAGER user와 page를 별도의 2차 SELECT로 채우고, 반환 시점까지 로딩된 FeedItem을 loadFeed에 돌려준다. 이후 DTO 매핑이 getHighlights()에 접근하면 Hibernate가 해당 아이템의 highlights 컬렉션 SELECT를 실행한다. 이때 나가는 SQL 원문은 SELECT * FROM highlights WHERE feed_item_id = ? 이다.
|
||||
|
||||
## Elements and evidence
|
||||
|
||||
- **loadFeed DTO 매핑** (participant): FeedItem을 순회하며 응답 DTO를 조립하고 highlights 게터에 접근하는 호출자. Evidence: L356–L356, L519–L522.
|
||||
- **Hibernate** (participant): 파생 쿼리의 루트 조회와 EAGER 2차 SELECT, LAZY 컬렉션 초기화를 수행하는 JPA provider. Evidence: L354–L356.
|
||||
- **PostgreSQL** (participant): Hibernate가 루트 및 연관 SELECT를 실행하는 데이터베이스. Evidence: L480–L487, L515–L515.
|
||||
|
||||
## Relationships
|
||||
|
||||
- **loadFeed DTO 매핑 → Hibernate:** findAllBy(...). Evidence: L354–L354.
|
||||
- **Hibernate → PostgreSQL:** SELECT feed_items. Evidence: L354–L354.
|
||||
- **Hibernate → PostgreSQL:** SELECT user / page · EAGER 2차. Evidence: L352–L355.
|
||||
- **Hibernate → loadFeed DTO 매핑:** EAGER 연관이 채워진 FeedItem 반환. Evidence: L352–L354.
|
||||
- **loadFeed DTO 매핑 → Hibernate:** 매핑 중 getHighlights() 접근. Evidence: L356–L356, L519–L522.
|
||||
- **Hibernate → PostgreSQL:** SELECT highlights · LAZY 초기화. Evidence: L356–L356, L493–L495.
|
||||
@@ -0,0 +1,18 @@
|
||||
# EAGER 2차 조회는 반환 전에, LAZY highlights 조회는 매핑 접근 뒤에 실행된다
|
||||
# Question: 루트 피드 조회부터 EAGER ToOne과 LAZY highlights 조회까지 SQL은 어떤 순서로 발생하는가?
|
||||
direction: right
|
||||
n0: "loadFeed DTO 매핑" {
|
||||
shape: rectangle
|
||||
}
|
||||
n1: "Hibernate" {
|
||||
shape: rectangle
|
||||
}
|
||||
n2: "PostgreSQL" {
|
||||
shape: rectangle
|
||||
}
|
||||
n0 -> n1: "findAllBy(...)"
|
||||
n1 -> n2: "SELECT feed_items"
|
||||
n1 -> n2: "SELECT user / page · EAGER 2차"
|
||||
n1 -> n0: "EAGER 연관이 채워진 FeedItem 반환"
|
||||
n0 -> n1: "매핑 중 getHighlights() 접근"
|
||||
n1 -> n2: "SELECT highlights · LAZY 초기화"
|
||||
@@ -0,0 +1,14 @@
|
||||
digraph techviz {
|
||||
graph [rankdir=LR, splines=ortho, nodesep=0.55, ranksep=0.85];
|
||||
node [fontname=Helvetica, fontsize=11, margin="0.18,0.12", style="rounded,filled", fillcolor=white, color="#2d4357", penwidth=1.5];
|
||||
edge [fontname=Helvetica, fontsize=10, color="#364b5f", penwidth=1.4, arrowsize=0.75];
|
||||
n0 [label="loadFeed DTO 매핑", shape=box, style="rounded,filled"];
|
||||
n1 [label="Hibernate", shape=box, style="rounded,filled"];
|
||||
n2 [label="PostgreSQL", shape=box, style="rounded,filled"];
|
||||
n0 -> n1 [label="findAllBy(...)", style=solid];
|
||||
n1 -> n2 [label="SELECT feed_items", style=solid];
|
||||
n1 -> n2 [label="SELECT user / page · EAGER 2차", style=solid];
|
||||
n1 -> n0 [label="EAGER 연관이 채워진 FeedItem 반환", style=solid];
|
||||
n0 -> n1 [label="매핑 중 getHighlights() 접근", style=solid];
|
||||
n1 -> n2 [label="SELECT highlights · LAZY 초기화", style=solid];
|
||||
}
|
||||
@@ -5,14 +5,14 @@
|
||||
<root>
|
||||
<mxCell id="0"/>
|
||||
<mxCell id="1" parent="0"/>
|
||||
<mxCell id="n_load-feed-mapping" value="loadFeed DTO 매핑" tooltip="FeedItem을 순회하며 응답 DTO를 조립하고 highlights 게터에 접근하는 호출자. | Evidence: L322-L322, L449-L451" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxCell id="n_load-feed-mapping" value="loadFeed DTO 매핑" tooltip="FeedItem을 순회하며 응답 DTO를 조립하고 highlights 게터에 접근하는 호출자. | Evidence: L356-L356, L519-L522" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="45.0" y="35.0" width="150.0" height="64.0" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="n_hibernate" value="Hibernate" tooltip="파생 쿼리의 루트 조회와 EAGER 2차 SELECT, LAZY 컬렉션 초기화를 수행하는 JPA provider. | Evidence: L320-L322" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;strokeColor=#2563eb;strokeWidth=2;" vertex="1" parent="1">
|
||||
<mxCell id="n_hibernate" value="Hibernate" tooltip="파생 쿼리의 루트 조회와 EAGER 2차 SELECT, LAZY 컬렉션 초기화를 수행하는 JPA provider. | Evidence: L354-L356" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;strokeColor=#2563eb;strokeWidth=2;" vertex="1" parent="1">
|
||||
<mxGeometry x="255.0" y="35.0" width="150.0" height="64.0" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="n_postgresql" value="PostgreSQL" tooltip="Hibernate가 루트 및 연관 SELECT를 실행하는 데이터베이스. | Evidence: L417-L422, L447-L447" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;shape=cylinder3;boundedLbl=1;backgroundOutline=1;fillColor=#eef6fb;" vertex="1" parent="1">
|
||||
<mxGeometry x="465.0" y="35.0" width="150.0" height="64.0" as="geometry"/>
|
||||
<mxCell id="n_postgresql" value="PostgreSQL" tooltip="Hibernate가 루트 및 연관 SELECT를 실행하는 데이터베이스. | Evidence: L480-L487, L515-L515" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;shape=cylinder3;boundedLbl=1;backgroundOutline=1;fillColor=#eef6fb;" vertex="1" parent="1">
|
||||
<mxGeometry x="465.0" y="35.0" width="150.0" height="78.0" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="e_m1-find-all" value="findAllBy(...)" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_load-feed-mapping" target="n_hibernate">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
@@ -39,7 +39,7 @@
|
||||
<mxPoint x="225.0" y="376.0" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="e_m6-lazy-highlights-select" value="SELECT highlights WHERE feed_item_id = ?" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_hibernate" target="n_postgresql">
|
||||
<mxCell id="e_m6-lazy-highlights-select" value="SELECT highlights · LAZY 초기화" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_hibernate" target="n_postgresql">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="435.0" y="438.0" as="offset"/>
|
||||
</mxGeometry>
|
||||
|
||||
@@ -0,0 +1,766 @@
|
||||
{
|
||||
"type": "excalidraw",
|
||||
"version": 2,
|
||||
"source": "techviz-harness",
|
||||
"elements": [
|
||||
{
|
||||
"id": "edge-m1-find-all",
|
||||
"type": "arrow",
|
||||
"x": 120.0,
|
||||
"y": 140.0,
|
||||
"width": 210.0,
|
||||
"height": 0.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": null,
|
||||
"seed": 1466915521,
|
||||
"version": 1,
|
||||
"versionNonce": 619922749,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
210.0,
|
||||
0.0
|
||||
]
|
||||
],
|
||||
"lastCommittedPoint": null,
|
||||
"startBinding": {
|
||||
"elementId": "node-load-feed-mapping",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"endBinding": {
|
||||
"elementId": "node-hibernate",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow",
|
||||
"elbowed": true
|
||||
},
|
||||
{
|
||||
"id": "edge-label-m1-find-all",
|
||||
"type": "text",
|
||||
"x": 169.0,
|
||||
"y": 116.0,
|
||||
"width": 112,
|
||||
"height": 24,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1222640664,
|
||||
"version": 1,
|
||||
"versionNonce": 97953758,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 13,
|
||||
"fontFamily": 5,
|
||||
"text": "findAllBy(...)",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "findAllBy(...)",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "edge-m2-root-select",
|
||||
"type": "arrow",
|
||||
"x": 330.0,
|
||||
"y": 202.0,
|
||||
"width": 210.0,
|
||||
"height": 0.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": null,
|
||||
"seed": 1220139771,
|
||||
"version": 1,
|
||||
"versionNonce": 672496334,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
210.0,
|
||||
0.0
|
||||
]
|
||||
],
|
||||
"lastCommittedPoint": null,
|
||||
"startBinding": {
|
||||
"elementId": "node-hibernate",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"endBinding": {
|
||||
"elementId": "node-postgresql",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow",
|
||||
"elbowed": true
|
||||
},
|
||||
{
|
||||
"id": "edge-label-m2-root-select",
|
||||
"type": "text",
|
||||
"x": 367.0,
|
||||
"y": 178.0,
|
||||
"width": 136,
|
||||
"height": 24,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1575837228,
|
||||
"version": 1,
|
||||
"versionNonce": 421553536,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 13,
|
||||
"fontFamily": 5,
|
||||
"text": "SELECT feed_items",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "SELECT feed_items",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "edge-m3-eager-secondary-selects",
|
||||
"type": "arrow",
|
||||
"x": 330.0,
|
||||
"y": 264.0,
|
||||
"width": 210.0,
|
||||
"height": 0.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": null,
|
||||
"seed": 463896237,
|
||||
"version": 1,
|
||||
"versionNonce": 630479025,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
210.0,
|
||||
0.0
|
||||
]
|
||||
],
|
||||
"lastCommittedPoint": null,
|
||||
"startBinding": {
|
||||
"elementId": "node-hibernate",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"endBinding": {
|
||||
"elementId": "node-postgresql",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow",
|
||||
"elbowed": true
|
||||
},
|
||||
{
|
||||
"id": "edge-label-m3-eager-secondary-selects",
|
||||
"type": "text",
|
||||
"x": 319.0,
|
||||
"y": 240.0,
|
||||
"width": 232,
|
||||
"height": 24,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 341136983,
|
||||
"version": 1,
|
||||
"versionNonce": 137161213,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 13,
|
||||
"fontFamily": 5,
|
||||
"text": "SELECT user / page · EAGER 2차",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "SELECT user / page · EAGER 2차",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "edge-m4-return-eager-loaded-items",
|
||||
"type": "arrow",
|
||||
"x": 120.0,
|
||||
"y": 326.0,
|
||||
"width": 210.0,
|
||||
"height": 0.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": null,
|
||||
"seed": 452217845,
|
||||
"version": 1,
|
||||
"versionNonce": 1156366167,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[
|
||||
210.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
0.0,
|
||||
0.0
|
||||
]
|
||||
],
|
||||
"lastCommittedPoint": null,
|
||||
"startBinding": {
|
||||
"elementId": "node-hibernate",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"endBinding": {
|
||||
"elementId": "node-load-feed-mapping",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow",
|
||||
"elbowed": true
|
||||
},
|
||||
{
|
||||
"id": "edge-label-m4-return-eager-loaded-items",
|
||||
"type": "text",
|
||||
"x": 125.0,
|
||||
"y": 302.0,
|
||||
"width": 200,
|
||||
"height": 24,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 949125269,
|
||||
"version": 1,
|
||||
"versionNonce": 180813121,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 13,
|
||||
"fontFamily": 5,
|
||||
"text": "EAGER 연관이 채워진 FeedItem 반환",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "EAGER 연관이 채워진 FeedItem 반환",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "edge-m5-access-highlights",
|
||||
"type": "arrow",
|
||||
"x": 120.0,
|
||||
"y": 388.0,
|
||||
"width": 210.0,
|
||||
"height": 0.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": null,
|
||||
"seed": 179055340,
|
||||
"version": 1,
|
||||
"versionNonce": 1368520467,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
210.0,
|
||||
0.0
|
||||
]
|
||||
],
|
||||
"lastCommittedPoint": null,
|
||||
"startBinding": {
|
||||
"elementId": "node-load-feed-mapping",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"endBinding": {
|
||||
"elementId": "node-hibernate",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow",
|
||||
"elbowed": true
|
||||
},
|
||||
{
|
||||
"id": "edge-label-m5-access-highlights",
|
||||
"type": "text",
|
||||
"x": 133.0,
|
||||
"y": 364.0,
|
||||
"width": 184,
|
||||
"height": 24,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 217145704,
|
||||
"version": 1,
|
||||
"versionNonce": 908548325,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 13,
|
||||
"fontFamily": 5,
|
||||
"text": "매핑 중 getHighlights() 접근",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "매핑 중 getHighlights() 접근",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "edge-m6-lazy-highlights-select",
|
||||
"type": "arrow",
|
||||
"x": 330.0,
|
||||
"y": 450.0,
|
||||
"width": 210.0,
|
||||
"height": 0.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": null,
|
||||
"seed": 1936623542,
|
||||
"version": 1,
|
||||
"versionNonce": 1780861516,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
210.0,
|
||||
0.0
|
||||
]
|
||||
],
|
||||
"lastCommittedPoint": null,
|
||||
"startBinding": {
|
||||
"elementId": "node-hibernate",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"endBinding": {
|
||||
"elementId": "node-postgresql",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow",
|
||||
"elbowed": true
|
||||
},
|
||||
{
|
||||
"id": "edge-label-m6-lazy-highlights-select",
|
||||
"type": "text",
|
||||
"x": 323.0,
|
||||
"y": 426.0,
|
||||
"width": 224,
|
||||
"height": 24,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 723720611,
|
||||
"version": 1,
|
||||
"versionNonce": 367100783,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 13,
|
||||
"fontFamily": 5,
|
||||
"text": "SELECT highlights · LAZY 초기화",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "SELECT highlights · LAZY 초기화",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "node-load-feed-mapping",
|
||||
"type": "rectangle",
|
||||
"x": 45.0,
|
||||
"y": 35.0,
|
||||
"width": 150.0,
|
||||
"height": 64.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#ffffff",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1069507714,
|
||||
"version": 1,
|
||||
"versionNonce": 344023964,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "node-label-load-feed-mapping",
|
||||
"type": "text",
|
||||
"x": 55.0,
|
||||
"y": 45.0,
|
||||
"width": 130.0,
|
||||
"height": 44.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 349239,
|
||||
"version": 1,
|
||||
"versionNonce": 1687869941,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 15,
|
||||
"fontFamily": 5,
|
||||
"text": "loadFeed DTO 매핑",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "loadFeed DTO 매핑",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "node-hibernate",
|
||||
"type": "rectangle",
|
||||
"x": 255.0,
|
||||
"y": 35.0,
|
||||
"width": 150.0,
|
||||
"height": 64.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#ffffff",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1178876136,
|
||||
"version": 1,
|
||||
"versionNonce": 283384724,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "node-label-hibernate",
|
||||
"type": "text",
|
||||
"x": 265.0,
|
||||
"y": 45.0,
|
||||
"width": 130.0,
|
||||
"height": 44.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1294638406,
|
||||
"version": 1,
|
||||
"versionNonce": 238144117,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 15,
|
||||
"fontFamily": 5,
|
||||
"text": "Hibernate",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "Hibernate",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "node-postgresql",
|
||||
"type": "rectangle",
|
||||
"x": 465.0,
|
||||
"y": 35.0,
|
||||
"width": 150.0,
|
||||
"height": 78.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#ffffff",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 51288073,
|
||||
"version": 1,
|
||||
"versionNonce": 91686637,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "node-label-postgresql",
|
||||
"type": "text",
|
||||
"x": 475.0,
|
||||
"y": 45.0,
|
||||
"width": 130.0,
|
||||
"height": 58.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 308492034,
|
||||
"version": 1,
|
||||
"versionNonce": 936735075,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 15,
|
||||
"fontFamily": 5,
|
||||
"text": "PostgreSQL",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "PostgreSQL",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
}
|
||||
],
|
||||
"appState": {
|
||||
"gridSize": 10,
|
||||
"viewBackgroundColor": "#ffffff",
|
||||
"currentItemFontFamily": 5
|
||||
},
|
||||
"files": {}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"harness_version": "0.2.0",
|
||||
"spec_id": "eager-lazy-query-sequence",
|
||||
"spec_version": "1.1",
|
||||
"spec_sha256": "8ffd28206d556cb3e726ccb5e28f21ecaf9ceee63b2427d2a9cfb0d900b06898",
|
||||
"source_context": {
|
||||
"document": "docs/n+1liner/final/document.md",
|
||||
"document_sha256": "f9e048a68db0ab82078955bf611b06a71a0033539e0c87ff0aa0d620a3126e36",
|
||||
"anchor": {
|
||||
"kind": "heading",
|
||||
"value": "5.4 왜 추가 쿼리가 나가나 — EAGER는 \"로딩 시점\" 계약이지 JOIN 보장이 아니다",
|
||||
"line": 347
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
"eager-lazy-query-sequence.svg",
|
||||
"eager-lazy-query-sequence.drawio",
|
||||
"eager-lazy-query-sequence.mmd",
|
||||
"eager-lazy-query-sequence.d2",
|
||||
"eager-lazy-query-sequence.dot",
|
||||
"eager-lazy-query-sequence.excalidraw",
|
||||
"eager-lazy-query-sequence.alt.md"
|
||||
],
|
||||
"lint_issue_count": 0,
|
||||
"assumption_count": 0,
|
||||
"assumptions_allowed": false,
|
||||
"composition_profile": "sequence",
|
||||
"reference_ids": [
|
||||
"payment-approval-sequence"
|
||||
],
|
||||
"diagram_only": true
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
%% EAGER 2차 조회는 반환 전에, LAZY highlights 조회는 매핑 접근 뒤에 실행된다
|
||||
%% question: 루트 피드 조회부터 EAGER ToOne과 LAZY highlights 조회까지 SQL은 어떤 순서로 발생하는가?
|
||||
sequenceDiagram
|
||||
participant n0 as loadFeed DTO 매핑
|
||||
participant n1 as Hibernate
|
||||
participant n2 as PostgreSQL
|
||||
n0->>n1: findAllBy(...)
|
||||
n1->>n2: SELECT feed_items
|
||||
n1->>n2: SELECT user / page · EAGER 2차
|
||||
n1->>n0: EAGER 연관이 채워진 FeedItem 반환
|
||||
n0->>n1: 매핑 중 getHighlights() 접근
|
||||
n1->>n2: SELECT highlights · LAZY 초기화
|
||||
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="680" height="542" viewBox="0 0 680 542" role="img" aria-labelledby="diagram-title diagram-description">
|
||||
<title id="diagram-title">EAGER 2차 조회는 반환 전에, LAZY highlights 조회는 매핑 접근 뒤에 실행된다</title>
|
||||
<desc id="diagram-description">세 참가자를 왼쪽부터 loadFeed DTO 매핑, Hibernate, PostgreSQL 순으로 읽는다. loadFeed가 findAllBy 파생 쿼리를 호출하면 Hibernate가 PostgreSQL에서 feed_items를 먼저 조회한다. 이어 fetch join되지 않은 EAGER user와 page를 별도의 2차 SELECT로 채우고, 반환 시점까지 로딩된 FeedItem을 loadFeed에 돌려준다. 이후 DTO 매핑이 getHighlights()에 접근하면 Hibernate가 해당 아이템의 highlights 컬렉션 SELECT를 실행한다.</desc>
|
||||
<metadata>{"techviz":{"spec_version":"1.1","id":"eager-lazy-query-sequence","profile":"sequence"},"source_context":{"document":"/home/donghyeon/workspace/ai-tool/topic-arrange/n+1liner/n+1liner.md","document_sha256":"1bb38964ca2a849690f5355646c1aa988111b4cd4e1055c6cb05cf7e5fc35cde","anchor":{"kind":"marker","value":"eager-lazy-query-sequence","line":324}},"evidence_policy":"Each factual element cites source lines or is marked assumption.","diagram_only":true}</metadata>
|
||||
<desc id="diagram-description">세 참가자를 왼쪽부터 loadFeed DTO 매핑, Hibernate, PostgreSQL 순으로 읽는다. loadFeed가 findAllBy 파생 쿼리를 호출하면 Hibernate가 PostgreSQL에서 feed_items를 먼저 조회한다. 이어 fetch join되지 않은 EAGER user와 page를 별도의 2차 SELECT로 채우고, 반환 시점까지 로딩된 FeedItem을 loadFeed에 돌려준다. 이후 DTO 매핑이 getHighlights()에 접근하면 Hibernate가 해당 아이템의 highlights 컬렉션 SELECT를 실행한다. 이때 나가는 SQL 원문은 SELECT * FROM highlights WHERE feed_item_id = ? 이다.</desc>
|
||||
<metadata>{"techviz":{"spec_version":"1.1","id":"eager-lazy-query-sequence","profile":"sequence"},"source_context":{"document":"docs/n+1liner/final/document.md","document_sha256":"f9e048a68db0ab82078955bf611b06a71a0033539e0c87ff0aa0d620a3126e36","anchor":{"kind":"heading","value":"5.4 왜 추가 쿼리가 나가나 — EAGER는 \"로딩 시점\" 계약이지 JOIN 보장이 아니다","line":347}},"evidence_policy":"Each factual element cites source lines or is marked assumption.","diagram_only":true}</metadata>
|
||||
<defs>
|
||||
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" />
|
||||
@@ -50,31 +50,31 @@
|
||||
</style>
|
||||
</defs>
|
||||
<rect class="canvas" width="680" height="542" />
|
||||
<rect class="node-shape kind-participant emphasis-normal role-participant" data-evidence="322-322,449-451" x="45.0" y="35.0" width="150.0" height="64.0" rx="7" />
|
||||
<rect class="node-shape kind-participant emphasis-normal role-participant" data-evidence="356-356,519-522" x="45.0" y="35.0" width="150.0" height="64.0" rx="7" />
|
||||
<text class="node-label" x="120.0" y="65.0">loadFeed DTO 매핑</text>
|
||||
<line class="lifeline" x1="120.0" y1="99.0" x2="120.0" y2="512.0" />
|
||||
<rect class="node-shape kind-participant emphasis-primary role-participant" data-evidence="320-322" x="255.0" y="35.0" width="150.0" height="64.0" rx="7" />
|
||||
<rect class="node-shape kind-participant emphasis-primary role-participant" data-evidence="354-356" x="255.0" y="35.0" width="150.0" height="64.0" rx="7" />
|
||||
<text class="node-label" x="330.0" y="65.0">Hibernate</text>
|
||||
<line class="lifeline" x1="330.0" y1="99.0" x2="330.0" y2="512.0" />
|
||||
<rect class="node-shape kind-participant emphasis-normal role-participant" data-evidence="417-422,447-447" x="465.0" y="45.7" width="150.0" height="42.7" /><ellipse class="node-shape kind-participant emphasis-normal role-participant" cx="540.0" cy="45.7" rx="75.0" ry="10.7" /><path class="storage-bottom" d="M 465.0 88.3 A 75.0 10.7 0 0 0 615.0 88.3" />
|
||||
<text class="node-label" x="540.0" y="65.0">PostgreSQL</text>
|
||||
<line class="lifeline" x1="540.0" y1="99.0" x2="540.0" y2="512.0" />
|
||||
<polyline class="edge kind-request style-solid emphasis-normal" points="120.0,140.0 330.0,140.0" data-evidence="320-320" />
|
||||
<rect class="node-shape kind-participant emphasis-normal role-participant" data-evidence="480-487,515-515" x="465.0" y="48.0" width="150.0" height="52.0" /><ellipse class="node-shape kind-participant emphasis-normal role-participant" cx="540.0" cy="48.0" rx="75.0" ry="13.0" /><path class="storage-bottom" d="M 465.0 100.0 A 75.0 13.0 0 0 0 615.0 100.0" />
|
||||
<text class="node-label" x="540.0" y="75.0">PostgreSQL</text>
|
||||
<line class="lifeline" x1="540.0" y1="113.0" x2="540.0" y2="512.0" />
|
||||
<polyline class="edge kind-request style-solid emphasis-normal" points="120.0,140.0 330.0,140.0" data-evidence="354-354" />
|
||||
<rect class="edge-label-bg" x="159.1" y="114.0" width="131.9" height="22" rx="3" />
|
||||
<text class="edge-label" x="225.0" y="129.0">1. findAllBy(...)</text>
|
||||
<polyline class="edge kind-data style-solid emphasis-primary" points="330.0,202.0 540.0,202.0" data-evidence="320-320" />
|
||||
<polyline class="edge kind-data style-solid emphasis-primary" points="330.0,202.0 540.0,202.0" data-evidence="354-354" />
|
||||
<rect class="edge-label-bg" x="359.0" y="176.0" width="152.0" height="22" rx="3" />
|
||||
<text class="edge-label" x="435.0" y="191.0">2. SELECT feed_items</text>
|
||||
<polyline class="edge kind-data style-solid emphasis-normal" points="330.0,264.0 540.0,264.0" data-evidence="318-321" />
|
||||
<polyline class="edge kind-data style-solid emphasis-normal" points="330.0,264.0 540.0,264.0" data-evidence="352-355" />
|
||||
<rect class="edge-label-bg" x="318.8" y="238.0" width="232.4" height="22" rx="3" />
|
||||
<text class="edge-label" x="435.0" y="253.0">3. SELECT user / page · EAGER 2차</text>
|
||||
<polyline class="edge kind-response style-dashed emphasis-normal semantic-dashed" points="330.0,326.0 120.0,326.0" data-evidence="318-320" />
|
||||
<polyline class="edge kind-response style-dashed emphasis-normal semantic-dashed" points="330.0,326.0 120.0,326.0" data-evidence="352-354" />
|
||||
<rect class="edge-label-bg" x="122.2" y="300.0" width="205.6" height="22" rx="3" />
|
||||
<text class="edge-label" x="225.0" y="315.0">4. EAGER 연관이 채워진 FeedItem 반환</text>
|
||||
<polyline class="edge kind-request style-solid emphasis-normal" points="120.0,388.0 330.0,388.0" data-evidence="322-322,449-451" />
|
||||
<polyline class="edge kind-request style-solid emphasis-normal" points="120.0,388.0 330.0,388.0" data-evidence="356-356,519-522" />
|
||||
<rect class="edge-label-bg" x="128.9" y="362.0" width="192.2" height="22" rx="3" />
|
||||
<text class="edge-label" x="225.0" y="377.0">5. 매핑 중 getHighlights() 접근</text>
|
||||
<polyline class="edge kind-data style-solid emphasis-primary" points="330.0,450.0 540.0,450.0" data-evidence="322-322,429-430" />
|
||||
<rect class="edge-label-bg" x="281.9" y="424.0" width="306.1" height="22" rx="3" />
|
||||
<text class="edge-label" x="435.0" y="439.0">6. SELECT highlights WHERE feed_item_id = ?</text>
|
||||
<polyline class="edge kind-data style-solid emphasis-primary" points="330.0,450.0 540.0,450.0" data-evidence="356-356,493-495" />
|
||||
<rect class="edge-label-bg" x="322.1" y="424.0" width="225.7" height="22" rx="3" />
|
||||
<text class="edge-label" x="435.0" y="439.0">6. SELECT highlights · LAZY 초기화</text>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 7.5 KiB |
@@ -0,0 +1,20 @@
|
||||
# 반환 부모 수 N이 컬렉션 초기화와 자식 SELECT 횟수를 결정한다
|
||||
|
||||
## Alternative text
|
||||
|
||||
FeedItem N개를 반환하는 loadFeed 요청이 컬렉션 초기화 N회와 Highlight SELECT N회로 이어지는 인과 흐름도.
|
||||
|
||||
## Long description
|
||||
|
||||
왼쪽의 loadFeed 요청은 한 페이지에서 N개의 FeedItem을 반환한다. 매핑이 각 부모의 Highlight 컬렉션에 접근하면 컬렉션 초기화가 부모마다 한 번씩 일어나 총 N회가 된다. 현재 기준선에서는 배치나 서브셀렉트가 없어 초기화 한 번마다 Highlight SELECT도 한 번 실행되므로 추가 조회가 N회 발생한다. 각 SELECT는 해당 부모의 Highlight 자식 행을 전부 읽는다.
|
||||
|
||||
## Elements and evidence
|
||||
|
||||
- **loadFeed(0, N) → FeedItem N개** (request): 한 요청에서 반환한 부모 수 N이 N+1 증가 계수가 되는 피드 조회. Evidence: L382–L385, L455–L456.
|
||||
- **Highlight 컬렉션 초기화 N회** (operation): 각 FeedItem의 지연 컬렉션 접근이 부모마다 한 번의 초기화를 만든 결과. Evidence: L374–L375, L431–L431, L437–L439.
|
||||
- **Highlight SELECT N회** (query): 배치와 서브셀렉트가 없는 기준선에서 컬렉션 초기화마다 실행되는 자식 SELECT. Evidence: L374–L375, L437–L442.
|
||||
|
||||
## Relationships
|
||||
|
||||
- **Highlight 컬렉션 초기화 N회 → Highlight SELECT N회:** SELECT 실행. Evidence: L374–L375, L437–L439.
|
||||
- **loadFeed(0, N) → FeedItem N개 → Highlight 컬렉션 초기화 N회:** 컬렉션 접근. Evidence: L437–L439.
|
||||
@@ -0,0 +1,14 @@
|
||||
# 반환 부모 수 N이 컬렉션 초기화와 자식 SELECT 횟수를 결정한다
|
||||
# Question: 왜 한 번의 피드 요청에서 반환한 FeedItem 수 N이 Highlight 추가 조회 N회로 이어지는가?
|
||||
direction: right
|
||||
n0: "loadFeed(0, N) → FeedItem N개" {
|
||||
shape: rectangle
|
||||
}
|
||||
n1: "Highlight 컬렉션 초기화 N회" {
|
||||
shape: rectangle
|
||||
}
|
||||
n2: "Highlight SELECT N회" {
|
||||
shape: rectangle
|
||||
}
|
||||
n0 -> n1: "컬렉션 접근"
|
||||
n1 -> n2: "SELECT 실행"
|
||||
@@ -0,0 +1,10 @@
|
||||
digraph techviz {
|
||||
graph [rankdir=LR, splines=ortho, nodesep=0.55, ranksep=0.85];
|
||||
node [fontname=Helvetica, fontsize=11, margin="0.18,0.12", style="rounded,filled", fillcolor=white, color="#2d4357", penwidth=1.5];
|
||||
edge [fontname=Helvetica, fontsize=10, color="#364b5f", penwidth=1.4, arrowsize=0.75];
|
||||
n0 [label="loadFeed(0, N) → FeedItem N개", shape=box, style="rounded,filled"];
|
||||
n1 [label="Highlight 컬렉션 초기화 N회", shape=box, style="rounded,filled"];
|
||||
n2 [label="Highlight SELECT N회", shape=box, style="rounded,filled"];
|
||||
n0 -> n1 [label="컬렉션 접근", style=solid];
|
||||
n1 -> n2 [label="SELECT 실행", style=solid];
|
||||
}
|
||||
@@ -5,21 +5,21 @@
|
||||
<root>
|
||||
<mxCell id="0"/>
|
||||
<mxCell id="1" parent="0"/>
|
||||
<mxCell id="n_feed-request" value="loadFeed(0, N) → FeedItem N개<br/>page size = 반환 부모 수 N" tooltip="한 요청에서 반환한 부모 수 N이 N+1 증가 계수가 되는 피드 조회. | Evidence: L341-L341, L398-L398" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxCell id="n_feed-request" value="loadFeed(0, N) → FeedItem N개<br/>page size = 반환 부모 수 N" tooltip="한 요청에서 반환한 부모 수 N이 N+1 증가 계수가 되는 피드 조회. | Evidence: L382-L385, L455-L456" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="70.0" y="60.0" width="181.0" height="91.0" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="n_collection-initializations" value="Highlight 컬렉션 초기화 N회<br/>collectionFetches = N" tooltip="각 FeedItem의 지연 컬렉션 접근이 부모마다 한 번의 초기화를 만든 결과. | Evidence: L337-L337, L378-L384" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;strokeColor=#2563eb;strokeWidth=2;" vertex="1" parent="1">
|
||||
<mxCell id="n_collection-initializations" value="Highlight 컬렉션 초기화 N회<br/>collectionFetches = N" tooltip="각 FeedItem의 지연 컬렉션 접근이 부모마다 한 번의 초기화를 만든 결과. | Evidence: L374-L375, L431-L431, L437-L439" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;strokeColor=#2563eb;strokeWidth=2;" vertex="1" parent="1">
|
||||
<mxGeometry x="411.0" y="70.0" width="181.0" height="71.0" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="n_highlight-selects" value="Highlight SELECT N회<br/>부모별 자식 행 전부 조회" tooltip="배치와 서브셀렉트가 없는 기준선에서 컬렉션 초기화마다 실행되는 자식 SELECT. | Evidence: L337-L337, L384-L385" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxCell id="n_highlight-selects" value="Highlight SELECT N회<br/>부모별 자식 행 전부 조회" tooltip="배치와 서브셀렉트가 없는 기준선에서 컬렉션 초기화마다 실행되는 자식 SELECT. | Evidence: L374-L375, L437-L442" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="752.0" y="70.0" width="167.0" height="71.0" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="e_initialization-runs-select" value="초기화마다 SELECT 1회" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_collection-initializations" target="n_highlight-selects">
|
||||
<mxCell id="e_initialization-runs-select" value="SELECT 실행" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_collection-initializations" target="n_highlight-selects">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="672.0" y="77.5" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="e_parents-trigger-initialization" value="아이템마다 컬렉션 접근" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_feed-request" target="n_collection-initializations">
|
||||
<mxCell id="e_parents-trigger-initialization" value="컬렉션 접근" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_feed-request" target="n_collection-initializations">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="331.0" y="77.5" as="offset"/>
|
||||
</mxGeometry>
|
||||
|
||||
@@ -0,0 +1,418 @@
|
||||
{
|
||||
"type": "excalidraw",
|
||||
"version": 2,
|
||||
"source": "techviz-harness",
|
||||
"elements": [
|
||||
{
|
||||
"id": "edge-initialization-runs-select",
|
||||
"type": "arrow",
|
||||
"x": 592.0,
|
||||
"y": 105.5,
|
||||
"width": 160.0,
|
||||
"height": 0.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": null,
|
||||
"seed": 1318721908,
|
||||
"version": 1,
|
||||
"versionNonce": 1365968324,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
80.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
80.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
160.0,
|
||||
0.0
|
||||
]
|
||||
],
|
||||
"lastCommittedPoint": null,
|
||||
"startBinding": {
|
||||
"elementId": "node-collection-initializations",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"endBinding": {
|
||||
"elementId": "node-highlight-selects",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow",
|
||||
"elbowed": true
|
||||
},
|
||||
{
|
||||
"id": "edge-label-initialization-runs-select",
|
||||
"type": "text",
|
||||
"x": 627.0,
|
||||
"y": 65.5,
|
||||
"width": 90,
|
||||
"height": 24,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 243019414,
|
||||
"version": 1,
|
||||
"versionNonce": 55617789,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 13,
|
||||
"fontFamily": 5,
|
||||
"text": "SELECT 실행",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "SELECT 실행",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "edge-parents-trigger-initialization",
|
||||
"type": "arrow",
|
||||
"x": 251.0,
|
||||
"y": 105.5,
|
||||
"width": 160.0,
|
||||
"height": 0.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": null,
|
||||
"seed": 1302431294,
|
||||
"version": 1,
|
||||
"versionNonce": 1634581734,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
80.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
80.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
160.0,
|
||||
0.0
|
||||
]
|
||||
],
|
||||
"lastCommittedPoint": null,
|
||||
"startBinding": {
|
||||
"elementId": "node-feed-request",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"endBinding": {
|
||||
"elementId": "node-collection-initializations",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow",
|
||||
"elbowed": true
|
||||
},
|
||||
{
|
||||
"id": "edge-label-parents-trigger-initialization",
|
||||
"type": "text",
|
||||
"x": 286.0,
|
||||
"y": 65.5,
|
||||
"width": 90,
|
||||
"height": 24,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1896235428,
|
||||
"version": 1,
|
||||
"versionNonce": 97377696,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 13,
|
||||
"fontFamily": 5,
|
||||
"text": "컬렉션 접근",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "컬렉션 접근",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "node-feed-request",
|
||||
"type": "rectangle",
|
||||
"x": 70.0,
|
||||
"y": 60.0,
|
||||
"width": 181.0,
|
||||
"height": 91.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#ffffff",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1722945234,
|
||||
"version": 1,
|
||||
"versionNonce": 1732177805,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "node-label-feed-request",
|
||||
"type": "text",
|
||||
"x": 80.0,
|
||||
"y": 70.0,
|
||||
"width": 161.0,
|
||||
"height": 71.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 619718788,
|
||||
"version": 1,
|
||||
"versionNonce": 1655842096,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 15,
|
||||
"fontFamily": 5,
|
||||
"text": "loadFeed(0, N) → FeedItem N개\npage size = 반환 부모 수 N",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "loadFeed(0, N) → FeedItem N개\npage size = 반환 부모 수 N",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "node-collection-initializations",
|
||||
"type": "rectangle",
|
||||
"x": 411.0,
|
||||
"y": 70.0,
|
||||
"width": 181.0,
|
||||
"height": 71.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#ffffff",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 110379247,
|
||||
"version": 1,
|
||||
"versionNonce": 1063194376,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "node-label-collection-initializations",
|
||||
"type": "text",
|
||||
"x": 421.0,
|
||||
"y": 80.0,
|
||||
"width": 161.0,
|
||||
"height": 51.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 278060709,
|
||||
"version": 1,
|
||||
"versionNonce": 164287162,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 15,
|
||||
"fontFamily": 5,
|
||||
"text": "Highlight 컬렉션 초기화 N회\ncollectionFetches = N",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "Highlight 컬렉션 초기화 N회\ncollectionFetches = N",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "node-highlight-selects",
|
||||
"type": "rectangle",
|
||||
"x": 752.0,
|
||||
"y": 70.0,
|
||||
"width": 167.0,
|
||||
"height": 71.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#ffffff",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 901339311,
|
||||
"version": 1,
|
||||
"versionNonce": 545382502,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "node-label-highlight-selects",
|
||||
"type": "text",
|
||||
"x": 762.0,
|
||||
"y": 80.0,
|
||||
"width": 147.0,
|
||||
"height": 51.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 647185193,
|
||||
"version": 1,
|
||||
"versionNonce": 436143708,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 15,
|
||||
"fontFamily": 5,
|
||||
"text": "Highlight SELECT N회\n부모별 자식 행 전부 조회",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "Highlight SELECT N회\n부모별 자식 행 전부 조회",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
}
|
||||
],
|
||||
"appState": {
|
||||
"gridSize": 10,
|
||||
"viewBackgroundColor": "#ffffff",
|
||||
"currentItemFontFamily": 5
|
||||
},
|
||||
"files": {}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"harness_version": "0.2.0",
|
||||
"spec_id": "nplus1-query-fanout",
|
||||
"spec_version": "1.1",
|
||||
"spec_sha256": "4d4e299c1c63db78c22a369d0065beed6ca55a83a2c19a305b85bca7c9619b7d",
|
||||
"source_context": {
|
||||
"document": "docs/n+1liner/final/document.md",
|
||||
"document_sha256": "f9e048a68db0ab82078955bf611b06a71a0033539e0c87ff0aa0d620a3126e36",
|
||||
"anchor": {
|
||||
"kind": "heading",
|
||||
"value": "6.2 실측 — 조회량이 N에 정확히 비례한다",
|
||||
"line": 380
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
"nplus1-query-fanout.svg",
|
||||
"nplus1-query-fanout.drawio",
|
||||
"nplus1-query-fanout.mmd",
|
||||
"nplus1-query-fanout.d2",
|
||||
"nplus1-query-fanout.dot",
|
||||
"nplus1-query-fanout.excalidraw",
|
||||
"nplus1-query-fanout.alt.md"
|
||||
],
|
||||
"lint_issue_count": 0,
|
||||
"assumption_count": 0,
|
||||
"assumptions_allowed": false,
|
||||
"composition_profile": "component-flow",
|
||||
"reference_ids": [
|
||||
"payment-event-flow"
|
||||
],
|
||||
"diagram_only": true
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
%% 반환 부모 수 N이 컬렉션 초기화와 자식 SELECT 횟수를 결정한다
|
||||
%% question: 왜 한 번의 피드 요청에서 반환한 FeedItem 수 N이 Highlight 추가 조회 N회로 이어지는가?
|
||||
flowchart LR
|
||||
n0["loadFeed(0, N) → FeedItem N개"]
|
||||
n1["Highlight 컬렉션 초기화 N회"]
|
||||
n2["Highlight SELECT N회"]
|
||||
n0 -->|"컬렉션 접근"| n1
|
||||
n1 -->|"SELECT 실행"| n2
|
||||
@@ -2,7 +2,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="964" height="300" viewBox="0 0 964 300" role="img" aria-labelledby="diagram-title diagram-description">
|
||||
<title id="diagram-title">반환 부모 수 N이 컬렉션 초기화와 자식 SELECT 횟수를 결정한다</title>
|
||||
<desc id="diagram-description">왼쪽의 loadFeed 요청은 한 페이지에서 N개의 FeedItem을 반환한다. 매핑이 각 부모의 Highlight 컬렉션에 접근하면 컬렉션 초기화가 부모마다 한 번씩 일어나 총 N회가 된다. 현재 기준선에서는 배치나 서브셀렉트가 없어 초기화 한 번마다 Highlight SELECT도 한 번 실행되므로 추가 조회가 N회 발생한다. 각 SELECT는 해당 부모의 Highlight 자식 행을 전부 읽는다.</desc>
|
||||
<metadata>{"techviz":{"spec_version":"1.1","id":"nplus1-query-fanout","profile":"component-flow"},"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}},"evidence_policy":"Each factual element cites source lines or is marked assumption.","diagram_only":true}</metadata>
|
||||
<metadata>{"techviz":{"spec_version":"1.1","id":"nplus1-query-fanout","profile":"component-flow"},"source_context":{"document":"docs/n+1liner/final/document.md","document_sha256":"f9e048a68db0ab82078955bf611b06a71a0033539e0c87ff0aa0d620a3126e36","anchor":{"kind":"heading","value":"6.2 실측 — 조회량이 N에 정확히 비례한다","line":380}},"evidence_policy":"Each factual element cites source lines or is marked assumption.","diagram_only":true}</metadata>
|
||||
<defs>
|
||||
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" />
|
||||
@@ -50,27 +50,27 @@
|
||||
</style>
|
||||
</defs>
|
||||
<rect class="canvas" width="964" height="300" />
|
||||
<polyline class="edge kind-request style-solid emphasis-normal" points="592.0,105.5 672.0,105.5 672.0,105.5 752.0,105.5" data-evidence="337-337,384-384" />
|
||||
<rect class="edge-label-bg" x="612.8" y="63.5" width="118.5" height="22" rx="3" />
|
||||
<text class="edge-label" x="672.0" y="78.5">초기화마다 SELECT 1회</text>
|
||||
<polyline class="edge kind-request style-solid emphasis-primary" points="251.0,105.5 331.0,105.5 331.0,105.5 411.0,105.5" data-evidence="384-384" />
|
||||
<rect class="edge-label-bg" x="281.8" y="63.5" width="98.4" height="22" rx="3" />
|
||||
<text class="edge-label" x="331.0" y="78.5">아이템마다 컬렉션 접근</text>
|
||||
<polyline class="edge kind-request style-solid emphasis-normal" points="592.0,105.5 672.0,105.5 672.0,105.5 752.0,105.5" data-evidence="374-375,437-439" />
|
||||
<rect class="edge-label-bg" x="632.9" y="63.5" width="78.3" height="22" rx="3" />
|
||||
<text class="edge-label" x="672.0" y="78.5">SELECT 실행</text>
|
||||
<polyline class="edge kind-request style-solid emphasis-primary" points="251.0,105.5 331.0,105.5 331.0,105.5 411.0,105.5" data-evidence="437-439" />
|
||||
<rect class="edge-label-bg" x="301.9" y="63.5" width="58.2" height="22" rx="3" />
|
||||
<text class="edge-label" x="331.0" y="78.5">컬렉션 접근</text>
|
||||
<g id="node-feed-request">
|
||||
<rect class="node-shape kind-request emphasis-normal role-source" data-evidence="341-341,398-398" x="70.0" y="60.0" width="181.0" height="91.0" rx="7" />
|
||||
<rect class="node-shape kind-request emphasis-normal role-source" data-evidence="382-385,455-456" x="70.0" y="60.0" width="181.0" height="91.0" rx="7" />
|
||||
<text class="node-label" x="160.5" y="87.0">loadFeed(0, N) →</text>
|
||||
<text class="node-label" x="160.5" y="105.0">FeedItem N개</text>
|
||||
<line class="node-detail-divider" x1="84.0" y1="126.0" x2="237.0" y2="126.0" />
|
||||
<text class="node-detail" x="86.0" y="143.0">page size = 반환 부모 수 N</text>
|
||||
</g>
|
||||
<g id="node-collection-initializations">
|
||||
<rect class="node-shape kind-operation emphasis-primary role-service" data-evidence="337-337,378-384" x="411.0" y="70.0" width="181.0" height="71.0" rx="7" />
|
||||
<rect class="node-shape kind-operation emphasis-primary role-service" data-evidence="374-375,431-431,437-439" x="411.0" y="70.0" width="181.0" height="71.0" rx="7" />
|
||||
<text class="node-label" x="501.5" y="97.0">Highlight 컬렉션 초기화 N회</text>
|
||||
<line class="node-detail-divider" x1="425.0" y1="118.0" x2="578.0" y2="118.0" />
|
||||
<text class="node-detail" x="427.0" y="135.0">collectionFetches = N</text>
|
||||
</g>
|
||||
<g id="node-highlight-selects">
|
||||
<rect class="node-shape kind-query emphasis-normal role-sink" data-evidence="337-337,384-385" x="752.0" y="70.0" width="167.0" height="71.0" rx="7" />
|
||||
<rect class="node-shape kind-query emphasis-normal role-sink" data-evidence="374-375,437-442" x="752.0" y="70.0" width="167.0" height="71.0" rx="7" />
|
||||
<text class="node-label" x="835.5" y="97.0">Highlight SELECT N회</text>
|
||||
<line class="node-detail-divider" x1="766.0" y1="118.0" x2="905.0" y2="118.0" />
|
||||
<text class="node-detail" x="768.0" y="135.0">부모별 자식 행 전부 조회</text>
|
||||
|
||||
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.5 KiB |
@@ -0,0 +1,22 @@
|
||||
# 프로젝션은 엔티티 적재를 없앴지만 페이지당 자식 전량은 남긴다
|
||||
|
||||
## Alternative text
|
||||
|
||||
페이지 부모에서 자식 IN 조회를 거쳐 자식 행 전량이 나오고, 화면이 그중 부모별 최신 몇 개만 쓰는 흐름. IN 조회 상자에는 엔티티 적재가 없다는 표시가 붙어 있다.
|
||||
|
||||
## Long description
|
||||
|
||||
페이지 부모의 키가 자식 IN 조회로 들어간다. 이 조회는 엔티티를 만들지 않지만 페이지 부모의 하이라이트를 전부 가져온다. 화면이 실제로 쓰는 것은 부모별 최신 몇 개뿐이라 가져온 자식 행과 쓰는 자식 행 사이에 차이가 남는다.
|
||||
|
||||
## Elements and evidence
|
||||
|
||||
- **페이지 부모** (source): 첫 페이지에 실린 부모 행 Evidence: L1121–L1122.
|
||||
- **자식 IN 조회** (service): 스칼라 프로젝션으로 바꾼 자식 조회 Evidence: L1121–L1122.
|
||||
- **자식 행 전량** (store): IN 조회가 실제로 가져온 자식 행 Evidence: L1121–L1123.
|
||||
- **화면 사용분** (sink): 화면 렌더링에 필요한 범위 Evidence: L1124–L1125.
|
||||
|
||||
## Relationships
|
||||
|
||||
- **자식 IN 조회 → 자식 행 전량:** 부모별 전량 반환. Evidence: L1121–L1123.
|
||||
- **페이지 부모 → 자식 IN 조회:** 부모 키 IN. Evidence: L1121–L1122.
|
||||
- **자식 행 전량 → 화면 사용분:** 부분 사용. Evidence: L1124–L1125.
|
||||
@@ -0,0 +1,18 @@
|
||||
# 프로젝션은 엔티티 적재를 없앴지만 페이지당 자식 전량은 남긴다
|
||||
# Question: 스칼라 프로젝션으로 바꾼 뒤에도 남아 있는 과조회는 무엇인가?
|
||||
direction: down
|
||||
n0: "페이지 부모" {
|
||||
shape: rectangle
|
||||
}
|
||||
n1: "자식 IN 조회" {
|
||||
shape: rectangle
|
||||
}
|
||||
n2: "자식 행 전량" {
|
||||
shape: rectangle
|
||||
}
|
||||
n3: "화면 사용분" {
|
||||
shape: rectangle
|
||||
}
|
||||
n0 -> n1: "부모 키 IN"
|
||||
n1 -> n2: "부모별 전량 반환"
|
||||
n2 -> n3: "부분 사용"
|
||||
@@ -0,0 +1,12 @@
|
||||
digraph techviz {
|
||||
graph [rankdir=TB, splines=ortho, nodesep=0.55, ranksep=0.85];
|
||||
node [fontname=Helvetica, fontsize=11, margin="0.18,0.12", style="rounded,filled", fillcolor=white, color="#2d4357", penwidth=1.5];
|
||||
edge [fontname=Helvetica, fontsize=10, color="#364b5f", penwidth=1.4, arrowsize=0.75];
|
||||
n0 [label="페이지 부모", shape=box, style="rounded,filled"];
|
||||
n1 [label="자식 IN 조회", shape=box, style="rounded,filled"];
|
||||
n2 [label="자식 행 전량", shape=box, style="rounded,filled"];
|
||||
n3 [label="화면 사용분", shape=box, style="rounded,filled"];
|
||||
n0 -> n1 [label="부모 키 IN", style=solid];
|
||||
n1 -> n2 [label="부모별 전량 반환", style=solid];
|
||||
n2 -> n3 [label="부분 사용", style=solid];
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<mxfile host="app.diagrams.net" modified="2026-07-23T00:00:00.000Z" agent="techviz-harness" version="24.7.17" type="device">
|
||||
<diagram id="projection-row-over-fetch" name="프로젝션은 엔티티 적재를 없앴지만 페이지당 자식 전량은 남긴다">
|
||||
<mxGraphModel dx="680" dy="670" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0"/>
|
||||
<mxCell id="1" parent="0"/>
|
||||
<mxCell id="n_page-parents" value="페이지 부모" tooltip="첫 페이지에 실린 부모 행 | Evidence: L1121-L1122" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="70.0" y="60.0" width="150.0" height="64.0" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="n_child-in-query" value="자식 IN 조회<br/>엔티티 적재 없음" tooltip="스칼라 프로젝션으로 바꾼 자식 조회 | Evidence: L1121-L1122" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="70.0" y="220.0" width="150.0" height="71.0" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="n_fetched-child-rows" value="자식 행 전량<br/>페이지 부모 하이라이트 전량" tooltip="IN 조회가 실제로 가져온 자식 행 | Evidence: L1121-L1123" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;strokeColor=#2563eb;strokeWidth=2;" vertex="1" parent="1">
|
||||
<mxGeometry x="70.0" y="387.0" width="150.0" height="71.0" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="n_screen-need" value="화면 사용분<br/>부모당 최신 몇 개" tooltip="화면 렌더링에 필요한 범위 | Evidence: L1124-L1125" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="70.0" y="554.0" width="150.0" height="71.0" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="e_fetch-all" value="부모별 전량 반환" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_child-in-query" target="n_fetched-child-rows">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="169.0" y="339.0" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="e_keys-in" value="부모 키 IN" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_page-parents" target="n_child-in-query">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="169.0" y="172.0" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="e_use-some" value="부분 사용" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_fetched-child-rows" target="n_screen-need">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="169.0" y="506.0" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
@@ -0,0 +1,586 @@
|
||||
{
|
||||
"type": "excalidraw",
|
||||
"version": 2,
|
||||
"source": "techviz-harness",
|
||||
"elements": [
|
||||
{
|
||||
"id": "edge-fetch-all",
|
||||
"type": "arrow",
|
||||
"x": 145.0,
|
||||
"y": 291.0,
|
||||
"width": 0.0,
|
||||
"height": 96.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": null,
|
||||
"seed": 777449244,
|
||||
"version": 1,
|
||||
"versionNonce": 1300382628,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
0.0,
|
||||
48.0
|
||||
],
|
||||
[
|
||||
0.0,
|
||||
48.0
|
||||
],
|
||||
[
|
||||
0.0,
|
||||
96.0
|
||||
]
|
||||
],
|
||||
"lastCommittedPoint": null,
|
||||
"startBinding": {
|
||||
"elementId": "node-child-in-query",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"endBinding": {
|
||||
"elementId": "node-fetched-child-rows",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow",
|
||||
"elbowed": true
|
||||
},
|
||||
{
|
||||
"id": "edge-label-fetch-all",
|
||||
"type": "text",
|
||||
"x": 124.0,
|
||||
"y": 327.0,
|
||||
"width": 90,
|
||||
"height": 24,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1912781145,
|
||||
"version": 1,
|
||||
"versionNonce": 243274595,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 13,
|
||||
"fontFamily": 5,
|
||||
"text": "부모별 전량 반환",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "부모별 전량 반환",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "edge-keys-in",
|
||||
"type": "arrow",
|
||||
"x": 145.0,
|
||||
"y": 124.0,
|
||||
"width": 0.0,
|
||||
"height": 96.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": null,
|
||||
"seed": 845589567,
|
||||
"version": 1,
|
||||
"versionNonce": 439193190,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
0.0,
|
||||
48.0
|
||||
],
|
||||
[
|
||||
0.0,
|
||||
48.0
|
||||
],
|
||||
[
|
||||
0.0,
|
||||
96.0
|
||||
]
|
||||
],
|
||||
"lastCommittedPoint": null,
|
||||
"startBinding": {
|
||||
"elementId": "node-page-parents",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"endBinding": {
|
||||
"elementId": "node-child-in-query",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow",
|
||||
"elbowed": true
|
||||
},
|
||||
{
|
||||
"id": "edge-label-keys-in",
|
||||
"type": "text",
|
||||
"x": 124.0,
|
||||
"y": 160.0,
|
||||
"width": 90,
|
||||
"height": 24,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 579027069,
|
||||
"version": 1,
|
||||
"versionNonce": 1049382910,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 13,
|
||||
"fontFamily": 5,
|
||||
"text": "부모 키 IN",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "부모 키 IN",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "edge-use-some",
|
||||
"type": "arrow",
|
||||
"x": 145.0,
|
||||
"y": 458.0,
|
||||
"width": 0.0,
|
||||
"height": 96.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": null,
|
||||
"seed": 1507096350,
|
||||
"version": 1,
|
||||
"versionNonce": 18460885,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
0.0,
|
||||
48.0
|
||||
],
|
||||
[
|
||||
0.0,
|
||||
48.0
|
||||
],
|
||||
[
|
||||
0.0,
|
||||
96.0
|
||||
]
|
||||
],
|
||||
"lastCommittedPoint": null,
|
||||
"startBinding": {
|
||||
"elementId": "node-fetched-child-rows",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"endBinding": {
|
||||
"elementId": "node-screen-need",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow",
|
||||
"elbowed": true
|
||||
},
|
||||
{
|
||||
"id": "edge-label-use-some",
|
||||
"type": "text",
|
||||
"x": 124.0,
|
||||
"y": 494.0,
|
||||
"width": 90,
|
||||
"height": 24,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1248018755,
|
||||
"version": 1,
|
||||
"versionNonce": 421876168,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 13,
|
||||
"fontFamily": 5,
|
||||
"text": "부분 사용",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "부분 사용",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "node-page-parents",
|
||||
"type": "rectangle",
|
||||
"x": 70.0,
|
||||
"y": 60.0,
|
||||
"width": 150.0,
|
||||
"height": 64.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#ffffff",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 695857676,
|
||||
"version": 1,
|
||||
"versionNonce": 56382000,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "node-label-page-parents",
|
||||
"type": "text",
|
||||
"x": 80.0,
|
||||
"y": 70.0,
|
||||
"width": 130.0,
|
||||
"height": 44.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 158684589,
|
||||
"version": 1,
|
||||
"versionNonce": 1357041806,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 15,
|
||||
"fontFamily": 5,
|
||||
"text": "페이지 부모",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "페이지 부모",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "node-child-in-query",
|
||||
"type": "rectangle",
|
||||
"x": 70.0,
|
||||
"y": 220.0,
|
||||
"width": 150.0,
|
||||
"height": 71.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#ffffff",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 735458388,
|
||||
"version": 1,
|
||||
"versionNonce": 1783321748,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "node-label-child-in-query",
|
||||
"type": "text",
|
||||
"x": 80.0,
|
||||
"y": 230.0,
|
||||
"width": 130.0,
|
||||
"height": 51.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1457452421,
|
||||
"version": 1,
|
||||
"versionNonce": 179176367,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 15,
|
||||
"fontFamily": 5,
|
||||
"text": "자식 IN 조회\n엔티티 적재 없음",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "자식 IN 조회\n엔티티 적재 없음",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "node-fetched-child-rows",
|
||||
"type": "rectangle",
|
||||
"x": 70.0,
|
||||
"y": 387.0,
|
||||
"width": 150.0,
|
||||
"height": 71.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#ffffff",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 793283664,
|
||||
"version": 1,
|
||||
"versionNonce": 63200629,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "node-label-fetched-child-rows",
|
||||
"type": "text",
|
||||
"x": 80.0,
|
||||
"y": 397.0,
|
||||
"width": 130.0,
|
||||
"height": 51.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1281703641,
|
||||
"version": 1,
|
||||
"versionNonce": 622338496,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 15,
|
||||
"fontFamily": 5,
|
||||
"text": "자식 행 전량\n페이지 부모 하이라이트 전량",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "자식 행 전량\n페이지 부모 하이라이트 전량",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "node-screen-need",
|
||||
"type": "rectangle",
|
||||
"x": 70.0,
|
||||
"y": 554.0,
|
||||
"width": 150.0,
|
||||
"height": 71.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#ffffff",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1089914742,
|
||||
"version": 1,
|
||||
"versionNonce": 1072410109,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "node-label-screen-need",
|
||||
"type": "text",
|
||||
"x": 80.0,
|
||||
"y": 564.0,
|
||||
"width": 130.0,
|
||||
"height": 51.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1387436670,
|
||||
"version": 1,
|
||||
"versionNonce": 24284981,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 15,
|
||||
"fontFamily": 5,
|
||||
"text": "화면 사용분\n부모당 최신 몇 개",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "화면 사용분\n부모당 최신 몇 개",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
}
|
||||
],
|
||||
"appState": {
|
||||
"gridSize": 10,
|
||||
"viewBackgroundColor": "#ffffff",
|
||||
"currentItemFontFamily": 5
|
||||
},
|
||||
"files": {}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"harness_version": "0.2.0",
|
||||
"spec_id": "projection-row-over-fetch",
|
||||
"spec_version": "1.1",
|
||||
"spec_sha256": "95e98e0f2612364cafdb2d028dbdb17c0298021d7d717b490223f2ad7d4af97d",
|
||||
"source_context": {
|
||||
"document": "docs/n+1liner/final/document.md",
|
||||
"document_sha256": "385ca44db5bf763cb1ff28a67d531d68876402b35d21dc54f2bc702e8bda0053",
|
||||
"anchor": {
|
||||
"kind": "heading",
|
||||
"value": "12.6 프로젝션이 못 푸는 것 — 페이지당 전량",
|
||||
"line": 1119
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
"projection-row-over-fetch.svg",
|
||||
"projection-row-over-fetch.drawio",
|
||||
"projection-row-over-fetch.mmd",
|
||||
"projection-row-over-fetch.d2",
|
||||
"projection-row-over-fetch.dot",
|
||||
"projection-row-over-fetch.excalidraw",
|
||||
"projection-row-over-fetch.alt.md"
|
||||
],
|
||||
"lint_issue_count": 0,
|
||||
"assumption_count": 0,
|
||||
"assumptions_allowed": false,
|
||||
"composition_profile": "component-flow",
|
||||
"reference_ids": [
|
||||
"payment-event-flow"
|
||||
],
|
||||
"diagram_only": true
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
%% 프로젝션은 엔티티 적재를 없앴지만 페이지당 자식 전량은 남긴다
|
||||
%% question: 스칼라 프로젝션으로 바꾼 뒤에도 남아 있는 과조회는 무엇인가?
|
||||
flowchart TB
|
||||
n0["페이지 부모"]
|
||||
n1["자식 IN 조회"]
|
||||
n2["자식 행 전량"]
|
||||
n3["화면 사용분"]
|
||||
n0 -->|"부모 키 IN"| n1
|
||||
n1 -->|"부모별 전량 반환"| n2
|
||||
n2 -->|"부분 사용"| n3
|
||||
@@ -0,0 +1,84 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="680" height="670" viewBox="0 0 680 670" role="img" aria-labelledby="diagram-title diagram-description">
|
||||
<title id="diagram-title">프로젝션은 엔티티 적재를 없앴지만 페이지당 자식 전량은 남긴다</title>
|
||||
<desc id="diagram-description">페이지 부모의 키가 자식 IN 조회로 들어간다. 이 조회는 엔티티를 만들지 않지만 페이지 부모의 하이라이트를 전부 가져온다. 화면이 실제로 쓰는 것은 부모별 최신 몇 개뿐이라 가져온 자식 행과 쓰는 자식 행 사이에 차이가 남는다.</desc>
|
||||
<metadata>{"techviz":{"spec_version":"1.1","id":"projection-row-over-fetch","profile":"component-flow"},"source_context":{"document":"docs/n+1liner/final/document.md","document_sha256":"385ca44db5bf763cb1ff28a67d531d68876402b35d21dc54f2bc702e8bda0053","anchor":{"kind":"heading","value":"12.6 프로젝션이 못 푸는 것 — 페이지당 전량","line":1119}},"evidence_policy":"Each factual element cites source lines or is marked assumption.","diagram_only":true}</metadata>
|
||||
<defs>
|
||||
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" />
|
||||
</marker>
|
||||
<style>
|
||||
:root { color-scheme: light; }
|
||||
text { font-family: Inter, Pretendard, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #111827; }
|
||||
.canvas { fill: #ffffff; }
|
||||
.group-box { fill: #ffffff; stroke: #9ca3af; stroke-width: 1.4; stroke-dasharray: 7 5; }
|
||||
.group-label-bg { fill: #ffffff; }
|
||||
.group-label { font-size: 13px; font-weight: 650; fill: #374151; }
|
||||
.edge { fill: none; stroke: #374151; stroke-width: 1.8; stroke-linejoin: round; stroke-linecap: round; marker-end: url(#arrow); }
|
||||
.edge.style-dashed, .edge.semantic-dashed, .edge.assumption { stroke-dasharray: 7 5; }
|
||||
.edge.style-dotted { stroke-dasharray: 2 5; }
|
||||
.edge.emphasis-primary { stroke: #2563eb; stroke-width: 2.2; }
|
||||
.edge.emphasis-muted { stroke: #9ca3af; }
|
||||
.edge.emphasis-warning, .edge.kind-failure, .edge.kind-error { stroke: #dc2626; stroke-width: 2.2; }
|
||||
.edge-label-bg { fill: #ffffff; }
|
||||
.edge-label { font-size: 12px; font-weight: 560; text-anchor: middle; }
|
||||
.node-shape { fill: #ffffff; stroke: #4b5563; stroke-width: 1.7; }
|
||||
.node-shape.emphasis-primary { stroke: #2563eb; stroke-width: 2.2; }
|
||||
.node-shape.emphasis-muted { stroke: #9ca3af; fill: #f9fafb; }
|
||||
.node-shape.emphasis-warning { stroke: #d97706; stroke-width: 2; fill: #fffdf5; }
|
||||
.node-shape.kind-database, .node-shape.kind-datastore, .node-shape.kind-storage { fill: #f8fafc; }
|
||||
.node-shape.kind-queue, .node-shape.kind-event, .node-shape.kind-topic { fill: #fafafa; }
|
||||
.node-shape.assumption { stroke-dasharray: 4 4; }
|
||||
.storage-bottom, .controller-divider { fill: none; stroke: #4b5563; stroke-width: 1.4; }
|
||||
.controller-led { fill: #4b5563; }
|
||||
.actor-symbol { fill: none; stroke: #4b5563; stroke-width: 1.8; stroke-linecap: round; }
|
||||
.actor-symbol.emphasis-primary { stroke: #2563eb; stroke-width: 2.2; }
|
||||
.node-label { font-size: 14px; font-weight: 650; text-anchor: middle; }
|
||||
.node-role { font-size: 10px; letter-spacing: 0.04em; text-anchor: middle; fill: #6b7280; }
|
||||
.node-detail-divider { stroke: #d1d5db; stroke-width: 1; }
|
||||
.node-detail { font-size: 11px; fill: #374151; }
|
||||
.assumption-badge { font-size: 9px; font-weight: 700; fill: #92400e; }
|
||||
.failure-mark { stroke: #dc2626; stroke-width: 4; stroke-linecap: round; }
|
||||
.lifeline { stroke: #9ca3af; stroke-width: 1.2; stroke-dasharray: 5 5; }
|
||||
.timeline-axis { stroke: #374151; stroke-width: 1.8; marker-end: url(#arrow); }
|
||||
.timeline-stem { stroke: #6b7280; stroke-width: 1.3; }
|
||||
.timeline-marker { fill: #ffffff; stroke: #374151; stroke-width: 1.7; }
|
||||
.timeline-marker.primary { fill: #2563eb; stroke: #2563eb; }
|
||||
.timeline-marker.warning { fill: #dc2626; stroke: #dc2626; }
|
||||
.timeline-label { font-size: 13px; font-weight: 650; text-anchor: middle; }
|
||||
.timeline-detail { font-size: 11px; fill: #4b5563; text-anchor: middle; }
|
||||
</style>
|
||||
</defs>
|
||||
<rect class="canvas" width="680" height="670" />
|
||||
<polyline class="edge kind-response style-solid emphasis-normal semantic-dashed" points="145.0,291.0 145.0,339.0 145.0,339.0 145.0,387.0" data-evidence="1121-1123" />
|
||||
<rect class="edge-label-bg" x="129.8" y="325.0" width="78.3" height="22" rx="3" />
|
||||
<text class="edge-label" x="169.0" y="340.0">부모별 전량 반환</text>
|
||||
<polyline class="edge kind-query style-solid emphasis-normal" points="145.0,124.0 145.0,172.0 145.0,172.0 145.0,220.0" data-evidence="1121-1122" />
|
||||
<rect class="edge-label-bg" x="136.6" y="158.0" width="64.9" height="22" rx="3" />
|
||||
<text class="edge-label" x="169.0" y="173.0">부모 키 IN</text>
|
||||
<polyline class="edge kind-transform style-dashed emphasis-normal" points="145.0,458.0 145.0,506.0 145.0,506.0 145.0,554.0" data-evidence="1124-1125" />
|
||||
<rect class="edge-label-bg" x="143.2" y="492.0" width="51.5" height="22" rx="3" />
|
||||
<text class="edge-label" x="169.0" y="507.0">부분 사용</text>
|
||||
<g id="node-page-parents">
|
||||
<rect class="node-shape kind-source emphasis-normal role-source" data-evidence="1121-1122" x="70.0" y="60.0" width="150.0" height="64.0" rx="7" />
|
||||
<text class="node-label" x="145.0" y="90.0">페이지 부모</text>
|
||||
</g>
|
||||
<g id="node-child-in-query">
|
||||
<rect class="node-shape kind-service emphasis-normal role-service" data-evidence="1121-1122" x="70.0" y="220.0" width="150.0" height="71.0" rx="7" />
|
||||
<text class="node-label" x="145.0" y="247.0">자식 IN 조회</text>
|
||||
<line class="node-detail-divider" x1="84.0" y1="268.0" x2="206.0" y2="268.0" />
|
||||
<text class="node-detail" x="86.0" y="285.0">엔티티 적재 없음</text>
|
||||
</g>
|
||||
<g id="node-fetched-child-rows">
|
||||
<rect class="node-shape kind-store emphasis-primary role-store" data-evidence="1121-1123" x="70.0" y="387.0" width="150.0" height="71.0" rx="7" />
|
||||
<text class="node-label" x="145.0" y="414.0">자식 행 전량</text>
|
||||
<line class="node-detail-divider" x1="84.0" y1="435.0" x2="206.0" y2="435.0" />
|
||||
<text class="node-detail" x="86.0" y="452.0">페이지 부모 하이라이트 전량</text>
|
||||
</g>
|
||||
<g id="node-screen-need">
|
||||
<rect class="node-shape kind-sink emphasis-normal role-sink" data-evidence="1124-1125" x="70.0" y="554.0" width="150.0" height="71.0" rx="7" />
|
||||
<text class="node-label" x="145.0" y="581.0">화면 사용분</text>
|
||||
<line class="node-detail-divider" x1="84.0" y1="602.0" x2="206.0" y2="602.0" />
|
||||
<text class="node-detail" x="86.0" y="619.0">부모당 최신 몇 개</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.8 KiB |
@@ -0,0 +1,22 @@
|
||||
# 조회 전략은 FeedQueryPort 뒤의 퍼시스턴스 어댑터에 격리된다
|
||||
|
||||
## Alternative text
|
||||
|
||||
GET /feed를 받는 FeedController에서 GetFeedUseCase와 FeedQueryPort로 이어지고 FeedQueryAdapter가 포트를 구현하는 포트·어댑터 구조.
|
||||
|
||||
## Long description
|
||||
|
||||
왼쪽의 FeedController가 GET /feed 요청을 받아 중앙의 GetFeedUseCase에 조회를 위임한다. 유스케이스는 오른쪽의 FeedQueryPort에 조회를 의존한다. FeedQueryAdapter는 FeedQueryPort를 구현하는 아웃바운드 어댑터이며 PostgreSQL 조회를 수행한다. Fetch Join, Batch Fetch, DTO Projection, 윈도우 함수 같은 구체 전략은 이 어댑터의 책임이므로 상위 계층은 전략 교체의 영향을 받지 않는다.
|
||||
|
||||
## Elements and evidence
|
||||
|
||||
- **FeedController** (adapter): 조회 입력과 FeedSummary 반환 형태만 아는 웹 계층. Evidence: L323–L324.
|
||||
- **GetFeedUseCase** (application): 조회 사용자, 페이지 크기, FeedSummary 계약만 아는 애플리케이션 계층. Evidence: L321–L324.
|
||||
- **FeedQueryPort** (interface): 상위 계층과 구체 조회 전략을 분리하는 조회 포트. Evidence: L323–L325, L329–L330.
|
||||
- **FeedQueryAdapter** (adapter): FeedQueryPort를 구현하며 구체 조회 전략을 책임지는 퍼시스턴스 어댑터. Evidence: L323–L325, L329–L330.
|
||||
|
||||
## Relationships
|
||||
|
||||
- **FeedQueryAdapter → FeedQueryPort:** implements. Evidence: L324–L325.
|
||||
- **FeedController → GetFeedUseCase:** GET /feed 조회 위임. Evidence: L323–L324.
|
||||
- **GetFeedUseCase → FeedQueryPort:** 조회 의존. Evidence: L323–L324.
|
||||
@@ -0,0 +1,18 @@
|
||||
# 조회 전략은 FeedQueryPort 뒤의 퍼시스턴스 어댑터에 격리된다
|
||||
# Question: GET /feed 조회는 어떤 상위 계층을 거쳐 포트에 도달하며, 실제 조회 전략은 어디에 격리되는가?
|
||||
direction: right
|
||||
n0: "FeedController" {
|
||||
shape: rectangle
|
||||
}
|
||||
n1: "GetFeedUseCase" {
|
||||
shape: rectangle
|
||||
}
|
||||
n2: "FeedQueryPort" {
|
||||
shape: rectangle
|
||||
}
|
||||
n3: "FeedQueryAdapter" {
|
||||
shape: rectangle
|
||||
}
|
||||
n0 -> n1: "GET /feed 조회 위임"
|
||||
n1 -> n2: "조회 의존"
|
||||
n3 -> n2: "implements"
|
||||
@@ -0,0 +1,12 @@
|
||||
digraph techviz {
|
||||
graph [rankdir=LR, splines=ortho, nodesep=0.55, ranksep=0.85];
|
||||
node [fontname=Helvetica, fontsize=11, margin="0.18,0.12", style="rounded,filled", fillcolor=white, color="#2d4357", penwidth=1.5];
|
||||
edge [fontname=Helvetica, fontsize=10, color="#364b5f", penwidth=1.4, arrowsize=0.75];
|
||||
n0 [label="FeedController", shape=box, style="rounded,filled"];
|
||||
n1 [label="GetFeedUseCase", shape=box, style="rounded,filled"];
|
||||
n2 [label="FeedQueryPort", shape=box, style="rounded,filled"];
|
||||
n3 [label="FeedQueryAdapter", shape=box, style="rounded,filled"];
|
||||
n0 -> n1 [label="GET /feed 조회 위임", style=solid];
|
||||
n1 -> n2 [label="조회 의존", style=solid];
|
||||
n3 -> n2 [label="implements", style=solid];
|
||||
}
|
||||
@@ -5,16 +5,16 @@
|
||||
<root>
|
||||
<mxCell id="0"/>
|
||||
<mxCell id="1" parent="0"/>
|
||||
<mxCell id="n_get-feed-use-case" value="GetFeedUseCase" tooltip="조회 사용자, 페이지 크기, FeedSummary 계약만 아는 애플리케이션 계층. | Evidence: L295-L295" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;shape=hexagon;perimeter=hexagonPerimeter2;strokeColor=#2563eb;strokeWidth=2;" vertex="1" parent="1">
|
||||
<mxCell id="n_get-feed-use-case" value="GetFeedUseCase" tooltip="조회 사용자, 페이지 크기, FeedSummary 계약만 아는 애플리케이션 계층. | Evidence: L321-L324" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;shape=hexagon;perimeter=hexagonPerimeter2;strokeColor=#2563eb;strokeWidth=2;" vertex="1" parent="1">
|
||||
<mxGeometry x="420.0" y="185.0" width="230.0" height="130.0" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="n_feed-controller" value="FeedController<br/>GET /feed" tooltip="조회 입력과 FeedSummary 반환 형태만 아는 웹 계층. | Evidence: L295-L295" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxCell id="n_feed-controller" value="FeedController<br/>GET /feed" tooltip="조회 입력과 FeedSummary 반환 형태만 아는 웹 계층. | Evidence: L323-L324" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="45.0" y="85.0" width="210.0" height="94.0" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="n_feed-query-adapter" value="FeedQueryAdapter<br/>PostgreSQL 조회<br/>Fetch Join · Batch Fetch<br/>DTO Projection · 윈도우 함수" tooltip="FeedQueryPort를 구현하며 구체 조회 전략을 책임지는 퍼시스턴스 어댑터. | Evidence: L295-L295, L299-L299" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxCell id="n_feed-query-adapter" value="FeedQueryAdapter<br/>PostgreSQL 조회<br/>Fetch Join · Batch Fetch<br/>DTO Projection · 윈도우 함수" tooltip="FeedQueryPort를 구현하며 구체 조회 전략을 책임지는 퍼시스턴스 어댑터. | Evidence: L323-L325, L329-L330" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="825.0" y="85.0" width="220.0" height="126.0" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="n_feed-query-port" value="FeedQueryPort" tooltip="상위 계층과 구체 조회 전략을 분리하는 조회 포트. | Evidence: L295-L295, L299-L299" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;rounded=1;arcSize=50;fillColor=#f6f1fb;" vertex="1" parent="1">
|
||||
<mxCell id="n_feed-query-port" value="FeedQueryPort" tooltip="상위 계층과 구체 조회 전략을 분리하는 조회 포트. | Evidence: L323-L325, L329-L330" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;rounded=1;arcSize=50;fillColor=#f6f1fb;" vertex="1" parent="1">
|
||||
<mxGeometry x="670.0" y="155.0" width="125.0" height="58.0" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="e_adapter-implements-port" value="implements" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_feed-query-adapter" target="n_feed-query-port">
|
||||
|
||||
@@ -0,0 +1,594 @@
|
||||
{
|
||||
"type": "excalidraw",
|
||||
"version": 2,
|
||||
"source": "techviz-harness",
|
||||
"elements": [
|
||||
{
|
||||
"id": "edge-adapter-implements-port",
|
||||
"type": "arrow",
|
||||
"x": 732.5,
|
||||
"y": 211.0,
|
||||
"width": 202.5,
|
||||
"height": 48.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": null,
|
||||
"seed": 1777142792,
|
||||
"version": 1,
|
||||
"versionNonce": 210476259,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[
|
||||
202.5,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
202.5,
|
||||
48.0
|
||||
],
|
||||
[
|
||||
0.0,
|
||||
48.0
|
||||
],
|
||||
[
|
||||
0.0,
|
||||
2.0
|
||||
]
|
||||
],
|
||||
"lastCommittedPoint": null,
|
||||
"startBinding": {
|
||||
"elementId": "node-feed-query-adapter",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"endBinding": {
|
||||
"elementId": "node-feed-query-port",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow",
|
||||
"elbowed": true
|
||||
},
|
||||
{
|
||||
"id": "edge-label-adapter-implements-port",
|
||||
"type": "text",
|
||||
"x": 789.75,
|
||||
"y": 219.0,
|
||||
"width": 90,
|
||||
"height": 24,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 784346515,
|
||||
"version": 1,
|
||||
"versionNonce": 1186202715,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 13,
|
||||
"fontFamily": 5,
|
||||
"text": "implements",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "implements",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "edge-controller-to-use-case",
|
||||
"type": "arrow",
|
||||
"x": 255.0,
|
||||
"y": 132.0,
|
||||
"width": 165.0,
|
||||
"height": 118.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": null,
|
||||
"seed": 1068662408,
|
||||
"version": 1,
|
||||
"versionNonce": 213078762,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
82.5,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
82.5,
|
||||
118.0
|
||||
],
|
||||
[
|
||||
165.0,
|
||||
118.0
|
||||
]
|
||||
],
|
||||
"lastCommittedPoint": null,
|
||||
"startBinding": {
|
||||
"elementId": "node-feed-controller",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"endBinding": {
|
||||
"elementId": "node-get-feed-use-case",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow",
|
||||
"elbowed": true
|
||||
},
|
||||
{
|
||||
"id": "edge-label-controller-to-use-case",
|
||||
"type": "text",
|
||||
"x": 301.5,
|
||||
"y": 179.0,
|
||||
"width": 120,
|
||||
"height": 24,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1964586448,
|
||||
"version": 1,
|
||||
"versionNonce": 1689984318,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 13,
|
||||
"fontFamily": 5,
|
||||
"text": "GET /feed 조회 위임",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "GET /feed 조회 위임",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "edge-use-case-to-port",
|
||||
"type": "arrow",
|
||||
"x": 640.0,
|
||||
"y": 119.0,
|
||||
"width": 40.0,
|
||||
"height": 131.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": null,
|
||||
"seed": 1760759207,
|
||||
"version": 1,
|
||||
"versionNonce": 39532668,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[
|
||||
10.0,
|
||||
131.0
|
||||
],
|
||||
[
|
||||
40.0,
|
||||
131.0
|
||||
],
|
||||
[
|
||||
40.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
0.0,
|
||||
65.0
|
||||
],
|
||||
[
|
||||
30.0,
|
||||
65.0
|
||||
]
|
||||
],
|
||||
"lastCommittedPoint": null,
|
||||
"startBinding": {
|
||||
"elementId": "node-get-feed-use-case",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"endBinding": {
|
||||
"elementId": "node-feed-query-port",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow",
|
||||
"elbowed": true
|
||||
},
|
||||
{
|
||||
"id": "edge-label-use-case-to-port",
|
||||
"type": "text",
|
||||
"x": 659.0,
|
||||
"y": 120.0,
|
||||
"width": 90,
|
||||
"height": 24,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1184230128,
|
||||
"version": 1,
|
||||
"versionNonce": 350650885,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 13,
|
||||
"fontFamily": 5,
|
||||
"text": "조회 의존",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "조회 의존",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "node-get-feed-use-case",
|
||||
"type": "rectangle",
|
||||
"x": 420.0,
|
||||
"y": 185.0,
|
||||
"width": 230.0,
|
||||
"height": 130.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#ffffff",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1013343996,
|
||||
"version": 1,
|
||||
"versionNonce": 1852011614,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "node-label-get-feed-use-case",
|
||||
"type": "text",
|
||||
"x": 430.0,
|
||||
"y": 195.0,
|
||||
"width": 210.0,
|
||||
"height": 110.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 692030406,
|
||||
"version": 1,
|
||||
"versionNonce": 302603492,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 15,
|
||||
"fontFamily": 5,
|
||||
"text": "GetFeedUseCase",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "GetFeedUseCase",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "node-feed-controller",
|
||||
"type": "rectangle",
|
||||
"x": 45.0,
|
||||
"y": 85.0,
|
||||
"width": 210.0,
|
||||
"height": 94.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#ffffff",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 862823723,
|
||||
"version": 1,
|
||||
"versionNonce": 1007435196,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "node-label-feed-controller",
|
||||
"type": "text",
|
||||
"x": 55.0,
|
||||
"y": 95.0,
|
||||
"width": 190.0,
|
||||
"height": 74.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1009068783,
|
||||
"version": 1,
|
||||
"versionNonce": 1697147702,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 15,
|
||||
"fontFamily": 5,
|
||||
"text": "FeedController\nGET /feed",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "FeedController\nGET /feed",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "node-feed-query-adapter",
|
||||
"type": "rectangle",
|
||||
"x": 825.0,
|
||||
"y": 85.0,
|
||||
"width": 220.0,
|
||||
"height": 126.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#ffffff",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1319244181,
|
||||
"version": 1,
|
||||
"versionNonce": 1104184560,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "node-label-feed-query-adapter",
|
||||
"type": "text",
|
||||
"x": 835.0,
|
||||
"y": 95.0,
|
||||
"width": 200.0,
|
||||
"height": 106.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1895645289,
|
||||
"version": 1,
|
||||
"versionNonce": 873497168,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 15,
|
||||
"fontFamily": 5,
|
||||
"text": "FeedQueryAdapter\nPostgreSQL 조회\nFetch Join · Batch Fetch\nDTO Projection · 윈도우 함수",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "FeedQueryAdapter\nPostgreSQL 조회\nFetch Join · Batch Fetch\nDTO Projection · 윈도우 함수",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "node-feed-query-port",
|
||||
"type": "rectangle",
|
||||
"x": 670.0,
|
||||
"y": 155.0,
|
||||
"width": 125.0,
|
||||
"height": 58.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#ffffff",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1666315225,
|
||||
"version": 1,
|
||||
"versionNonce": 861140725,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "node-label-feed-query-port",
|
||||
"type": "text",
|
||||
"x": 680.0,
|
||||
"y": 165.0,
|
||||
"width": 105.0,
|
||||
"height": 38.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 692866467,
|
||||
"version": 1,
|
||||
"versionNonce": 250579912,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 15,
|
||||
"fontFamily": 5,
|
||||
"text": "FeedQueryPort",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "FeedQueryPort",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
}
|
||||
],
|
||||
"appState": {
|
||||
"gridSize": 10,
|
||||
"viewBackgroundColor": "#ffffff",
|
||||
"currentItemFontFamily": 5
|
||||
},
|
||||
"files": {}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"harness_version": "0.2.0",
|
||||
"spec_id": "query-port-boundary",
|
||||
"spec_version": "1.1",
|
||||
"spec_sha256": "7ca0fd6c2ed79efdd0ead574b3ad31e7c43d902d049c43e1f4884e9e8d0c1efe",
|
||||
"source_context": {
|
||||
"document": "docs/n+1liner/final/document.md",
|
||||
"document_sha256": "f9e048a68db0ab82078955bf611b06a71a0033539e0c87ff0aa0d620a3126e36",
|
||||
"anchor": {
|
||||
"kind": "heading",
|
||||
"value": "5.2 조회 전략은 포트 뒤 어댑터의 책임",
|
||||
"line": 319
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
"query-port-boundary.svg",
|
||||
"query-port-boundary.drawio",
|
||||
"query-port-boundary.mmd",
|
||||
"query-port-boundary.d2",
|
||||
"query-port-boundary.dot",
|
||||
"query-port-boundary.excalidraw",
|
||||
"query-port-boundary.alt.md"
|
||||
],
|
||||
"lint_issue_count": 0,
|
||||
"assumption_count": 0,
|
||||
"assumptions_allowed": false,
|
||||
"composition_profile": "ports-adapters",
|
||||
"reference_ids": [
|
||||
"order-ports-adapters"
|
||||
],
|
||||
"diagram_only": true
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
%% 조회 전략은 FeedQueryPort 뒤의 퍼시스턴스 어댑터에 격리된다
|
||||
%% question: GET /feed 조회는 어떤 상위 계층을 거쳐 포트에 도달하며, 실제 조회 전략은 어디에 격리되는가?
|
||||
flowchart LR
|
||||
n0["FeedController"]
|
||||
n1["GetFeedUseCase"]
|
||||
n2["FeedQueryPort"]
|
||||
n3["FeedQueryAdapter"]
|
||||
n0 -->|"GET /feed 조회 위임"| n1
|
||||
n1 -->|"조회 의존"| n2
|
||||
n3 -->|"implements"| n2
|
||||
@@ -2,7 +2,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1090" height="360" viewBox="0 0 1090 360" role="img" aria-labelledby="diagram-title diagram-description">
|
||||
<title id="diagram-title">조회 전략은 FeedQueryPort 뒤의 퍼시스턴스 어댑터에 격리된다</title>
|
||||
<desc id="diagram-description">왼쪽의 FeedController가 GET /feed 요청을 받아 중앙의 GetFeedUseCase에 조회를 위임한다. 유스케이스는 오른쪽의 FeedQueryPort에 조회를 의존한다. FeedQueryAdapter는 FeedQueryPort를 구현하는 아웃바운드 어댑터이며 PostgreSQL 조회를 수행한다. Fetch Join, Batch Fetch, DTO Projection, 윈도우 함수 같은 구체 전략은 이 어댑터의 책임이므로 상위 계층은 전략 교체의 영향을 받지 않는다.</desc>
|
||||
<metadata>{"techviz":{"spec_version":"1.1","id":"query-port-boundary","profile":"ports-adapters"},"source_context":{"document":"/home/donghyeon/workspace/ai-tool/topic-arrange/n+1liner/n+1liner.md","document_sha256":"1bb38964ca2a849690f5355646c1aa988111b4cd4e1055c6cb05cf7e5fc35cde","anchor":{"kind":"marker","value":"query-port-boundary","line":297}},"evidence_policy":"Each factual element cites source lines or is marked assumption.","diagram_only":true}</metadata>
|
||||
<metadata>{"techviz":{"spec_version":"1.1","id":"query-port-boundary","profile":"ports-adapters"},"source_context":{"document":"docs/n+1liner/final/document.md","document_sha256":"f9e048a68db0ab82078955bf611b06a71a0033539e0c87ff0aa0d620a3126e36","anchor":{"kind":"heading","value":"5.2 조회 전략은 포트 뒤 어댑터의 책임","line":319}},"evidence_policy":"Each factual element cites source lines or is marked assumption.","diagram_only":true}</metadata>
|
||||
<defs>
|
||||
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" />
|
||||
@@ -50,29 +50,29 @@
|
||||
</style>
|
||||
</defs>
|
||||
<rect class="canvas" width="1090" height="360" />
|
||||
<polyline class="edge kind-dependency style-solid emphasis-normal" points="935.0,211.0 935.0,259.0 732.5,259.0 732.5,213.0" data-evidence="295-295" />
|
||||
<polyline class="edge kind-dependency style-solid emphasis-normal" points="935.0,211.0 935.0,259.0 732.5,259.0 732.5,213.0" data-evidence="324-325" />
|
||||
<rect class="edge-label-bg" x="792.2" y="217.0" width="85.0" height="22" rx="3" />
|
||||
<text class="edge-label" x="834.8" y="232.0">implements</text>
|
||||
<polyline class="edge kind-request style-solid emphasis-primary" points="255.0,132.0 337.5,132.0 337.5,250.0 420.0,250.0" data-evidence="295-295" />
|
||||
<polyline class="edge kind-request style-solid emphasis-primary" points="255.0,132.0 337.5,132.0 337.5,250.0 420.0,250.0" data-evidence="323-324" />
|
||||
<rect class="edge-label-bg" x="302.2" y="177.0" width="118.5" height="22" rx="3" />
|
||||
<text class="edge-label" x="361.5" y="192.0">GET /feed 조회 위임</text>
|
||||
<polyline class="edge kind-dependency style-solid emphasis-normal" points="650.0,250.0 680.0,250.0 680.0,119.0 640.0,119.0 640.0,184.0 670.0,184.0" data-evidence="295-295" />
|
||||
<polyline class="edge kind-dependency style-solid emphasis-normal" points="650.0,250.0 680.0,250.0 680.0,119.0 640.0,119.0 640.0,184.0 670.0,184.0" data-evidence="323-324" />
|
||||
<rect class="edge-label-bg" x="678.2" y="118.0" width="51.5" height="22" rx="3" />
|
||||
<text class="edge-label" x="704.0" y="133.0">조회 의존</text>
|
||||
<g id="node-get-feed-use-case">
|
||||
<polygon class="node-shape kind-application emphasis-primary role-core" data-evidence="295-295" points="454.0,185.0 616.0,185.0 650.0,250.0 616.0,315.0 454.0,315.0 420.0,250.0" />
|
||||
<polygon class="node-shape kind-application emphasis-primary role-core" data-evidence="321-324" points="454.0,185.0 616.0,185.0 650.0,250.0 616.0,315.0 454.0,315.0 420.0,250.0" />
|
||||
<text class="node-role" x="535.0" y="204.0">«core»</text>
|
||||
<text class="node-label" x="535.0" y="248.0">GetFeedUseCase</text>
|
||||
</g>
|
||||
<g id="node-feed-controller">
|
||||
<rect class="node-shape kind-adapter emphasis-normal role-inbound-adapter" data-evidence="295-295" x="45.0" y="85.0" width="210.0" height="94.0" rx="7" />
|
||||
<rect class="node-shape kind-adapter emphasis-normal role-inbound-adapter" data-evidence="323-324" x="45.0" y="85.0" width="210.0" height="94.0" rx="7" />
|
||||
<text class="node-role" x="150.0" y="104.0">«inbound-adapter»</text>
|
||||
<text class="node-label" x="150.0" y="131.0">FeedController</text>
|
||||
<line class="node-detail-divider" x1="59.0" y1="152.0" x2="241.0" y2="152.0" />
|
||||
<text class="node-detail" x="61.0" y="169.0">GET /feed</text>
|
||||
</g>
|
||||
<g id="node-feed-query-adapter">
|
||||
<rect class="node-shape kind-adapter emphasis-normal role-outbound-adapter" data-evidence="295-295,299-299" x="825.0" y="85.0" width="220.0" height="126.0" rx="7" />
|
||||
<rect class="node-shape kind-adapter emphasis-normal role-outbound-adapter" data-evidence="323-325,329-330" x="825.0" y="85.0" width="220.0" height="126.0" rx="7" />
|
||||
<text class="node-role" x="935.0" y="104.0">«outbound-adapter»</text>
|
||||
<text class="node-label" x="935.0" y="131.0">FeedQueryAdapter</text>
|
||||
<line class="node-detail-divider" x1="839.0" y1="152.0" x2="1031.0" y2="152.0" />
|
||||
@@ -81,7 +81,7 @@
|
||||
<text class="node-detail" x="841.0" y="201.0">DTO Projection · 윈도우 함수</text>
|
||||
</g>
|
||||
<g id="node-feed-query-port">
|
||||
<rect class="node-shape kind-interface emphasis-normal role-port" data-evidence="295-295,299-299" x="670.0" y="155.0" width="125.0" height="58.0" rx="22" />
|
||||
<rect class="node-shape kind-interface emphasis-normal role-port" data-evidence="323-325,329-330" x="670.0" y="155.0" width="125.0" height="58.0" rx="22" />
|
||||
<text class="node-role" x="732.5" y="174.0">«port»</text>
|
||||
<text class="node-label" x="732.5" y="201.0">FeedQueryPort</text>
|
||||
</g>
|
||||
|
||||
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 7.2 KiB |
@@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<mxfile host="app.diagrams.net" modified="2026-07-23T00:00:00.000Z" agent="techviz-harness" version="24.7.17" type="device">
|
||||
<diagram id="skew-profile" name="Zipf-like 분포만 무거운 머리와 긴 꼬리를 함께 재현한다">
|
||||
<mxGraphModel dx="854" dy="300" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="854" pageHeight="1169" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0"/>
|
||||
<mxCell id="1" parent="0"/>
|
||||
<mxCell id="n_uniform" value="균일분포<br/>분포 형태: 모두 3개<br/>극단적 소수: 없음<br/>스트레스 조건: 재현 못함<br/>선택 결과: 제외" tooltip="모든 아이템에 하이라이트 3개를 주는 분포로, 매우 많은 하이라이트 조건을 재현하지 못한다. | Evidence: L177-L177, L195-L195" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;strokeColor=#9ca3af;fillColor=#f9fafb;" vertex="1" parent="1">
|
||||
<mxGeometry x="45.0" y="45.0" width="220.0" height="122.0" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="n_normal" value="정규분포<br/>분포 형태: 평균 근처 집중<br/>극단적 소수: 없음<br/>스트레스 조건: 재현 못함<br/>선택 결과: 제외" tooltip="평균 근처에 몰려 극단적으로 많은 소수와 무거운 머리를 만들지 못하는 분포다. | Evidence: L177-L177, L196-L196" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;strokeColor=#9ca3af;fillColor=#f9fafb;" vertex="1" parent="1">
|
||||
<mxGeometry x="317.0" y="45.0" width="220.0" height="122.0" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="n_zipf-like" value="Zipf-like 합성 분포<br/>분포 형태: 무거운 머리 + 긴 꼬리<br/>극단적 소수: 있음 · 1위 500개<br/>스트레스 조건: 재현<br/>선택 결과: s=1.15 합성 분포" tooltip="순위 기반 지수로 편중 강도를 조절하며 무거운 머리와 긴 꼬리를 재현하는 합성 스트레스 분포다. | Evidence: L177-L177, L181-L181, L184-L184, L188-L190, L197-L197, L203-L203" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;strokeColor=#2563eb;strokeWidth=2;" vertex="1" parent="1">
|
||||
<mxGeometry x="589.0" y="45.0" width="220.0" height="122.0" as="geometry"/>
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
@@ -1,80 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="854" height="300" viewBox="0 0 854 300" role="img" aria-labelledby="diagram-title diagram-description">
|
||||
<title id="diagram-title">Zipf-like 분포만 무거운 머리와 긴 꼬리를 함께 재현한다</title>
|
||||
<desc id="diagram-description">왼쪽부터 균일분포, 정규분포, Zipf-like 합성 분포를 같은 네 기준으로 비교한다. 균일분포는 모든 아이템이 3개이고, 정규분포는 평균 근처에 몰려 둘 다 극단적으로 많은 소수를 만들지 못하므로 제외된다. Zipf-like 분포는 소수의 인기 아이템이 압도적인 무거운 머리와 나머지의 긴 꼬리를 만들며, 지수 s=1.15와 상한 500·하한 1을 사용해 매우 많은 하이라이트 조건과 Top-N 필요성을 재현하는 합성 스트레스 분포로 선택된다.</desc>
|
||||
<metadata>{"techviz":{"spec_version":"1.1","id":"skew-profile","profile":"comparison"},"source_context":{"document":"/home/donghyeon/workspace/ai-tool/topic-arrange/n+1liner/n+1liner.md","document_sha256":"1bb38964ca2a849690f5355646c1aa988111b4cd4e1055c6cb05cf7e5fc35cde","anchor":{"kind":"marker","value":"skew-profile","line":192}},"evidence_policy":"Each factual element cites source lines or is marked assumption.","diagram_only":true}</metadata>
|
||||
<defs>
|
||||
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" />
|
||||
</marker>
|
||||
<style>
|
||||
:root { color-scheme: light; }
|
||||
text { font-family: Inter, Pretendard, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #111827; }
|
||||
.canvas { fill: #ffffff; }
|
||||
.group-box { fill: #ffffff; stroke: #9ca3af; stroke-width: 1.4; stroke-dasharray: 7 5; }
|
||||
.group-label-bg { fill: #ffffff; }
|
||||
.group-label { font-size: 13px; font-weight: 650; fill: #374151; }
|
||||
.edge { fill: none; stroke: #374151; stroke-width: 1.8; stroke-linejoin: round; stroke-linecap: round; marker-end: url(#arrow); }
|
||||
.edge.style-dashed, .edge.semantic-dashed, .edge.assumption { stroke-dasharray: 7 5; }
|
||||
.edge.style-dotted { stroke-dasharray: 2 5; }
|
||||
.edge.emphasis-primary { stroke: #2563eb; stroke-width: 2.2; }
|
||||
.edge.emphasis-muted { stroke: #9ca3af; }
|
||||
.edge.emphasis-warning, .edge.kind-failure, .edge.kind-error { stroke: #dc2626; stroke-width: 2.2; }
|
||||
.edge-label-bg { fill: #ffffff; }
|
||||
.edge-label { font-size: 12px; font-weight: 560; text-anchor: middle; }
|
||||
.node-shape { fill: #ffffff; stroke: #4b5563; stroke-width: 1.7; }
|
||||
.node-shape.emphasis-primary { stroke: #2563eb; stroke-width: 2.2; }
|
||||
.node-shape.emphasis-muted { stroke: #9ca3af; fill: #f9fafb; }
|
||||
.node-shape.emphasis-warning { stroke: #d97706; stroke-width: 2; fill: #fffdf5; }
|
||||
.node-shape.kind-database, .node-shape.kind-datastore, .node-shape.kind-storage { fill: #f8fafc; }
|
||||
.node-shape.kind-queue, .node-shape.kind-event, .node-shape.kind-topic { fill: #fafafa; }
|
||||
.node-shape.assumption { stroke-dasharray: 4 4; }
|
||||
.storage-bottom, .controller-divider { fill: none; stroke: #4b5563; stroke-width: 1.4; }
|
||||
.controller-led { fill: #4b5563; }
|
||||
.actor-symbol { fill: none; stroke: #4b5563; stroke-width: 1.8; stroke-linecap: round; }
|
||||
.actor-symbol.emphasis-primary { stroke: #2563eb; stroke-width: 2.2; }
|
||||
.node-label { font-size: 14px; font-weight: 650; text-anchor: middle; }
|
||||
.node-role { font-size: 10px; letter-spacing: 0.04em; text-anchor: middle; fill: #6b7280; }
|
||||
.node-detail-divider { stroke: #d1d5db; stroke-width: 1; }
|
||||
.node-detail { font-size: 11px; fill: #374151; }
|
||||
.assumption-badge { font-size: 9px; font-weight: 700; fill: #92400e; }
|
||||
.failure-mark { stroke: #dc2626; stroke-width: 4; stroke-linecap: round; }
|
||||
.lifeline { stroke: #9ca3af; stroke-width: 1.2; stroke-dasharray: 5 5; }
|
||||
.timeline-axis { stroke: #374151; stroke-width: 1.8; marker-end: url(#arrow); }
|
||||
.timeline-stem { stroke: #6b7280; stroke-width: 1.3; }
|
||||
.timeline-marker { fill: #ffffff; stroke: #374151; stroke-width: 1.7; }
|
||||
.timeline-marker.primary { fill: #2563eb; stroke: #2563eb; }
|
||||
.timeline-marker.warning { fill: #dc2626; stroke: #dc2626; }
|
||||
.timeline-label { font-size: 13px; font-weight: 650; text-anchor: middle; }
|
||||
.timeline-detail { font-size: 11px; fill: #4b5563; text-anchor: middle; }
|
||||
</style>
|
||||
</defs>
|
||||
<rect class="canvas" width="854" height="300" />
|
||||
<g id="node-uniform">
|
||||
<rect class="node-shape kind-distribution emphasis-muted role-option" data-evidence="177-177,195-195" x="45.0" y="45.0" width="220.0" height="122.0" rx="7" />
|
||||
<text class="node-label" x="155.0" y="72.0">균일분포</text>
|
||||
<line class="node-detail-divider" x1="59.0" y1="93.0" x2="251.0" y2="93.0" />
|
||||
<text class="node-detail" x="61.0" y="110.0">분포 형태: 모두 3개</text>
|
||||
<text class="node-detail" x="61.0" y="126.0">극단적 소수: 없음</text>
|
||||
<text class="node-detail" x="61.0" y="142.0">스트레스 조건: 재현 못함</text>
|
||||
<text class="node-detail" x="61.0" y="158.0">선택 결과: 제외</text>
|
||||
</g>
|
||||
<g id="node-normal">
|
||||
<rect class="node-shape kind-distribution emphasis-muted role-option" data-evidence="177-177,196-196" x="317.0" y="45.0" width="220.0" height="122.0" rx="7" />
|
||||
<text class="node-label" x="427.0" y="72.0">정규분포</text>
|
||||
<line class="node-detail-divider" x1="331.0" y1="93.0" x2="523.0" y2="93.0" />
|
||||
<text class="node-detail" x="333.0" y="110.0">분포 형태: 평균 근처 집중</text>
|
||||
<text class="node-detail" x="333.0" y="126.0">극단적 소수: 없음</text>
|
||||
<text class="node-detail" x="333.0" y="142.0">스트레스 조건: 재현 못함</text>
|
||||
<text class="node-detail" x="333.0" y="158.0">선택 결과: 제외</text>
|
||||
</g>
|
||||
<g id="node-zipf-like">
|
||||
<rect class="node-shape kind-distribution emphasis-primary role-option" data-evidence="177-177,181-181,184-184,188-190,197-197,203-203" x="589.0" y="45.0" width="220.0" height="122.0" rx="7" />
|
||||
<text class="node-label" x="699.0" y="72.0">Zipf-like 합성 분포</text>
|
||||
<line class="node-detail-divider" x1="603.0" y1="93.0" x2="795.0" y2="93.0" />
|
||||
<text class="node-detail" x="605.0" y="110.0">분포 형태: 무거운 머리 + 긴 꼬리</text>
|
||||
<text class="node-detail" x="605.0" y="126.0">극단적 소수: 있음 · 1위 500개</text>
|
||||
<text class="node-detail" x="605.0" y="142.0">스트레스 조건: 재현</text>
|
||||
<text class="node-detail" x="605.0" y="158.0">선택 결과: s=1.15 합성 분포</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 6.6 KiB |
@@ -0,0 +1,25 @@
|
||||
# 두 형제 문제는 Fetch Join에서 합류한 뒤 단계별 해법으로 최종 구조가 된다
|
||||
|
||||
## Alternative text
|
||||
|
||||
요구사항과 모델에서 기준선으로 진행한 뒤 N1과 N2로 분기하고 Fetch Join에서 합류해, 실패와 다섯 개선 단계를 거쳐 최종 피드 조회 구조에 이르는 흐름도.
|
||||
|
||||
## Long description
|
||||
|
||||
왼쪽에서 과제 요구사항, 도메인·데이터 모델, 최초 피드 조회 기준선 순으로 시작한다. 기준선에서 컬렉션 N+1(N1)과 User·Page 연관의 숨은 쿼리(N2)가 서로 앞뒤가 아닌 형제 문제로 동시에 갈라지고, 두 경로는 Fetch Join 시도에서 합류한다. 이 시도는 다중 컬렉션·페이징 실패로 이어진다. 마지막 노드는 Batch Fetch, DTO Projection, 아이템별 Top-3, Keyset Pagination, 가시성 조건 인덱싱을 거쳐 최종 피드 조회 구조에 도달하는 순서를 담는다.
|
||||
|
||||
## Elements and evidence
|
||||
|
||||
- **요구사항·모델·기준선** (journey-stage): 전체 여정은 과제 요구사항에서 도메인·데이터 모델을 거쳐 최초 피드 조회 기준선으로 시작한다. Evidence: L23–L26, L42–L42.
|
||||
- **컬렉션 N+1 (N1)** (problem): 기준선에서 N2와 동시에 나타나는 컬렉션 조회 문제다. Evidence: L32–L34.
|
||||
- **User·Page 숨은 쿼리 (N2)** (problem): 기준선에서 N1과 동시에 나타나는 User·Page 연관의 숨은 쿼리 문제다. Evidence: L32–L34.
|
||||
- **Fetch Join → 실패** (query-strategy): N1과 N2가 Fetch Join 시도에서 합류한 뒤 다중 컬렉션·페이징 실패로 이어진다. Evidence: L34–L35.
|
||||
- **후속 개선 → 최종 구조** (query-strategy): 실패 뒤 다섯 조회 전략 단계가 명시된 순서로 발전해 최종 피드 조회 구조를 만든다. Evidence: L32–L32, L35–L36.
|
||||
|
||||
## Relationships
|
||||
|
||||
- **요구사항·모델·기준선 → 컬렉션 N+1 (N1):** 동시 분기. Evidence: L32–L33.
|
||||
- **요구사항·모델·기준선 → User·Page 숨은 쿼리 (N2):** 동시 분기. Evidence: L32–L33.
|
||||
- **Fetch Join → 실패 → 후속 개선 → 최종 구조:** 실패 뒤 단계별 개선. Evidence: L34–L36.
|
||||
- **컬렉션 N+1 (N1) → Fetch Join → 실패:** 합류. Evidence: L34–L34.
|
||||
- **User·Page 숨은 쿼리 (N2) → Fetch Join → 실패:** 합류. Evidence: L34–L34.
|
||||
@@ -0,0 +1,23 @@
|
||||
# 두 형제 문제는 Fetch Join에서 합류한 뒤 단계별 해법으로 최종 구조가 된다
|
||||
# Question: 과제 요구사항에서 시작해 기준선의 N1·N2 분기와 실패·개선을 거쳐 최종 피드 조회 구조는 어떻게 발전하는가?
|
||||
direction: right
|
||||
n0: "요구사항·모델·기준선" {
|
||||
shape: rectangle
|
||||
}
|
||||
n1: "컬렉션 N+1 (N1)" {
|
||||
shape: rectangle
|
||||
}
|
||||
n2: "User·Page 숨은 쿼리 (N2)" {
|
||||
shape: rectangle
|
||||
}
|
||||
n3: "Fetch Join → 실패" {
|
||||
shape: rectangle
|
||||
}
|
||||
n4: "후속 개선 → 최종 구조" {
|
||||
shape: rectangle
|
||||
}
|
||||
n0 -> n1: "동시 분기"
|
||||
n0 -> n2: "동시 분기"
|
||||
n1 -> n3: "합류"
|
||||
n2 -> n3: "합류"
|
||||
n3 -> n4: "실패 뒤 단계별 개선"
|
||||
@@ -0,0 +1,15 @@
|
||||
digraph techviz {
|
||||
graph [rankdir=LR, splines=ortho, nodesep=0.55, ranksep=0.85];
|
||||
node [fontname=Helvetica, fontsize=11, margin="0.18,0.12", style="rounded,filled", fillcolor=white, color="#2d4357", penwidth=1.5];
|
||||
edge [fontname=Helvetica, fontsize=10, color="#364b5f", penwidth=1.4, arrowsize=0.75];
|
||||
n0 [label="요구사항·모델·기준선", shape=box, style="rounded,filled"];
|
||||
n1 [label="컬렉션 N+1 (N1)", shape=box, style="rounded,filled"];
|
||||
n2 [label="User·Page 숨은 쿼리 (N2)", shape=box, style="rounded,filled"];
|
||||
n3 [label="Fetch Join → 실패", shape=box, style="rounded,filled"];
|
||||
n4 [label="후속 개선 → 최종 구조", shape=box, style="rounded,filled"];
|
||||
n0 -> n1 [label="동시 분기", style=solid];
|
||||
n0 -> n2 [label="동시 분기", style=solid];
|
||||
n1 -> n3 [label="합류", style=solid];
|
||||
n2 -> n3 [label="합류", style=solid];
|
||||
n3 -> n4 [label="실패 뒤 단계별 개선", style=solid];
|
||||
}
|
||||
@@ -5,27 +5,27 @@
|
||||
<root>
|
||||
<mxCell id="0"/>
|
||||
<mxCell id="1" parent="0"/>
|
||||
<mxCell id="n_requirements-model-baseline" value="요구사항·모델·기준선<br/>1 과제 요구사항<br/>2 도메인·데이터 모델<br/>3 최초 피드 조회" tooltip="전체 여정은 과제 요구사항에서 도메인·데이터 모델을 거쳐 최초 피드 조회 기준선으로 시작한다. | Evidence: L25-L25" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxCell id="n_requirements-model-baseline" value="요구사항·모델·기준선<br/>1 과제 요구사항<br/>2 도메인·데이터 모델<br/>3 최초 피드 조회" tooltip="전체 여정은 과제 요구사항에서 도메인·데이터 모델을 거쳐 최초 피드 조회 기준선으로 시작한다. | Evidence: L23-L26, L42-L42" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="70.0" y="165.5" width="150.0" height="105.0" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="n_collection-n-plus-one" value="컬렉션 N+1 (N1)<br/>유형: 컬렉션 N+1<br/>발생: 기준선과 동시에<br/>관계: N2와 형제 문제<br/>진행: Fetch Join으로 합류" tooltip="기준선에서 N2와 동시에 나타나는 컬렉션 조회 문제다. | Evidence: L25-L25" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="383.5" y="60.0" width="167.0" height="122.0" as="geometry"/>
|
||||
<mxCell id="n_collection-n-plus-one" value="컬렉션 N+1 (N1)<br/>유형: 컬렉션 N+1<br/>발생: 기준선과 동시에<br/>관계: N2와 형제 문제<br/>진행: Fetch Join 합류" tooltip="기준선에서 N2와 동시에 나타나는 컬렉션 조회 문제다. | Evidence: L32-L34" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="390.5" y="60.0" width="153.0" height="122.0" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="n_hidden-to-one-queries" value="User·Page 숨은 쿼리 (N2)<br/>유형: User·Page 숨은 쿼리<br/>발생: 기준선과 동시에<br/>관계: N1과 형제 문제<br/>진행: Fetch Join으로 합류" tooltip="기준선에서 N1과 동시에 나타나는 User·Page 연관의 숨은 쿼리 문제다. | Evidence: L25-L25" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxCell id="n_hidden-to-one-queries" value="User·Page 숨은 쿼리 (N2)<br/>유형: User·Page 숨은 쿼리<br/>발생: 기준선과 동시에<br/>관계: N1과 형제 문제<br/>진행: Fetch Join 합류" tooltip="기준선에서 N1과 동시에 나타나는 User·Page 연관의 숨은 쿼리 문제다. | Evidence: L32-L34" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="380.0" y="254.0" width="174.0" height="122.0" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="n_fetch-join-failure" value="Fetch Join → 실패<br/>1 N1·N2 합류<br/>2 다중 컬렉션·페이징 실패" tooltip="N1과 N2가 Fetch Join 시도에서 합류한 뒤 다중 컬렉션·페이징 실패로 이어진다. | Evidence: L25-L25" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;strokeColor=#d97706;fillColor=#fffdf5;" vertex="1" parent="1">
|
||||
<mxCell id="n_fetch-join-failure" value="Fetch Join → 실패<br/>1 N1·N2 합류<br/>2 다중 컬렉션·페이징 실패" tooltip="N1과 N2가 Fetch Join 시도에서 합류한 뒤 다중 컬렉션·페이징 실패로 이어진다. | Evidence: L34-L35" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;strokeColor=#d97706;fillColor=#fffdf5;" vertex="1" parent="1">
|
||||
<mxGeometry x="714.0" y="174.0" width="150.0" height="88.0" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="n_improvement-chain" value="후속 개선 → 최종 구조<br/>1 Batch Fetch<br/>2 DTO Projection<br/>3 아이템별 Top-3<br/>4 Keyset Pagination<br/>5 가시성 조건 인덱싱<br/>6 최종 피드 조회 구조" tooltip="실패 뒤 다섯 조회 전략 단계가 명시된 순서로 발전해 최종 피드 조회 구조를 만든다. | Evidence: L25-L25" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;strokeColor=#2563eb;strokeWidth=2;" vertex="1" parent="1">
|
||||
<mxCell id="n_improvement-chain" value="후속 개선 → 최종 구조<br/>1 Batch Fetch<br/>2 DTO Projection<br/>3 아이템별 Top-3<br/>4 Keyset Pagination<br/>5 가시성 조건 인덱싱<br/>6 최종 피드 조회 구조" tooltip="실패 뒤 다섯 조회 전략 단계가 명시된 순서로 발전해 최종 피드 조회 구조를 만든다. | Evidence: L32-L32, L35-L36" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;strokeColor=#2563eb;strokeWidth=2;" vertex="1" parent="1">
|
||||
<mxGeometry x="1024.0" y="140.0" width="167.0" height="156.0" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="e_baseline-reveals-n1" value="기준선에서 갈라짐" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_requirements-model-baseline" target="n_collection-n-plus-one">
|
||||
<mxCell id="e_baseline-reveals-n1" value="동시 분기" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_requirements-model-baseline" target="n_collection-n-plus-one">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="325.8" y="165.0" as="offset"/>
|
||||
<mxPoint x="329.2" y="165.0" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="e_baseline-reveals-n2" value="기준선에서 갈라짐" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_requirements-model-baseline" target="n_hidden-to-one-queries">
|
||||
<mxCell id="e_baseline-reveals-n2" value="동시 분기" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_requirements-model-baseline" target="n_hidden-to-one-queries">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="324.0" y="271.0" as="offset"/>
|
||||
</mxGeometry>
|
||||
@@ -35,12 +35,12 @@
|
||||
<mxPoint x="944.0" y="190.0" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="e_n1-joins-fetch-join" value="Fetch Join으로 합류" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_collection-n-plus-one" target="n_fetch-join-failure">
|
||||
<mxCell id="e_n1-joins-fetch-join" value="합류" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_collection-n-plus-one" target="n_fetch-join-failure">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="656.2" y="165.0" as="offset"/>
|
||||
<mxPoint x="652.8" y="165.0" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="e_n2-joins-fetch-join" value="Fetch Join으로 합류" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_hidden-to-one-queries" target="n_fetch-join-failure">
|
||||
<mxCell id="e_n2-joins-fetch-join" value="합류" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_hidden-to-one-queries" target="n_fetch-join-failure">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="658.0" y="271.0" as="offset"/>
|
||||
</mxGeometry>
|
||||
|
||||
@@ -0,0 +1,853 @@
|
||||
{
|
||||
"type": "excalidraw",
|
||||
"version": 2,
|
||||
"source": "techviz-harness",
|
||||
"elements": [
|
||||
{
|
||||
"id": "edge-baseline-reveals-n1",
|
||||
"type": "arrow",
|
||||
"x": 220.0,
|
||||
"y": 121.0,
|
||||
"width": 170.5,
|
||||
"height": 88.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": null,
|
||||
"seed": 552529844,
|
||||
"version": 1,
|
||||
"versionNonce": 472576796,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[
|
||||
0.0,
|
||||
88.0
|
||||
],
|
||||
[
|
||||
85.25,
|
||||
88.0
|
||||
],
|
||||
[
|
||||
85.25,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
170.5,
|
||||
0.0
|
||||
]
|
||||
],
|
||||
"lastCommittedPoint": null,
|
||||
"startBinding": {
|
||||
"elementId": "node-requirements-model-baseline",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"endBinding": {
|
||||
"elementId": "node-collection-n-plus-one",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow",
|
||||
"elbowed": true
|
||||
},
|
||||
{
|
||||
"id": "edge-label-baseline-reveals-n1",
|
||||
"type": "text",
|
||||
"x": 284.25,
|
||||
"y": 153.0,
|
||||
"width": 90,
|
||||
"height": 24,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1033652899,
|
||||
"version": 1,
|
||||
"versionNonce": 1516968670,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 13,
|
||||
"fontFamily": 5,
|
||||
"text": "동시 분기",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "동시 분기",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "edge-baseline-reveals-n2",
|
||||
"type": "arrow",
|
||||
"x": 220.0,
|
||||
"y": 227.0,
|
||||
"width": 160.0,
|
||||
"height": 88.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": null,
|
||||
"seed": 591789630,
|
||||
"version": 1,
|
||||
"versionNonce": 484551927,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
80.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
80.0,
|
||||
88.0
|
||||
],
|
||||
[
|
||||
160.0,
|
||||
88.0
|
||||
]
|
||||
],
|
||||
"lastCommittedPoint": null,
|
||||
"startBinding": {
|
||||
"elementId": "node-requirements-model-baseline",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"endBinding": {
|
||||
"elementId": "node-hidden-to-one-queries",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow",
|
||||
"elbowed": true
|
||||
},
|
||||
{
|
||||
"id": "edge-label-baseline-reveals-n2",
|
||||
"type": "text",
|
||||
"x": 279.0,
|
||||
"y": 259.0,
|
||||
"width": 90,
|
||||
"height": 24,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 205942638,
|
||||
"version": 1,
|
||||
"versionNonce": 190066354,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 13,
|
||||
"fontFamily": 5,
|
||||
"text": "동시 분기",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "동시 분기",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "edge-failure-to-improvements",
|
||||
"type": "arrow",
|
||||
"x": 864.0,
|
||||
"y": 218.0,
|
||||
"width": 160.0,
|
||||
"height": 0.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": null,
|
||||
"seed": 771986691,
|
||||
"version": 1,
|
||||
"versionNonce": 1642137980,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
80.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
80.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
160.0,
|
||||
0.0
|
||||
]
|
||||
],
|
||||
"lastCommittedPoint": null,
|
||||
"startBinding": {
|
||||
"elementId": "node-fetch-join-failure",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"endBinding": {
|
||||
"elementId": "node-improvement-chain",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow",
|
||||
"elbowed": true
|
||||
},
|
||||
{
|
||||
"id": "edge-label-failure-to-improvements",
|
||||
"type": "text",
|
||||
"x": 899.0,
|
||||
"y": 178.0,
|
||||
"width": 90,
|
||||
"height": 24,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 104239920,
|
||||
"version": 1,
|
||||
"versionNonce": 977780642,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 13,
|
||||
"fontFamily": 5,
|
||||
"text": "실패 뒤 단계별 개선",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "실패 뒤 단계별 개선",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "edge-n1-joins-fetch-join",
|
||||
"type": "arrow",
|
||||
"x": 543.5,
|
||||
"y": 121.0,
|
||||
"width": 170.5,
|
||||
"height": 88.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": null,
|
||||
"seed": 1375189997,
|
||||
"version": 1,
|
||||
"versionNonce": 831453832,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
85.25,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
85.25,
|
||||
88.0
|
||||
],
|
||||
[
|
||||
170.5,
|
||||
88.0
|
||||
]
|
||||
],
|
||||
"lastCommittedPoint": null,
|
||||
"startBinding": {
|
||||
"elementId": "node-collection-n-plus-one",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"endBinding": {
|
||||
"elementId": "node-fetch-join-failure",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow",
|
||||
"elbowed": true
|
||||
},
|
||||
{
|
||||
"id": "edge-label-n1-joins-fetch-join",
|
||||
"type": "text",
|
||||
"x": 607.75,
|
||||
"y": 153.0,
|
||||
"width": 90,
|
||||
"height": 24,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 375090892,
|
||||
"version": 1,
|
||||
"versionNonce": 248795047,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 13,
|
||||
"fontFamily": 5,
|
||||
"text": "합류",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "합류",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "edge-n2-joins-fetch-join",
|
||||
"type": "arrow",
|
||||
"x": 554.0,
|
||||
"y": 227.0,
|
||||
"width": 160.0,
|
||||
"height": 88.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": null,
|
||||
"seed": 780844363,
|
||||
"version": 1,
|
||||
"versionNonce": 1056135667,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[
|
||||
0.0,
|
||||
88.0
|
||||
],
|
||||
[
|
||||
80.0,
|
||||
88.0
|
||||
],
|
||||
[
|
||||
80.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
160.0,
|
||||
0.0
|
||||
]
|
||||
],
|
||||
"lastCommittedPoint": null,
|
||||
"startBinding": {
|
||||
"elementId": "node-hidden-to-one-queries",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"endBinding": {
|
||||
"elementId": "node-fetch-join-failure",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow",
|
||||
"elbowed": true
|
||||
},
|
||||
{
|
||||
"id": "edge-label-n2-joins-fetch-join",
|
||||
"type": "text",
|
||||
"x": 613.0,
|
||||
"y": 259.0,
|
||||
"width": 90,
|
||||
"height": 24,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1639294716,
|
||||
"version": 1,
|
||||
"versionNonce": 1805717144,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 13,
|
||||
"fontFamily": 5,
|
||||
"text": "합류",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "합류",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "node-requirements-model-baseline",
|
||||
"type": "rectangle",
|
||||
"x": 70.0,
|
||||
"y": 165.5,
|
||||
"width": 150.0,
|
||||
"height": 105.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#ffffff",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 195897388,
|
||||
"version": 1,
|
||||
"versionNonce": 1827986565,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "node-label-requirements-model-baseline",
|
||||
"type": "text",
|
||||
"x": 80.0,
|
||||
"y": 175.5,
|
||||
"width": 130.0,
|
||||
"height": 85.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 836154815,
|
||||
"version": 1,
|
||||
"versionNonce": 195330917,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 15,
|
||||
"fontFamily": 5,
|
||||
"text": "요구사항·모델·기준선\n1 과제 요구사항\n2 도메인·데이터 모델\n3 최초 피드 조회",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "요구사항·모델·기준선\n1 과제 요구사항\n2 도메인·데이터 모델\n3 최초 피드 조회",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "node-collection-n-plus-one",
|
||||
"type": "rectangle",
|
||||
"x": 390.5,
|
||||
"y": 60.0,
|
||||
"width": 153.0,
|
||||
"height": 122.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#ffffff",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1539566879,
|
||||
"version": 1,
|
||||
"versionNonce": 70442440,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "node-label-collection-n-plus-one",
|
||||
"type": "text",
|
||||
"x": 400.5,
|
||||
"y": 70.0,
|
||||
"width": 133.0,
|
||||
"height": 102.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1797128049,
|
||||
"version": 1,
|
||||
"versionNonce": 350363393,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 15,
|
||||
"fontFamily": 5,
|
||||
"text": "컬렉션 N+1 (N1)\n유형: 컬렉션 N+1\n발생: 기준선과 동시에\n관계: N2와 형제 문제\n진행: Fetch Join 합류",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "컬렉션 N+1 (N1)\n유형: 컬렉션 N+1\n발생: 기준선과 동시에\n관계: N2와 형제 문제\n진행: Fetch Join 합류",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "node-hidden-to-one-queries",
|
||||
"type": "rectangle",
|
||||
"x": 380.0,
|
||||
"y": 254.0,
|
||||
"width": 174.0,
|
||||
"height": 122.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#ffffff",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1430794108,
|
||||
"version": 1,
|
||||
"versionNonce": 1806137646,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "node-label-hidden-to-one-queries",
|
||||
"type": "text",
|
||||
"x": 390.0,
|
||||
"y": 264.0,
|
||||
"width": 154.0,
|
||||
"height": 102.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1014481634,
|
||||
"version": 1,
|
||||
"versionNonce": 6450502,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 15,
|
||||
"fontFamily": 5,
|
||||
"text": "User·Page 숨은 쿼리 (N2)\n유형: User·Page 숨은 쿼리\n발생: 기준선과 동시에\n관계: N1과 형제 문제\n진행: Fetch Join 합류",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "User·Page 숨은 쿼리 (N2)\n유형: User·Page 숨은 쿼리\n발생: 기준선과 동시에\n관계: N1과 형제 문제\n진행: Fetch Join 합류",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "node-fetch-join-failure",
|
||||
"type": "rectangle",
|
||||
"x": 714.0,
|
||||
"y": 174.0,
|
||||
"width": 150.0,
|
||||
"height": 88.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#ffffff",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 218595495,
|
||||
"version": 1,
|
||||
"versionNonce": 316372391,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "node-label-fetch-join-failure",
|
||||
"type": "text",
|
||||
"x": 724.0,
|
||||
"y": 184.0,
|
||||
"width": 130.0,
|
||||
"height": 68.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 599407563,
|
||||
"version": 1,
|
||||
"versionNonce": 1890755630,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 15,
|
||||
"fontFamily": 5,
|
||||
"text": "Fetch Join → 실패\n1 N1·N2 합류\n2 다중 컬렉션·페이징 실패",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "Fetch Join → 실패\n1 N1·N2 합류\n2 다중 컬렉션·페이징 실패",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "node-improvement-chain",
|
||||
"type": "rectangle",
|
||||
"x": 1024.0,
|
||||
"y": 140.0,
|
||||
"width": 167.0,
|
||||
"height": 156.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#ffffff",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1124544465,
|
||||
"version": 1,
|
||||
"versionNonce": 96234792,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "node-label-improvement-chain",
|
||||
"type": "text",
|
||||
"x": 1034.0,
|
||||
"y": 150.0,
|
||||
"width": 147.0,
|
||||
"height": 136.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1789169640,
|
||||
"version": 1,
|
||||
"versionNonce": 220144649,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 15,
|
||||
"fontFamily": 5,
|
||||
"text": "후속 개선 → 최종 구조\n1 Batch Fetch\n2 DTO Projection\n3 아이템별 Top-3\n4 Keyset Pagination\n5 가시성 조건 인덱싱\n6 최종 피드 조회 구조",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "후속 개선 → 최종 구조\n1 Batch Fetch\n2 DTO Projection\n3 아이템별 Top-3\n4 Keyset Pagination\n5 가시성 조건 인덱싱\n6 최종 피드 조회 구조",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
}
|
||||
],
|
||||
"appState": {
|
||||
"gridSize": 10,
|
||||
"viewBackgroundColor": "#ffffff",
|
||||
"currentItemFontFamily": 5
|
||||
},
|
||||
"files": {}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"harness_version": "0.2.0",
|
||||
"spec_id": "strategy-journey",
|
||||
"spec_version": "1.1",
|
||||
"spec_sha256": "e3e4c2346e9995960b8e3a1c4f4574e6a1f4a2e464cd38e9b098c9db41f47334",
|
||||
"source_context": {
|
||||
"document": "docs/n+1liner/final/document.md",
|
||||
"document_sha256": "f9e048a68db0ab82078955bf611b06a71a0033539e0c87ff0aa0d620a3126e36",
|
||||
"anchor": {
|
||||
"kind": "heading",
|
||||
"value": "2. 조회 전략의 전체 여정",
|
||||
"line": 30
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
"strategy-journey.svg",
|
||||
"strategy-journey.drawio",
|
||||
"strategy-journey.mmd",
|
||||
"strategy-journey.d2",
|
||||
"strategy-journey.dot",
|
||||
"strategy-journey.excalidraw",
|
||||
"strategy-journey.alt.md"
|
||||
],
|
||||
"lint_issue_count": 0,
|
||||
"assumption_count": 0,
|
||||
"assumptions_allowed": false,
|
||||
"composition_profile": "component-flow",
|
||||
"reference_ids": [
|
||||
"payment-event-flow"
|
||||
],
|
||||
"diagram_only": true
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
%% 두 형제 문제는 Fetch Join에서 합류한 뒤 단계별 해법으로 최종 구조가 된다
|
||||
%% question: 과제 요구사항에서 시작해 기준선의 N1·N2 분기와 실패·개선을 거쳐 최종 피드 조회 구조는 어떻게 발전하는가?
|
||||
flowchart LR
|
||||
n0["요구사항·모델·기준선"]
|
||||
n1["컬렉션 N+1 (N1)"]
|
||||
n2["User·Page 숨은 쿼리 (N2)"]
|
||||
n3["Fetch Join → 실패"]
|
||||
n4["후속 개선 → 최종 구조"]
|
||||
n0 -->|"동시 분기"| n1
|
||||
n0 -->|"동시 분기"| n2
|
||||
n1 -->|"합류"| n3
|
||||
n2 -->|"합류"| n3
|
||||
n3 -->|"실패 뒤 단계별 개선"| n4
|
||||
@@ -2,7 +2,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1236" height="421" viewBox="0 0 1236 421" role="img" aria-labelledby="diagram-title diagram-description">
|
||||
<title id="diagram-title">두 형제 문제는 Fetch Join에서 합류한 뒤 단계별 해법으로 최종 구조가 된다</title>
|
||||
<desc id="diagram-description">왼쪽에서 과제 요구사항, 도메인·데이터 모델, 최초 피드 조회 기준선 순으로 시작한다. 기준선에서 컬렉션 N+1(N1)과 User·Page 연관의 숨은 쿼리(N2)가 서로 앞뒤가 아닌 형제 문제로 동시에 갈라지고, 두 경로는 Fetch Join 시도에서 합류한다. 이 시도는 다중 컬렉션·페이징 실패로 이어진다. 마지막 노드는 Batch Fetch, DTO Projection, 아이템별 Top-3, Keyset Pagination, 가시성 조건 인덱싱을 거쳐 최종 피드 조회 구조에 도달하는 순서를 담는다.</desc>
|
||||
<metadata>{"techviz":{"spec_version":"1.1","id":"strategy-journey","profile":"component-flow"},"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}},"evidence_policy":"Each factual element cites source lines or is marked assumption.","diagram_only":true}</metadata>
|
||||
<metadata>{"techviz":{"spec_version":"1.1","id":"strategy-journey","profile":"component-flow"},"source_context":{"document":"docs/n+1liner/final/document.md","document_sha256":"f9e048a68db0ab82078955bf611b06a71a0033539e0c87ff0aa0d620a3126e36","anchor":{"kind":"heading","value":"2. 조회 전략의 전체 여정","line":30}},"evidence_policy":"Each factual element cites source lines or is marked assumption.","diagram_only":true}</metadata>
|
||||
<defs>
|
||||
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" />
|
||||
@@ -50,23 +50,23 @@
|
||||
</style>
|
||||
</defs>
|
||||
<rect class="canvas" width="1236" height="421" />
|
||||
<polyline class="edge kind-problem style-solid emphasis-normal" points="220.0,209.0 301.8,209.0 301.8,121.0 383.5,121.0" data-evidence="25-25" />
|
||||
<rect class="edge-label-bg" x="286.6" y="151.0" width="78.3" height="22" rx="3" />
|
||||
<text class="edge-label" x="325.8" y="166.0">기준선에서 갈라짐</text>
|
||||
<polyline class="edge kind-problem style-solid emphasis-normal" points="220.0,227.0 300.0,227.0 300.0,315.0 380.0,315.0" data-evidence="25-25" />
|
||||
<rect class="edge-label-bg" x="284.9" y="257.0" width="78.3" height="22" rx="3" />
|
||||
<text class="edge-label" x="324.0" y="272.0">기준선에서 갈라짐</text>
|
||||
<polyline class="edge kind-improvement style-solid emphasis-normal" points="864.0,218.0 944.0,218.0 944.0,218.0 1024.0,218.0" data-evidence="25-25" />
|
||||
<polyline class="edge kind-problem style-solid emphasis-normal" points="220.0,209.0 305.2,209.0 305.2,121.0 390.5,121.0" data-evidence="32-33" />
|
||||
<rect class="edge-label-bg" x="303.5" y="151.0" width="51.5" height="22" rx="3" />
|
||||
<text class="edge-label" x="329.2" y="166.0">동시 분기</text>
|
||||
<polyline class="edge kind-problem style-solid emphasis-normal" points="220.0,227.0 300.0,227.0 300.0,315.0 380.0,315.0" data-evidence="32-33" />
|
||||
<rect class="edge-label-bg" x="298.2" y="257.0" width="51.5" height="22" rx="3" />
|
||||
<text class="edge-label" x="324.0" y="272.0">동시 분기</text>
|
||||
<polyline class="edge kind-improvement style-solid emphasis-normal" points="864.0,218.0 944.0,218.0 944.0,218.0 1024.0,218.0" data-evidence="34-36" />
|
||||
<rect class="edge-label-bg" x="898.1" y="176.0" width="91.7" height="22" rx="3" />
|
||||
<text class="edge-label" x="944.0" y="191.0">실패 뒤 단계별 개선</text>
|
||||
<polyline class="edge kind-flow style-solid emphasis-normal" points="550.5,121.0 632.2,121.0 632.2,209.0 714.0,209.0" data-evidence="25-25" />
|
||||
<rect class="edge-label-bg" x="597.0" y="151.0" width="118.5" height="22" rx="3" />
|
||||
<text class="edge-label" x="656.2" y="166.0">Fetch Join으로 합류</text>
|
||||
<polyline class="edge kind-flow style-solid emphasis-normal" points="554.0,315.0 634.0,315.0 634.0,227.0 714.0,227.0" data-evidence="25-25" />
|
||||
<rect class="edge-label-bg" x="598.8" y="257.0" width="118.5" height="22" rx="3" />
|
||||
<text class="edge-label" x="658.0" y="272.0">Fetch Join으로 합류</text>
|
||||
<polyline class="edge kind-flow style-solid emphasis-normal" points="543.5,121.0 628.8,121.0 628.8,209.0 714.0,209.0" data-evidence="34-34" />
|
||||
<rect class="edge-label-bg" x="630.8" y="151.0" width="44.0" height="22" rx="3" />
|
||||
<text class="edge-label" x="652.8" y="166.0">합류</text>
|
||||
<polyline class="edge kind-flow style-solid emphasis-normal" points="554.0,315.0 634.0,315.0 634.0,227.0 714.0,227.0" data-evidence="34-34" />
|
||||
<rect class="edge-label-bg" x="636.0" y="257.0" width="44.0" height="22" rx="3" />
|
||||
<text class="edge-label" x="658.0" y="272.0">합류</text>
|
||||
<g id="node-requirements-model-baseline">
|
||||
<rect class="node-shape kind-journey-stage emphasis-normal role-source" data-evidence="25-25" x="70.0" y="165.5" width="150.0" height="105.0" rx="7" />
|
||||
<rect class="node-shape kind-journey-stage emphasis-normal role-source" data-evidence="23-26,42-42" x="70.0" y="165.5" width="150.0" height="105.0" rx="7" />
|
||||
<text class="node-label" x="145.0" y="192.5">요구사항·모델·기준선</text>
|
||||
<line class="node-detail-divider" x1="84.0" y1="213.5" x2="206.0" y2="213.5" />
|
||||
<text class="node-detail" x="86.0" y="230.5">1 과제 요구사항</text>
|
||||
@@ -74,32 +74,32 @@
|
||||
<text class="node-detail" x="86.0" y="262.5">3 최초 피드 조회</text>
|
||||
</g>
|
||||
<g id="node-collection-n-plus-one">
|
||||
<rect class="node-shape kind-problem emphasis-normal role-service" data-evidence="25-25" x="383.5" y="60.0" width="167.0" height="122.0" rx="7" />
|
||||
<rect class="node-shape kind-problem emphasis-normal role-service" data-evidence="32-34" x="390.5" y="60.0" width="153.0" height="122.0" rx="7" />
|
||||
<text class="node-label" x="467.0" y="87.0">컬렉션 N+1 (N1)</text>
|
||||
<line class="node-detail-divider" x1="397.5" y1="108.0" x2="536.5" y2="108.0" />
|
||||
<text class="node-detail" x="399.5" y="125.0">유형: 컬렉션 N+1</text>
|
||||
<text class="node-detail" x="399.5" y="141.0">발생: 기준선과 동시에</text>
|
||||
<text class="node-detail" x="399.5" y="157.0">관계: N2와 형제 문제</text>
|
||||
<text class="node-detail" x="399.5" y="173.0">진행: Fetch Join으로 합류</text>
|
||||
<line class="node-detail-divider" x1="404.5" y1="108.0" x2="529.5" y2="108.0" />
|
||||
<text class="node-detail" x="406.5" y="125.0">유형: 컬렉션 N+1</text>
|
||||
<text class="node-detail" x="406.5" y="141.0">발생: 기준선과 동시에</text>
|
||||
<text class="node-detail" x="406.5" y="157.0">관계: N2와 형제 문제</text>
|
||||
<text class="node-detail" x="406.5" y="173.0">진행: Fetch Join 합류</text>
|
||||
</g>
|
||||
<g id="node-hidden-to-one-queries">
|
||||
<rect class="node-shape kind-problem emphasis-normal role-service" data-evidence="25-25" x="380.0" y="254.0" width="174.0" height="122.0" rx="7" />
|
||||
<rect class="node-shape kind-problem emphasis-normal role-service" data-evidence="32-34" x="380.0" y="254.0" width="174.0" height="122.0" rx="7" />
|
||||
<text class="node-label" x="467.0" y="281.0">User·Page 숨은 쿼리 (N2)</text>
|
||||
<line class="node-detail-divider" x1="394.0" y1="302.0" x2="540.0" y2="302.0" />
|
||||
<text class="node-detail" x="396.0" y="319.0">유형: User·Page 숨은 쿼리</text>
|
||||
<text class="node-detail" x="396.0" y="335.0">발생: 기준선과 동시에</text>
|
||||
<text class="node-detail" x="396.0" y="351.0">관계: N1과 형제 문제</text>
|
||||
<text class="node-detail" x="396.0" y="367.0">진행: Fetch Join으로 합류</text>
|
||||
<text class="node-detail" x="396.0" y="367.0">진행: Fetch Join 합류</text>
|
||||
</g>
|
||||
<g id="node-fetch-join-failure">
|
||||
<rect class="node-shape kind-query-strategy emphasis-warning role-service" data-evidence="25-25" x="714.0" y="174.0" width="150.0" height="88.0" rx="7" />
|
||||
<rect class="node-shape kind-query-strategy emphasis-warning role-service" data-evidence="34-35" x="714.0" y="174.0" width="150.0" height="88.0" rx="7" />
|
||||
<text class="node-label" x="789.0" y="201.0">Fetch Join → 실패</text>
|
||||
<line class="node-detail-divider" x1="728.0" y1="222.0" x2="850.0" y2="222.0" />
|
||||
<text class="node-detail" x="730.0" y="239.0">1 N1·N2 합류</text>
|
||||
<text class="node-detail" x="730.0" y="255.0">2 다중 컬렉션·페이징 실패</text>
|
||||
</g>
|
||||
<g id="node-improvement-chain">
|
||||
<rect class="node-shape kind-query-strategy emphasis-primary role-sink" data-evidence="25-25" x="1024.0" y="140.0" width="167.0" height="156.0" rx="7" />
|
||||
<rect class="node-shape kind-query-strategy emphasis-primary role-sink" data-evidence="32-32,35-36" x="1024.0" y="140.0" width="167.0" height="156.0" rx="7" />
|
||||
<text class="node-label" x="1107.5" y="168.0">후속 개선 → 최종 구조</text>
|
||||
<line class="node-detail-divider" x1="1038.0" y1="189.0" x2="1177.0" y2="189.0" />
|
||||
<text class="node-detail" x="1040.0" y="206.0">1 Batch Fetch</text>
|
||||
|
||||
|
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 9.3 KiB |
@@ -0,0 +1,25 @@
|
||||
# 목표 스키마에 추가되는 mention 관계
|
||||
|
||||
## Alternative text
|
||||
|
||||
기존 users를 mentioned 사용자 역할로 재사용해 feed_item_mentions와 연결한 5노드 목표 관계도.
|
||||
|
||||
## Long description
|
||||
|
||||
왼쪽의 users와 pages가 중앙의 feed_items에 연결된다. 오른쪽에는 highlights와 feed_item_mentions가 놓인다. feed_items는 두 엔티티에 각각 연결되고, 기존 users도 mentioned 사용자 역할로 feed_item_mentions에 연결된다.
|
||||
|
||||
## Elements and evidence
|
||||
|
||||
- **users** (entity): No additional description. Evidence: L46–L46.
|
||||
- **pages** (entity): No additional description. Evidence: L47–L47.
|
||||
- **feed_items** (entity): No additional description. Evidence: L46–L46.
|
||||
- **highlights** (entity): No additional description. Evidence: L48–L48.
|
||||
- **feed_item_mentions** (entity): No additional description. Evidence: L56–L56.
|
||||
|
||||
## Relationships
|
||||
|
||||
- **feed_items → highlights:** 1:N. Evidence: L48–L48.
|
||||
- **feed_items → feed_item_mentions:** 피드 아이템. Evidence: L56–L56.
|
||||
- **pages → feed_items:** 1:N. Evidence: L47–L47.
|
||||
- **users → feed_items:** 1:N. Evidence: L46–L46.
|
||||
- **users → feed_item_mentions:** mentioned 사용자. Evidence: L56–L56.
|
||||
@@ -0,0 +1,23 @@
|
||||
# 목표 스키마에 추가되는 mention 관계
|
||||
# Question: 목표 모델에서 기존 users는 mentioned 사용자 역할로 feed_item_mentions에 어떻게 연결되는가?
|
||||
direction: right
|
||||
n0: "users" {
|
||||
shape: rectangle
|
||||
}
|
||||
n1: "pages" {
|
||||
shape: rectangle
|
||||
}
|
||||
n2: "feed_items" {
|
||||
shape: rectangle
|
||||
}
|
||||
n3: "highlights" {
|
||||
shape: rectangle
|
||||
}
|
||||
n4: "feed_item_mentions" {
|
||||
shape: rectangle
|
||||
}
|
||||
n0 -> n2: "1:N"
|
||||
n1 -> n2: "1:N"
|
||||
n2 -> n3: "1:N"
|
||||
n2 -> n4: "피드 아이템"
|
||||
n0 -> n4: "mentioned 사용자"
|
||||
@@ -0,0 +1,15 @@
|
||||
digraph techviz {
|
||||
graph [rankdir=LR, splines=ortho, nodesep=0.55, ranksep=0.85];
|
||||
node [fontname=Helvetica, fontsize=11, margin="0.18,0.12", style="rounded,filled", fillcolor=white, color="#2d4357", penwidth=1.5];
|
||||
edge [fontname=Helvetica, fontsize=10, color="#364b5f", penwidth=1.4, arrowsize=0.75];
|
||||
n0 [label="users", shape=box, style="rounded,filled"];
|
||||
n1 [label="pages", shape=box, style="rounded,filled"];
|
||||
n2 [label="feed_items", shape=box, style="rounded,filled"];
|
||||
n3 [label="highlights", shape=box, style="rounded,filled"];
|
||||
n4 [label="feed_item_mentions", shape=box, style="rounded,filled"];
|
||||
n0 -> n2 [label="1:N", style=solid];
|
||||
n1 -> n2 [label="1:N", style=solid];
|
||||
n2 -> n3 [label="1:N", style=solid];
|
||||
n2 -> n4 [label="피드 아이템", style=solid];
|
||||
n0 -> n4 [label="mentioned 사용자", style=solid];
|
||||
}
|
||||
@@ -5,42 +5,42 @@
|
||||
<root>
|
||||
<mxCell id="0"/>
|
||||
<mxCell id="1" parent="0"/>
|
||||
<mxCell id="n_users" value="users" tooltip="entity | Evidence: L35-L35" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxCell id="n_users" value="users" tooltip="entity | Evidence: L46-L46" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="70.0" y="60.0" width="150.0" height="64.0" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="n_pages" value="pages" tooltip="entity | Evidence: L36-L36" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxCell id="n_pages" value="pages" tooltip="entity | Evidence: L47-L47" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="70.0" y="196.0" width="150.0" height="64.0" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="n_feed-items" value="feed_items" tooltip="entity | Evidence: L35-L35" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxCell id="n_feed-items" value="feed_items" tooltip="entity | Evidence: L46-L46" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="380.0" y="128.0" width="150.0" height="64.0" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="n_feed-item-mentions" value="feed_item_mentions" tooltip="entity | Evidence: L43-L43" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxCell id="n_feed-item-mentions" value="feed_item_mentions" tooltip="entity | Evidence: L56-L56" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="690.0" y="60.0" width="160.0" height="64.0" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="n_highlights" value="highlights" tooltip="entity | Evidence: L37-L37" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxCell id="n_highlights" value="highlights" tooltip="entity | Evidence: L48-L48" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="695.0" y="196.0" width="150.0" height="64.0" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="e_feed-items-have-highlights" value="여러 highlights를 가짐" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_feed-items" target="n_highlights">
|
||||
<mxCell id="e_feed-items-have-highlights" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_feed-items" target="n_highlights">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="636.5" y="198.5" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="e_feed-items-to-mentions" value="피드 아이템을 연결" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_feed-items" target="n_feed-item-mentions">
|
||||
<mxCell id="e_feed-items-to-mentions" value="피드 아이템" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_feed-items" target="n_feed-item-mentions">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="634.0" y="126.0" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="e_pages-have-feed-items" value="여러 feed_item이 딸림" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_pages" target="n_feed-items">
|
||||
<mxCell id="e_pages-have-feed-items" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_pages" target="n_feed-items">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="324.0" y="198.5" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="e_users-have-feed-items" value="여러 feed_item을 가짐" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_users" target="n_feed-items">
|
||||
<mxCell id="e_users-have-feed-items" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_users" target="n_feed-items">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="324.0" y="126.0" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="e_users-to-feed-item-mentions" value="mentioned 사용자로 연결" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_users" target="n_feed-item-mentions">
|
||||
<mxCell id="e_users-to-feed-item-mentions" value="mentioned 사용자" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_users" target="n_feed-item-mentions">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="455.0" y="55.0" as="offset"/>
|
||||
</mxGeometry>
|
||||
|
||||
@@ -0,0 +1,853 @@
|
||||
{
|
||||
"type": "excalidraw",
|
||||
"version": 2,
|
||||
"source": "techviz-harness",
|
||||
"elements": [
|
||||
{
|
||||
"id": "edge-feed-items-have-highlights",
|
||||
"type": "arrow",
|
||||
"x": 530.0,
|
||||
"y": 169.0,
|
||||
"width": 165.0,
|
||||
"height": 59.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": null,
|
||||
"seed": 1670845141,
|
||||
"version": 1,
|
||||
"versionNonce": 1130181305,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
82.5,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
82.5,
|
||||
59.0
|
||||
],
|
||||
[
|
||||
165.0,
|
||||
59.0
|
||||
]
|
||||
],
|
||||
"lastCommittedPoint": null,
|
||||
"startBinding": {
|
||||
"elementId": "node-feed-items",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"endBinding": {
|
||||
"elementId": "node-highlights",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow",
|
||||
"elbowed": true
|
||||
},
|
||||
{
|
||||
"id": "edge-label-feed-items-have-highlights",
|
||||
"type": "text",
|
||||
"x": 591.5,
|
||||
"y": 186.5,
|
||||
"width": 90,
|
||||
"height": 24,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1739141275,
|
||||
"version": 1,
|
||||
"versionNonce": 1481418449,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 13,
|
||||
"fontFamily": 5,
|
||||
"text": "1:N",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "1:N",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "edge-feed-items-to-mentions",
|
||||
"type": "arrow",
|
||||
"x": 530.0,
|
||||
"y": 101.0,
|
||||
"width": 160.0,
|
||||
"height": 50.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": null,
|
||||
"seed": 82102697,
|
||||
"version": 1,
|
||||
"versionNonce": 1614863002,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[
|
||||
0.0,
|
||||
50.0
|
||||
],
|
||||
[
|
||||
80.0,
|
||||
50.0
|
||||
],
|
||||
[
|
||||
80.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
160.0,
|
||||
0.0
|
||||
]
|
||||
],
|
||||
"lastCommittedPoint": null,
|
||||
"startBinding": {
|
||||
"elementId": "node-feed-items",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"endBinding": {
|
||||
"elementId": "node-feed-item-mentions",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow",
|
||||
"elbowed": true
|
||||
},
|
||||
{
|
||||
"id": "edge-label-feed-items-to-mentions",
|
||||
"type": "text",
|
||||
"x": 589.0,
|
||||
"y": 114.0,
|
||||
"width": 90,
|
||||
"height": 24,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 373616939,
|
||||
"version": 1,
|
||||
"versionNonce": 1642356364,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 13,
|
||||
"fontFamily": 5,
|
||||
"text": "피드 아이템",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "피드 아이템",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "edge-pages-have-feed-items",
|
||||
"type": "arrow",
|
||||
"x": 220.0,
|
||||
"y": 169.0,
|
||||
"width": 160.0,
|
||||
"height": 59.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": null,
|
||||
"seed": 1351293053,
|
||||
"version": 1,
|
||||
"versionNonce": 766527047,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[
|
||||
0.0,
|
||||
59.0
|
||||
],
|
||||
[
|
||||
80.0,
|
||||
59.0
|
||||
],
|
||||
[
|
||||
80.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
160.0,
|
||||
0.0
|
||||
]
|
||||
],
|
||||
"lastCommittedPoint": null,
|
||||
"startBinding": {
|
||||
"elementId": "node-pages",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"endBinding": {
|
||||
"elementId": "node-feed-items",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow",
|
||||
"elbowed": true
|
||||
},
|
||||
{
|
||||
"id": "edge-label-pages-have-feed-items",
|
||||
"type": "text",
|
||||
"x": 279.0,
|
||||
"y": 186.5,
|
||||
"width": 90,
|
||||
"height": 24,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 453319515,
|
||||
"version": 1,
|
||||
"versionNonce": 1432469046,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 13,
|
||||
"fontFamily": 5,
|
||||
"text": "1:N",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "1:N",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "edge-users-have-feed-items",
|
||||
"type": "arrow",
|
||||
"x": 220.0,
|
||||
"y": 101.0,
|
||||
"width": 160.0,
|
||||
"height": 50.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": null,
|
||||
"seed": 568432588,
|
||||
"version": 1,
|
||||
"versionNonce": 1091126773,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
80.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
80.0,
|
||||
50.0
|
||||
],
|
||||
[
|
||||
160.0,
|
||||
50.0
|
||||
]
|
||||
],
|
||||
"lastCommittedPoint": null,
|
||||
"startBinding": {
|
||||
"elementId": "node-users",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"endBinding": {
|
||||
"elementId": "node-feed-items",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow",
|
||||
"elbowed": true
|
||||
},
|
||||
{
|
||||
"id": "edge-label-users-have-feed-items",
|
||||
"type": "text",
|
||||
"x": 279.0,
|
||||
"y": 114.0,
|
||||
"width": 90,
|
||||
"height": 24,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 776017595,
|
||||
"version": 1,
|
||||
"versionNonce": 515332551,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 13,
|
||||
"fontFamily": 5,
|
||||
"text": "1:N",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "1:N",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "edge-users-to-feed-item-mentions",
|
||||
"type": "arrow",
|
||||
"x": 220.0,
|
||||
"y": 83.0,
|
||||
"width": 470.0,
|
||||
"height": 0.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": null,
|
||||
"seed": 1095576195,
|
||||
"version": 1,
|
||||
"versionNonce": 875682076,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"points": [
|
||||
[
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
235.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
235.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
470.0,
|
||||
0.0
|
||||
]
|
||||
],
|
||||
"lastCommittedPoint": null,
|
||||
"startBinding": {
|
||||
"elementId": "node-users",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"endBinding": {
|
||||
"elementId": "node-feed-item-mentions",
|
||||
"focus": 0,
|
||||
"gap": 4
|
||||
},
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow",
|
||||
"elbowed": true
|
||||
},
|
||||
{
|
||||
"id": "edge-label-users-to-feed-item-mentions",
|
||||
"type": "text",
|
||||
"x": 403.0,
|
||||
"y": 43.0,
|
||||
"width": 104,
|
||||
"height": 24,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1537041547,
|
||||
"version": 1,
|
||||
"versionNonce": 1212755797,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 13,
|
||||
"fontFamily": 5,
|
||||
"text": "mentioned 사용자",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "mentioned 사용자",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "node-users",
|
||||
"type": "rectangle",
|
||||
"x": 70.0,
|
||||
"y": 60.0,
|
||||
"width": 150.0,
|
||||
"height": 64.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#ffffff",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1127380143,
|
||||
"version": 1,
|
||||
"versionNonce": 471339421,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "node-label-users",
|
||||
"type": "text",
|
||||
"x": 80.0,
|
||||
"y": 70.0,
|
||||
"width": 130.0,
|
||||
"height": 44.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 154326264,
|
||||
"version": 1,
|
||||
"versionNonce": 531211316,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 15,
|
||||
"fontFamily": 5,
|
||||
"text": "users",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "users",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "node-pages",
|
||||
"type": "rectangle",
|
||||
"x": 70.0,
|
||||
"y": 196.0,
|
||||
"width": 150.0,
|
||||
"height": 64.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#ffffff",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1932513815,
|
||||
"version": 1,
|
||||
"versionNonce": 1597034816,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "node-label-pages",
|
||||
"type": "text",
|
||||
"x": 80.0,
|
||||
"y": 206.0,
|
||||
"width": 130.0,
|
||||
"height": 44.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1992702022,
|
||||
"version": 1,
|
||||
"versionNonce": 1805895229,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 15,
|
||||
"fontFamily": 5,
|
||||
"text": "pages",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "pages",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "node-feed-items",
|
||||
"type": "rectangle",
|
||||
"x": 380.0,
|
||||
"y": 128.0,
|
||||
"width": 150.0,
|
||||
"height": 64.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#ffffff",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1553680117,
|
||||
"version": 1,
|
||||
"versionNonce": 1528008093,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "node-label-feed-items",
|
||||
"type": "text",
|
||||
"x": 390.0,
|
||||
"y": 138.0,
|
||||
"width": 130.0,
|
||||
"height": 44.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1561637253,
|
||||
"version": 1,
|
||||
"versionNonce": 189981097,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 15,
|
||||
"fontFamily": 5,
|
||||
"text": "feed_items",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "feed_items",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "node-feed-item-mentions",
|
||||
"type": "rectangle",
|
||||
"x": 690.0,
|
||||
"y": 60.0,
|
||||
"width": 160.0,
|
||||
"height": 64.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#ffffff",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 403282740,
|
||||
"version": 1,
|
||||
"versionNonce": 674769035,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "node-label-feed-item-mentions",
|
||||
"type": "text",
|
||||
"x": 700.0,
|
||||
"y": 70.0,
|
||||
"width": 140.0,
|
||||
"height": 44.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 447923518,
|
||||
"version": 1,
|
||||
"versionNonce": 1028562806,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 15,
|
||||
"fontFamily": 5,
|
||||
"text": "feed_item_mentions",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "feed_item_mentions",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "node-highlights",
|
||||
"type": "rectangle",
|
||||
"x": 695.0,
|
||||
"y": 196.0,
|
||||
"width": 150.0,
|
||||
"height": 64.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "#ffffff",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 281310605,
|
||||
"version": 1,
|
||||
"versionNonce": 933280984,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "node-label-highlights",
|
||||
"type": "text",
|
||||
"x": 705.0,
|
||||
"y": 206.0,
|
||||
"width": 130.0,
|
||||
"height": 44.0,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 0,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"index": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"seed": 1591294741,
|
||||
"version": 1,
|
||||
"versionNonce": 457414640,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 15,
|
||||
"fontFamily": 5,
|
||||
"text": "highlights",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "highlights",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
}
|
||||
],
|
||||
"appState": {
|
||||
"gridSize": 10,
|
||||
"viewBackgroundColor": "#ffffff",
|
||||
"currentItemFontFamily": 5
|
||||
},
|
||||
"files": {}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"harness_version": "0.2.0",
|
||||
"spec_id": "target-schema",
|
||||
"spec_version": "1.1",
|
||||
"spec_sha256": "7d91c3bd3b7fc82a08d475cec06db930af5635fd7f35b50436f32172be0cbae0",
|
||||
"source_context": {
|
||||
"document": "docs/n+1liner/final/document.md",
|
||||
"document_sha256": "f9e048a68db0ab82078955bf611b06a71a0033539e0c87ff0aa0d620a3126e36",
|
||||
"anchor": {
|
||||
"kind": "heading",
|
||||
"value": "3.1 관계와 스키마",
|
||||
"line": 44
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
"target-schema.svg",
|
||||
"target-schema.drawio",
|
||||
"target-schema.mmd",
|
||||
"target-schema.d2",
|
||||
"target-schema.dot",
|
||||
"target-schema.excalidraw",
|
||||
"target-schema.alt.md"
|
||||
],
|
||||
"lint_issue_count": 0,
|
||||
"assumption_count": 0,
|
||||
"assumptions_allowed": false,
|
||||
"composition_profile": "component-flow",
|
||||
"reference_ids": [
|
||||
"payment-event-flow"
|
||||
],
|
||||
"diagram_only": true
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
%% 목표 스키마에 추가되는 mention 관계
|
||||
%% question: 목표 모델에서 기존 users는 mentioned 사용자 역할로 feed_item_mentions에 어떻게 연결되는가?
|
||||
flowchart LR
|
||||
n0["users"]
|
||||
n1["pages"]
|
||||
n2["feed_items"]
|
||||
n3["highlights"]
|
||||
n4["feed_item_mentions"]
|
||||
n0 -->|"1:N"| n2
|
||||
n1 -->|"1:N"| n2
|
||||
n2 -->|"1:N"| n3
|
||||
n2 -->|"피드 아이템"| n4
|
||||
n0 -->|"mentioned 사용자"| n4
|
||||
@@ -2,7 +2,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="895" height="305" viewBox="0 0 895 305" role="img" aria-labelledby="diagram-title diagram-description">
|
||||
<title id="diagram-title">목표 스키마에 추가되는 mention 관계</title>
|
||||
<desc id="diagram-description">왼쪽의 users와 pages가 중앙의 feed_items에 연결된다. 오른쪽에는 highlights와 feed_item_mentions가 놓인다. feed_items는 두 엔티티에 각각 연결되고, 기존 users도 mentioned 사용자 역할로 feed_item_mentions에 연결된다.</desc>
|
||||
<metadata>{"techviz":{"spec_version":"1.1","id":"target-schema","profile":"component-flow"},"source_context":{"document":"/home/donghyeon/workspace/ai-tool/topic-arrange/n+1liner/n+1liner.md","document_sha256":"1bb38964ca2a849690f5355646c1aa988111b4cd4e1055c6cb05cf7e5fc35cde","anchor":{"kind":"marker","value":"target-schema","line":45}},"evidence_policy":"Each factual element cites source lines or is marked assumption.","diagram_only":true}</metadata>
|
||||
<metadata>{"techviz":{"spec_version":"1.1","id":"target-schema","profile":"component-flow"},"source_context":{"document":"docs/n+1liner/final/document.md","document_sha256":"f9e048a68db0ab82078955bf611b06a71a0033539e0c87ff0aa0d620a3126e36","anchor":{"kind":"heading","value":"3.1 관계와 스키마","line":44}},"evidence_policy":"Each factual element cites source lines or is marked assumption.","diagram_only":true}</metadata>
|
||||
<defs>
|
||||
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" />
|
||||
@@ -50,39 +50,39 @@
|
||||
</style>
|
||||
</defs>
|
||||
<rect class="canvas" width="895" height="305" />
|
||||
<polyline class="edge kind-relationship style-solid emphasis-normal" points="530.0,169.0 612.5,169.0 612.5,228.0 695.0,228.0" data-evidence="37-37" />
|
||||
<rect class="edge-label-bg" x="570.5" y="184.5" width="131.9" height="22" rx="3" />
|
||||
<text class="edge-label" x="636.5" y="199.5">여러 highlights를 가짐</text>
|
||||
<polyline class="edge kind-relationship style-solid emphasis-normal" points="530.0,151.0 610.0,151.0 610.0,101.0 690.0,101.0" data-evidence="43-43" />
|
||||
<rect class="edge-label-bg" x="591.5" y="112.0" width="85.0" height="22" rx="3" />
|
||||
<text class="edge-label" x="634.0" y="127.0">피드 아이템을 연결</text>
|
||||
<polyline class="edge kind-relationship style-solid emphasis-normal" points="220.0,228.0 300.0,228.0 300.0,169.0 380.0,169.0" data-evidence="36-36" />
|
||||
<rect class="edge-label-bg" x="261.4" y="184.5" width="125.2" height="22" rx="3" />
|
||||
<text class="edge-label" x="324.0" y="199.5">여러 feed_item이 딸림</text>
|
||||
<polyline class="edge kind-relationship style-solid emphasis-normal" points="220.0,101.0 300.0,101.0 300.0,151.0 380.0,151.0" data-evidence="35-35" />
|
||||
<rect class="edge-label-bg" x="261.4" y="112.0" width="125.2" height="22" rx="3" />
|
||||
<text class="edge-label" x="324.0" y="127.0">여러 feed_item을 가짐</text>
|
||||
<polyline class="edge kind-relationship style-solid emphasis-normal" points="220.0,83.0 455.0,83.0 455.0,83.0 690.0,83.0" data-evidence="43-43" />
|
||||
<rect class="edge-label-bg" x="389.1" y="41.0" width="131.9" height="22" rx="3" />
|
||||
<text class="edge-label" x="455.0" y="56.0">mentioned 사용자로 연결</text>
|
||||
<polyline class="edge kind-relationship style-solid emphasis-normal" points="530.0,169.0 612.5,169.0 612.5,228.0 695.0,228.0" data-evidence="48-48" />
|
||||
<rect class="edge-label-bg" x="614.5" y="184.5" width="44.0" height="22" rx="3" />
|
||||
<text class="edge-label" x="636.5" y="199.5">1:N</text>
|
||||
<polyline class="edge kind-relationship style-solid emphasis-normal" points="530.0,151.0 610.0,151.0 610.0,101.0 690.0,101.0" data-evidence="56-56" />
|
||||
<rect class="edge-label-bg" x="604.9" y="112.0" width="58.2" height="22" rx="3" />
|
||||
<text class="edge-label" x="634.0" y="127.0">피드 아이템</text>
|
||||
<polyline class="edge kind-relationship style-solid emphasis-normal" points="220.0,228.0 300.0,228.0 300.0,169.0 380.0,169.0" data-evidence="47-47" />
|
||||
<rect class="edge-label-bg" x="302.0" y="184.5" width="44.0" height="22" rx="3" />
|
||||
<text class="edge-label" x="324.0" y="199.5">1:N</text>
|
||||
<polyline class="edge kind-relationship style-solid emphasis-normal" points="220.0,101.0 300.0,101.0 300.0,151.0 380.0,151.0" data-evidence="46-46" />
|
||||
<rect class="edge-label-bg" x="302.0" y="112.0" width="44.0" height="22" rx="3" />
|
||||
<text class="edge-label" x="324.0" y="127.0">1:N</text>
|
||||
<polyline class="edge kind-relationship style-solid emphasis-normal" points="220.0,83.0 455.0,83.0 455.0,83.0 690.0,83.0" data-evidence="56-56" />
|
||||
<rect class="edge-label-bg" x="402.4" y="41.0" width="105.1" height="22" rx="3" />
|
||||
<text class="edge-label" x="455.0" y="56.0">mentioned 사용자</text>
|
||||
<g id="node-users">
|
||||
<rect class="node-shape kind-entity emphasis-normal role-source" data-evidence="35-35" x="70.0" y="60.0" width="150.0" height="64.0" rx="7" />
|
||||
<rect class="node-shape kind-entity emphasis-normal role-source" data-evidence="46-46" x="70.0" y="60.0" width="150.0" height="64.0" rx="7" />
|
||||
<text class="node-label" x="145.0" y="90.0">users</text>
|
||||
</g>
|
||||
<g id="node-pages">
|
||||
<rect class="node-shape kind-entity emphasis-normal role-source" data-evidence="36-36" x="70.0" y="196.0" width="150.0" height="64.0" rx="7" />
|
||||
<rect class="node-shape kind-entity emphasis-normal role-source" data-evidence="47-47" x="70.0" y="196.0" width="150.0" height="64.0" rx="7" />
|
||||
<text class="node-label" x="145.0" y="226.0">pages</text>
|
||||
</g>
|
||||
<g id="node-feed-items">
|
||||
<rect class="node-shape kind-entity emphasis-normal role-store" data-evidence="35-35" x="380.0" y="128.0" width="150.0" height="64.0" rx="7" />
|
||||
<rect class="node-shape kind-entity emphasis-normal role-store" data-evidence="46-46" x="380.0" y="128.0" width="150.0" height="64.0" rx="7" />
|
||||
<text class="node-label" x="455.0" y="158.0">feed_items</text>
|
||||
</g>
|
||||
<g id="node-feed-item-mentions">
|
||||
<rect class="node-shape kind-entity emphasis-normal role-sink" data-evidence="43-43" x="690.0" y="60.0" width="160.0" height="64.0" rx="7" />
|
||||
<rect class="node-shape kind-entity emphasis-normal role-sink" data-evidence="56-56" x="690.0" y="60.0" width="160.0" height="64.0" rx="7" />
|
||||
<text class="node-label" x="770.0" y="90.0">feed_item_mentions</text>
|
||||
</g>
|
||||
<g id="node-highlights">
|
||||
<rect class="node-shape kind-entity emphasis-normal role-sink" data-evidence="37-37" x="695.0" y="196.0" width="150.0" height="64.0" rx="7" />
|
||||
<rect class="node-shape kind-entity emphasis-normal role-sink" data-evidence="48-48" x="695.0" y="196.0" width="150.0" height="64.0" rx="7" />
|
||||
<text class="node-label" x="770.0" y="226.0">highlights</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 6.9 KiB |
@@ -1,45 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="700" height="230" viewBox="0 0 700 230" role="img" aria-labelledby="diagram-title diagram-description">
|
||||
<title id="diagram-title">배치 페치는 부모를 먼저 페이징하고 자식을 IN 으로 묶는다</title>
|
||||
<desc id="diagram-description">왼쪽에서 엔티티만 페이징해 DB Limit이 정상 발행되고 부모 페이지가 정해진다. 가운데에서 그 페이지의 부모 키를 모아 자식을 IN 조건 한 번으로 가져온다. 오른쪽 실행계획은 준조인이라 부모와 자식을 곱하지 않고 자식 행만 반환한다. 왕복 수는 부모 수를 배치 크기로 나눈 올림값이 된다.</desc>
|
||||
<defs>
|
||||
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" />
|
||||
</marker>
|
||||
<pattern id="hatch" width="7" height="7" patternTransform="rotate(45)" patternUnits="userSpaceOnUse">
|
||||
<line x1="0" y1="0" x2="0" y2="7" stroke="#9ca3af" stroke-width="3" />
|
||||
</pattern>
|
||||
<style>
|
||||
:root { color-scheme: light; }
|
||||
text { font-family: Inter, Pretendard, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #111827; }
|
||||
.canvas { fill: #ffffff; }
|
||||
.group-box { fill: #ffffff; stroke: #9ca3af; stroke-width: 1.4; stroke-dasharray: 7 5; }
|
||||
.group-label-bg { fill: #ffffff; }
|
||||
.group-label { font-size: 13px; font-weight: 650; fill: #374151; }
|
||||
.edge { fill: none; stroke: #374151; stroke-width: 1.8; stroke-linejoin: round; stroke-linecap: round; marker-end: url(#arrow); }
|
||||
.edge.dashed { stroke-dasharray: 7 5; }
|
||||
.edge.primary { stroke: #2563eb; stroke-width: 2.2; }
|
||||
.edge.muted { stroke: #9ca3af; }
|
||||
.node { fill: #ffffff; stroke: #4b5563; stroke-width: 1.7; }
|
||||
.node.primary { stroke: #2563eb; stroke-width: 2.4; }
|
||||
.node.muted { stroke: #9ca3af; fill: #f9fafb; }
|
||||
.node.marked { stroke: #4b5563; stroke-width: 2.4; stroke-dasharray: 5 4; }
|
||||
.label { font-size: 14px; font-weight: 650; text-anchor: middle; }
|
||||
.fill-kept { fill: #2563eb; stroke: none; }
|
||||
.fill-drop { fill: url(#hatch); stroke: #9ca3af; stroke-width: 1; }
|
||||
.fill-none { fill: #ffffff; stroke: #d1d5db; stroke-width: 1; }
|
||||
</style>
|
||||
</defs>
|
||||
<rect class="canvas" width="700" height="230" />
|
||||
<rect class="node primary" x="40" y="70" width="160" height="80" rx="7" />
|
||||
<text class="label" x="120.0" y="115.0">부모 페이징</text>
|
||||
<rect class="node primary" x="265" y="60" width="170" height="100" rx="7" />
|
||||
<text class="label" x="350.0" y="115.0">부모 키 IN</text>
|
||||
<rect class="node " x="500" y="70" width="160" height="80" rx="7" />
|
||||
<text class="label" x="580.0" y="115.0">준조인</text>
|
||||
<polyline class="edge primary" points="200,110 265,110" />
|
||||
<polyline class="edge primary" points="435,110 500,110" />
|
||||
<rect class="group-box" x="20" y="178" width="660" height="40" rx="9" />
|
||||
<rect class="group-label-bg" x="34" y="169" width="97.6" height="18" />
|
||||
<text class="group-label" x="40" y="182">자식은 따로 가져온다</text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.1 KiB |
@@ -1,45 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="700" height="250" viewBox="0 0 700 250" role="img" aria-labelledby="diagram-title diagram-description">
|
||||
<title id="diagram-title">컬렉션 fetch join은 부모 행을 자식 수만큼 늘린다</title>
|
||||
<desc id="diagram-description">왼쪽 부모 테이블에서 출발한 조인이 가운데를 지나면서 부모 한 행을 자식 수만큼 반복한 행 묶음으로 만든다. 그 묶음이 오른쪽 전송 단계로 그대로 나간다. 아래쪽에서 Hibernate 6 이상이 루트 엔티티를 중복 제거해 결과 리스트를 부모 수로 되돌리지만, 늘어난 행은 SQL과 전송 단계에 남아 있다. 그래서 리스트 크기로는 이 증가가 보이지 않고 조인 카디널리티나 실행계획의 실제 행수로 확인해야 한다.</desc>
|
||||
<defs>
|
||||
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" />
|
||||
</marker>
|
||||
<pattern id="hatch" width="7" height="7" patternTransform="rotate(45)" patternUnits="userSpaceOnUse">
|
||||
<line x1="0" y1="0" x2="0" y2="7" stroke="#9ca3af" stroke-width="3" />
|
||||
</pattern>
|
||||
<style>
|
||||
:root { color-scheme: light; }
|
||||
text { font-family: Inter, Pretendard, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #111827; }
|
||||
.canvas { fill: #ffffff; }
|
||||
.group-box { fill: #ffffff; stroke: #9ca3af; stroke-width: 1.4; stroke-dasharray: 7 5; }
|
||||
.group-label-bg { fill: #ffffff; }
|
||||
.group-label { font-size: 13px; font-weight: 650; fill: #374151; }
|
||||
.edge { fill: none; stroke: #374151; stroke-width: 1.8; stroke-linejoin: round; stroke-linecap: round; marker-end: url(#arrow); }
|
||||
.edge.dashed { stroke-dasharray: 7 5; }
|
||||
.edge.primary { stroke: #2563eb; stroke-width: 2.2; }
|
||||
.edge.muted { stroke: #9ca3af; }
|
||||
.node { fill: #ffffff; stroke: #4b5563; stroke-width: 1.7; }
|
||||
.node.primary { stroke: #2563eb; stroke-width: 2.4; }
|
||||
.node.muted { stroke: #9ca3af; fill: #f9fafb; }
|
||||
.node.marked { stroke: #4b5563; stroke-width: 2.4; stroke-dasharray: 5 4; }
|
||||
.label { font-size: 14px; font-weight: 650; text-anchor: middle; }
|
||||
.fill-kept { fill: #2563eb; stroke: none; }
|
||||
.fill-drop { fill: url(#hatch); stroke: #9ca3af; stroke-width: 1; }
|
||||
.fill-none { fill: #ffffff; stroke: #d1d5db; stroke-width: 1; }
|
||||
</style>
|
||||
</defs>
|
||||
<rect class="canvas" width="700" height="250" />
|
||||
<rect class="node " x="40" y="70" width="150" height="80" rx="7" />
|
||||
<text class="label" x="115.0" y="115.0">부모 테이블</text>
|
||||
<rect class="node primary" x="255" y="60" width="170" height="100" rx="7" />
|
||||
<text class="label" x="340.0" y="115.0">조인</text>
|
||||
<rect class="node " x="490" y="70" width="170" height="80" rx="7" />
|
||||
<text class="label" x="575.0" y="115.0">전송 행</text>
|
||||
<rect class="node muted" x="490" y="175" width="170" height="60" rx="7" />
|
||||
<text class="label" x="575.0" y="210.0">결과 리스트</text>
|
||||
<polyline class="edge " points="190,110 255,110" />
|
||||
<polyline class="edge primary" points="425,110 490,110" />
|
||||
<polyline class="edge dashed muted" points="575,150 575,175" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.1 KiB |
@@ -1,80 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="680" height="542" viewBox="0 0 680 542" role="img" aria-labelledby="diagram-title diagram-description">
|
||||
<title id="diagram-title">EAGER 2차 조회는 반환 전에, LAZY highlights 조회는 매핑 접근 뒤에 실행된다</title>
|
||||
<desc id="diagram-description">세 참가자를 왼쪽부터 loadFeed DTO 매핑, Hibernate, PostgreSQL 순으로 읽는다. loadFeed가 findAllBy 파생 쿼리를 호출하면 Hibernate가 PostgreSQL에서 feed_items를 먼저 조회한다. 이어 fetch join되지 않은 EAGER user와 page를 별도의 2차 SELECT로 채우고, 반환 시점까지 로딩된 FeedItem을 loadFeed에 돌려준다. 이후 DTO 매핑이 getHighlights()에 접근하면 Hibernate가 해당 아이템의 highlights 컬렉션 SELECT를 실행한다.</desc>
|
||||
<metadata>{"techviz":{"spec_version":"1.1","id":"eager-lazy-query-sequence","profile":"sequence"},"source_context":{"document":"/home/donghyeon/workspace/ai-tool/topic-arrange/n+1liner/n+1liner.md","document_sha256":"1bb38964ca2a849690f5355646c1aa988111b4cd4e1055c6cb05cf7e5fc35cde","anchor":{"kind":"marker","value":"eager-lazy-query-sequence","line":324}},"evidence_policy":"Each factual element cites source lines or is marked assumption.","diagram_only":true}</metadata>
|
||||
<defs>
|
||||
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" />
|
||||
</marker>
|
||||
<style>
|
||||
:root { color-scheme: light; }
|
||||
text { font-family: Inter, Pretendard, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #111827; }
|
||||
.canvas { fill: #ffffff; }
|
||||
.group-box { fill: #ffffff; stroke: #9ca3af; stroke-width: 1.4; stroke-dasharray: 7 5; }
|
||||
.group-label-bg { fill: #ffffff; }
|
||||
.group-label { font-size: 13px; font-weight: 650; fill: #374151; }
|
||||
.edge { fill: none; stroke: #374151; stroke-width: 1.8; stroke-linejoin: round; stroke-linecap: round; marker-end: url(#arrow); }
|
||||
.edge.style-dashed, .edge.semantic-dashed, .edge.assumption { stroke-dasharray: 7 5; }
|
||||
.edge.style-dotted { stroke-dasharray: 2 5; }
|
||||
.edge.emphasis-primary { stroke: #2563eb; stroke-width: 2.2; }
|
||||
.edge.emphasis-muted { stroke: #9ca3af; }
|
||||
.edge.emphasis-warning, .edge.kind-failure, .edge.kind-error { stroke: #dc2626; stroke-width: 2.2; }
|
||||
.edge-label-bg { fill: #ffffff; }
|
||||
.edge-label { font-size: 12px; font-weight: 560; text-anchor: middle; }
|
||||
.node-shape { fill: #ffffff; stroke: #4b5563; stroke-width: 1.7; }
|
||||
.node-shape.emphasis-primary { stroke: #2563eb; stroke-width: 2.2; }
|
||||
.node-shape.emphasis-muted { stroke: #9ca3af; fill: #f9fafb; }
|
||||
.node-shape.emphasis-warning { stroke: #d97706; stroke-width: 2; fill: #fffdf5; }
|
||||
.node-shape.kind-database, .node-shape.kind-datastore, .node-shape.kind-storage { fill: #f8fafc; }
|
||||
.node-shape.kind-queue, .node-shape.kind-event, .node-shape.kind-topic { fill: #fafafa; }
|
||||
.node-shape.assumption { stroke-dasharray: 4 4; }
|
||||
.storage-bottom, .controller-divider { fill: none; stroke: #4b5563; stroke-width: 1.4; }
|
||||
.controller-led { fill: #4b5563; }
|
||||
.actor-symbol { fill: none; stroke: #4b5563; stroke-width: 1.8; stroke-linecap: round; }
|
||||
.actor-symbol.emphasis-primary { stroke: #2563eb; stroke-width: 2.2; }
|
||||
.node-label { font-size: 14px; font-weight: 650; text-anchor: middle; }
|
||||
.node-role { font-size: 10px; letter-spacing: 0.04em; text-anchor: middle; fill: #6b7280; }
|
||||
.node-detail-divider { stroke: #d1d5db; stroke-width: 1; }
|
||||
.node-detail { font-size: 11px; fill: #374151; }
|
||||
.assumption-badge { font-size: 9px; font-weight: 700; fill: #92400e; }
|
||||
.failure-mark { stroke: #dc2626; stroke-width: 4; stroke-linecap: round; }
|
||||
.lifeline { stroke: #9ca3af; stroke-width: 1.2; stroke-dasharray: 5 5; }
|
||||
.timeline-axis { stroke: #374151; stroke-width: 1.8; marker-end: url(#arrow); }
|
||||
.timeline-stem { stroke: #6b7280; stroke-width: 1.3; }
|
||||
.timeline-marker { fill: #ffffff; stroke: #374151; stroke-width: 1.7; }
|
||||
.timeline-marker.primary { fill: #2563eb; stroke: #2563eb; }
|
||||
.timeline-marker.warning { fill: #dc2626; stroke: #dc2626; }
|
||||
.timeline-label { font-size: 13px; font-weight: 650; text-anchor: middle; }
|
||||
.timeline-detail { font-size: 11px; fill: #4b5563; text-anchor: middle; }
|
||||
</style>
|
||||
</defs>
|
||||
<rect class="canvas" width="680" height="542" />
|
||||
<rect class="node-shape kind-participant emphasis-normal role-participant" data-evidence="322-322,449-451" x="45.0" y="35.0" width="150.0" height="64.0" rx="7" />
|
||||
<text class="node-label" x="120.0" y="65.0">loadFeed DTO 매핑</text>
|
||||
<line class="lifeline" x1="120.0" y1="99.0" x2="120.0" y2="512.0" />
|
||||
<rect class="node-shape kind-participant emphasis-primary role-participant" data-evidence="320-322" x="255.0" y="35.0" width="150.0" height="64.0" rx="7" />
|
||||
<text class="node-label" x="330.0" y="65.0">Hibernate</text>
|
||||
<line class="lifeline" x1="330.0" y1="99.0" x2="330.0" y2="512.0" />
|
||||
<rect class="node-shape kind-participant emphasis-normal role-participant" data-evidence="417-422,447-447" x="465.0" y="45.7" width="150.0" height="42.7" /><ellipse class="node-shape kind-participant emphasis-normal role-participant" cx="540.0" cy="45.7" rx="75.0" ry="10.7" /><path class="storage-bottom" d="M 465.0 88.3 A 75.0 10.7 0 0 0 615.0 88.3" />
|
||||
<text class="node-label" x="540.0" y="65.0">PostgreSQL</text>
|
||||
<line class="lifeline" x1="540.0" y1="99.0" x2="540.0" y2="512.0" />
|
||||
<polyline class="edge kind-request style-solid emphasis-normal" points="120.0,140.0 330.0,140.0" data-evidence="320-320" />
|
||||
<rect class="edge-label-bg" x="159.1" y="114.0" width="131.9" height="22" rx="3" />
|
||||
<text class="edge-label" x="225.0" y="129.0">1. findAllBy(...)</text>
|
||||
<polyline class="edge kind-data style-solid emphasis-primary" points="330.0,202.0 540.0,202.0" data-evidence="320-320" />
|
||||
<rect class="edge-label-bg" x="359.0" y="176.0" width="152.0" height="22" rx="3" />
|
||||
<text class="edge-label" x="435.0" y="191.0">2. SELECT feed_items</text>
|
||||
<polyline class="edge kind-data style-solid emphasis-normal" points="330.0,264.0 540.0,264.0" data-evidence="318-321" />
|
||||
<rect class="edge-label-bg" x="318.8" y="238.0" width="232.4" height="22" rx="3" />
|
||||
<text class="edge-label" x="435.0" y="253.0">3. SELECT user / page · EAGER 2차</text>
|
||||
<polyline class="edge kind-response style-dashed emphasis-normal semantic-dashed" points="330.0,326.0 120.0,326.0" data-evidence="318-320" />
|
||||
<rect class="edge-label-bg" x="122.2" y="300.0" width="205.6" height="22" rx="3" />
|
||||
<text class="edge-label" x="225.0" y="315.0">4. EAGER 연관이 채워진 FeedItem 반환</text>
|
||||
<polyline class="edge kind-request style-solid emphasis-normal" points="120.0,388.0 330.0,388.0" data-evidence="322-322,449-451" />
|
||||
<rect class="edge-label-bg" x="128.9" y="362.0" width="192.2" height="22" rx="3" />
|
||||
<text class="edge-label" x="225.0" y="377.0">5. 매핑 중 getHighlights() 접근</text>
|
||||
<polyline class="edge kind-data style-solid emphasis-primary" points="330.0,450.0 540.0,450.0" data-evidence="322-322,429-430" />
|
||||
<rect class="edge-label-bg" x="281.9" y="424.0" width="306.1" height="22" rx="3" />
|
||||
<text class="edge-label" x="435.0" y="439.0">6. SELECT highlights WHERE feed_item_id = ?</text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 7.4 KiB |
@@ -1,56 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="700" height="290" viewBox="0 0 700 290" role="img" aria-labelledby="diagram-title diagram-description">
|
||||
<title id="diagram-title">컬렉션 fetch join에 페이징을 걸면 자르는 곳이 DB에서 메모리로 옮겨진다</title>
|
||||
<desc id="diagram-description">위쪽은 컬렉션 fetch join에 페이징을 건 경로다. 발행 SQL에 Limit 노드가 없어 조인 결과 전체가 애플리케이션으로 넘어오고, Hibernate가 메모리에서 부모 페이지만 남기고 나머지를 버린다. 아래쪽은 엔티티만 페이징한 경로다. Limit 노드가 붙어 DB가 페이지만 돌려주므로 애플리케이션은 버릴 것을 받지 않는다. 두 경로의 반환 건수는 같지만 위쪽은 부모 전체를 하이드레이트한다.</desc>
|
||||
<defs>
|
||||
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" />
|
||||
</marker>
|
||||
<pattern id="hatch" width="7" height="7" patternTransform="rotate(45)" patternUnits="userSpaceOnUse">
|
||||
<line x1="0" y1="0" x2="0" y2="7" stroke="#9ca3af" stroke-width="3" />
|
||||
</pattern>
|
||||
<style>
|
||||
:root { color-scheme: light; }
|
||||
text { font-family: Inter, Pretendard, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #111827; }
|
||||
.canvas { fill: #ffffff; }
|
||||
.group-box { fill: #ffffff; stroke: #9ca3af; stroke-width: 1.4; stroke-dasharray: 7 5; }
|
||||
.group-label-bg { fill: #ffffff; }
|
||||
.group-label { font-size: 13px; font-weight: 650; fill: #374151; }
|
||||
.edge { fill: none; stroke: #374151; stroke-width: 1.8; stroke-linejoin: round; stroke-linecap: round; marker-end: url(#arrow); }
|
||||
.edge.dashed { stroke-dasharray: 7 5; }
|
||||
.edge.primary { stroke: #2563eb; stroke-width: 2.2; }
|
||||
.edge.muted { stroke: #9ca3af; }
|
||||
.node { fill: #ffffff; stroke: #4b5563; stroke-width: 1.7; }
|
||||
.node.primary { stroke: #2563eb; stroke-width: 2.4; }
|
||||
.node.muted { stroke: #9ca3af; fill: #f9fafb; }
|
||||
.node.marked { stroke: #4b5563; stroke-width: 2.4; stroke-dasharray: 5 4; }
|
||||
.label { font-size: 14px; font-weight: 650; text-anchor: middle; }
|
||||
.fill-kept { fill: #2563eb; stroke: none; }
|
||||
.fill-drop { fill: url(#hatch); stroke: #9ca3af; stroke-width: 1; }
|
||||
.fill-none { fill: #ffffff; stroke: #d1d5db; stroke-width: 1; }
|
||||
</style>
|
||||
</defs>
|
||||
<rect class="canvas" width="700" height="290" />
|
||||
<rect class="group-box" x="30" y="50" width="640" height="96" rx="9" />
|
||||
<rect class="group-label-bg" x="44" y="41" width="166.0" height="18" />
|
||||
<text class="group-label" x="50" y="54">컬렉션 fetch join + 페이징</text>
|
||||
<rect class="node " x="55" y="74" width="165" height="56" rx="7" />
|
||||
<text class="label" x="137.5" y="107.0">조인 결과 전량</text>
|
||||
<rect class="node primary" x="275" y="74" width="165" height="56" rx="7" />
|
||||
<text class="label" x="357.5" y="107.0">메모리에서 자름</text>
|
||||
<rect class="node " x="495" y="74" width="150" height="56" rx="7" />
|
||||
<text class="label" x="570.0" y="107.0">페이지</text>
|
||||
<polyline class="edge " points="220,102 275,102" />
|
||||
<polyline class="edge " points="440,102 495,102" />
|
||||
<rect class="group-box" x="30" y="186" width="640" height="96" rx="9" />
|
||||
<rect class="group-label-bg" x="44" y="177" width="74.8" height="18" />
|
||||
<text class="group-label" x="50" y="190">엔티티만 페이징</text>
|
||||
<rect class="node primary" x="55" y="210" width="165" height="56" rx="7" />
|
||||
<text class="label" x="137.5" y="243.0">정렬 + Limit</text>
|
||||
<rect class="node primary" x="275" y="210" width="165" height="56" rx="7" />
|
||||
<text class="label" x="357.5" y="243.0">DB가 자름</text>
|
||||
<rect class="node " x="495" y="210" width="150" height="56" rx="7" />
|
||||
<text class="label" x="570.0" y="243.0">페이지</text>
|
||||
<polyline class="edge primary" points="220,238 275,238" />
|
||||
<polyline class="edge primary" points="440,238 495,238" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.9 KiB |
@@ -1,48 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="720" height="280" viewBox="0 0 720 280" role="img" aria-labelledby="diagram-title diagram-description">
|
||||
<title id="diagram-title">OFFSET은 건너뛸 행까지 읽고 버리지만 keyset은 커서 이후만 읽는다</title>
|
||||
<desc id="diagram-description">위쪽 OFFSET 막대에서 빗금 구간은 정렬 순서상 앞에 있어 만들어졌다가 버려지는 범위이고, 오른쪽 진한 구간이 실제로 반환되는 페이지다. 아래쪽 keyset 막대에서 빈 구간은 아예 읽지 않는 범위이고, 커서 표시 뒤부터 페이지만 읽는다. 페이지가 깊어질수록 위쪽 빗금 구간만 길어지고 아래쪽은 그대로다. 이 이점은 커서와 같은 순서의 정렬키 인덱스가 있을 때만 성립한다.</desc>
|
||||
<defs>
|
||||
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" />
|
||||
</marker>
|
||||
<pattern id="hatch" width="7" height="7" patternTransform="rotate(45)" patternUnits="userSpaceOnUse">
|
||||
<line x1="0" y1="0" x2="0" y2="7" stroke="#9ca3af" stroke-width="3" />
|
||||
</pattern>
|
||||
<style>
|
||||
:root { color-scheme: light; }
|
||||
text { font-family: Inter, Pretendard, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #111827; }
|
||||
.canvas { fill: #ffffff; }
|
||||
.group-box { fill: #ffffff; stroke: #9ca3af; stroke-width: 1.4; stroke-dasharray: 7 5; }
|
||||
.group-label-bg { fill: #ffffff; }
|
||||
.group-label { font-size: 13px; font-weight: 650; fill: #374151; }
|
||||
.edge { fill: none; stroke: #374151; stroke-width: 1.8; stroke-linejoin: round; stroke-linecap: round; marker-end: url(#arrow); }
|
||||
.edge.dashed { stroke-dasharray: 7 5; }
|
||||
.edge.primary { stroke: #2563eb; stroke-width: 2.2; }
|
||||
.edge.muted { stroke: #9ca3af; }
|
||||
.node { fill: #ffffff; stroke: #4b5563; stroke-width: 1.7; }
|
||||
.node.primary { stroke: #2563eb; stroke-width: 2.4; }
|
||||
.node.muted { stroke: #9ca3af; fill: #f9fafb; }
|
||||
.node.marked { stroke: #4b5563; stroke-width: 2.4; stroke-dasharray: 5 4; }
|
||||
.label { font-size: 14px; font-weight: 650; text-anchor: middle; }
|
||||
.fill-kept { fill: #2563eb; stroke: none; }
|
||||
.fill-drop { fill: url(#hatch); stroke: #9ca3af; stroke-width: 1; }
|
||||
.fill-none { fill: #ffffff; stroke: #d1d5db; stroke-width: 1; }
|
||||
</style>
|
||||
</defs>
|
||||
<rect class="canvas" width="720" height="280" />
|
||||
<rect class="group-box" x="30" y="46" width="660" height="84" rx="9" />
|
||||
<rect class="group-label-bg" x="44" y="37" width="59.599999999999994" height="18" />
|
||||
<text class="group-label" x="50" y="50">OFFSET</text>
|
||||
<rect class="fill-drop" x="55" y="76" width="500" height="34" rx="3" />
|
||||
<rect class="fill-kept" x="555" y="76" width="110" height="34" rx="3" />
|
||||
<text class="group-label" x="305" y="98" text-anchor="middle">버려지는 범위</text>
|
||||
<rect class="group-box" x="30" y="170" width="660" height="84" rx="9" />
|
||||
<rect class="group-label-bg" x="44" y="161" width="59.599999999999994" height="18" />
|
||||
<text class="group-label" x="50" y="174">keyset</text>
|
||||
<rect class="fill-none" x="55" y="200" width="500" height="34" rx="3" />
|
||||
<rect class="fill-kept" x="555" y="200" width="110" height="34" rx="3" />
|
||||
<text class="group-label" x="305" y="222" text-anchor="middle">읽지 않는 범위</text>
|
||||
<line x1="555" y1="188" x2="555" y2="246" stroke="#2563eb" stroke-width="2.4" />
|
||||
<text class="group-label" x="555" y="182" text-anchor="middle" fill="#2563eb">커서</text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.5 KiB |
@@ -1,45 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="660" height="230" viewBox="0 0 660 230" role="img" aria-labelledby="diagram-title diagram-description">
|
||||
<title id="diagram-title">프로젝션은 엔티티 적재를 없애지만 자식 행수는 줄지 않는다</title>
|
||||
<desc id="diagram-description">왼쪽 배치 방식은 페이지를 조회하면서 엔티티를 대량으로 하이드레이트한다. 가운데 프로젝션은 필요한 스칼라 값만 캐리어로 받아 엔티티를 만들지 않고 발행 쿼리도 상수로 고정한다. 다만 오른쪽처럼 자식 조회는 페이지 부모의 자식을 전부 가져오므로 행수는 줄지 않는다. 화면이 필요로 하는 것은 부모당 상위 몇 개뿐이다.</desc>
|
||||
<defs>
|
||||
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" />
|
||||
</marker>
|
||||
<pattern id="hatch" width="7" height="7" patternTransform="rotate(45)" patternUnits="userSpaceOnUse">
|
||||
<line x1="0" y1="0" x2="0" y2="7" stroke="#9ca3af" stroke-width="3" />
|
||||
</pattern>
|
||||
<style>
|
||||
:root { color-scheme: light; }
|
||||
text { font-family: Inter, Pretendard, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #111827; }
|
||||
.canvas { fill: #ffffff; }
|
||||
.group-box { fill: #ffffff; stroke: #9ca3af; stroke-width: 1.4; stroke-dasharray: 7 5; }
|
||||
.group-label-bg { fill: #ffffff; }
|
||||
.group-label { font-size: 13px; font-weight: 650; fill: #374151; }
|
||||
.edge { fill: none; stroke: #374151; stroke-width: 1.8; stroke-linejoin: round; stroke-linecap: round; marker-end: url(#arrow); }
|
||||
.edge.dashed { stroke-dasharray: 7 5; }
|
||||
.edge.primary { stroke: #2563eb; stroke-width: 2.2; }
|
||||
.edge.muted { stroke: #9ca3af; }
|
||||
.node { fill: #ffffff; stroke: #4b5563; stroke-width: 1.7; }
|
||||
.node.primary { stroke: #2563eb; stroke-width: 2.4; }
|
||||
.node.muted { stroke: #9ca3af; fill: #f9fafb; }
|
||||
.node.marked { stroke: #4b5563; stroke-width: 2.4; stroke-dasharray: 5 4; }
|
||||
.label { font-size: 14px; font-weight: 650; text-anchor: middle; }
|
||||
.fill-kept { fill: #2563eb; stroke: none; }
|
||||
.fill-drop { fill: url(#hatch); stroke: #9ca3af; stroke-width: 1; }
|
||||
.fill-none { fill: #ffffff; stroke: #d1d5db; stroke-width: 1; }
|
||||
</style>
|
||||
</defs>
|
||||
<rect class="canvas" width="660" height="230" />
|
||||
<rect class="node " x="30" y="70" width="170" height="80" rx="7" />
|
||||
<text class="label" x="115.0" y="115.0">엔티티 적재</text>
|
||||
<rect class="node primary" x="245" y="60" width="170" height="100" rx="7" />
|
||||
<text class="label" x="330.0" y="115.0">스칼라 프로젝션</text>
|
||||
<rect class="node marked" x="460" y="70" width="170" height="80" rx="7" />
|
||||
<text class="label" x="545.0" y="115.0">자식 행 전량</text>
|
||||
<polyline class="edge primary" points="200,110 245,110" />
|
||||
<polyline class="edge muted" points="415,110 460,110" />
|
||||
<rect class="group-box" x="20" y="178" width="620" height="40" rx="9" />
|
||||
<rect class="group-label-bg" x="34" y="169" width="135.6" height="18" />
|
||||
<text class="group-label" x="40" y="182">적재는 사라지고 행수는 남는다</text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.1 KiB |
@@ -1,49 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="720" height="300" viewBox="0 0 720 300" role="img" aria-labelledby="diagram-title diagram-description">
|
||||
<title id="diagram-title">그룹당 상위 몇 개를 만드는 세 방식은 읽는 범위가 다르다</title>
|
||||
<desc id="diagram-description">세 방식 모두 부모마다 상위 몇 개씩 같은 결과를 만든다. 윈도우 함수는 파티션 전체를 읽어 순번을 매긴 뒤 상위만 남긴다. LATERAL은 부모마다 정렬 인덱스에서 필요한 개수만 읽고 멈춘다. 애플리케이션 그룹핑은 자식 전량을 애플리케이션으로 보낸 뒤 코드에서 자른다. 읽는 범위가 다르므로 읽은 블록 수도 갈린다. 그룹이 크고 필요한 개수가 작을수록 LATERAL이 읽지 않는 범위가 넓어진다.</desc>
|
||||
<defs>
|
||||
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" />
|
||||
</marker>
|
||||
<pattern id="hatch" width="7" height="7" patternTransform="rotate(45)" patternUnits="userSpaceOnUse">
|
||||
<line x1="0" y1="0" x2="0" y2="7" stroke="#9ca3af" stroke-width="3" />
|
||||
</pattern>
|
||||
<style>
|
||||
:root { color-scheme: light; }
|
||||
text { font-family: Inter, Pretendard, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #111827; }
|
||||
.canvas { fill: #ffffff; }
|
||||
.group-box { fill: #ffffff; stroke: #9ca3af; stroke-width: 1.4; stroke-dasharray: 7 5; }
|
||||
.group-label-bg { fill: #ffffff; }
|
||||
.group-label { font-size: 13px; font-weight: 650; fill: #374151; }
|
||||
.edge { fill: none; stroke: #374151; stroke-width: 1.8; stroke-linejoin: round; stroke-linecap: round; marker-end: url(#arrow); }
|
||||
.edge.dashed { stroke-dasharray: 7 5; }
|
||||
.edge.primary { stroke: #2563eb; stroke-width: 2.2; }
|
||||
.edge.muted { stroke: #9ca3af; }
|
||||
.node { fill: #ffffff; stroke: #4b5563; stroke-width: 1.7; }
|
||||
.node.primary { stroke: #2563eb; stroke-width: 2.4; }
|
||||
.node.muted { stroke: #9ca3af; fill: #f9fafb; }
|
||||
.node.marked { stroke: #4b5563; stroke-width: 2.4; stroke-dasharray: 5 4; }
|
||||
.label { font-size: 14px; font-weight: 650; text-anchor: middle; }
|
||||
.fill-kept { fill: #2563eb; stroke: none; }
|
||||
.fill-drop { fill: url(#hatch); stroke: #9ca3af; stroke-width: 1; }
|
||||
.fill-none { fill: #ffffff; stroke: #d1d5db; stroke-width: 1; }
|
||||
</style>
|
||||
</defs>
|
||||
<rect class="canvas" width="720" height="300" />
|
||||
<rect class="node " x="40" y="56" width="190" height="62" rx="7" />
|
||||
<text class="label" x="135.0" y="92.0">윈도우 함수</text>
|
||||
<rect class="node " x="330" y="56" width="350" height="62" rx="7" />
|
||||
<text class="label" x="505.0" y="92.0">파티션 전체</text>
|
||||
<polyline class="edge " points="230,87 330,87" />
|
||||
<rect class="node primary" x="40" y="146" width="190" height="62" rx="7" />
|
||||
<text class="label" x="135.0" y="182.0">LATERAL</text>
|
||||
<rect class="node primary" x="330" y="146" width="350" height="62" rx="7" />
|
||||
<text class="label" x="505.0" y="182.0">부모별 인덱스 seek</text>
|
||||
<polyline class="edge primary" points="230,177 330,177" />
|
||||
<rect class="node " x="40" y="236" width="190" height="62" rx="7" />
|
||||
<text class="label" x="135.0" y="272.0">애플리케이션 그룹핑</text>
|
||||
<rect class="node muted" x="330" y="236" width="350" height="62" rx="7" />
|
||||
<text class="label" x="505.0" y="272.0">자식 전량</text>
|
||||
<polyline class="edge " points="230,267 330,267" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.4 KiB |