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 |