feat: 가상화 문서들 추가

This commit is contained in:
DongHyeonka
2026-09-10 08:54:05 +09:00
parent e9f6a93327
commit 43e1aadef0
695 changed files with 153404 additions and 12754 deletions
@@ -0,0 +1,26 @@
# 가상 머신 둘과 호스트 프로세스가 하나의 NVMe 로 모인다
## Alternative text
VM1 QEMU, VM2 QEMU, Nginx, Host 기타 네 곳에서 나온 화살표가 하나의 Host Block Layer 로 모이고 거기서 NVMe 로 나가는 수렴 흐름도.
## Long description
위쪽에 네 출발점이 나란히 있다. VM1 QEMU 는 WRITE X 와 READ Y, WRITE Z 를 내고 VM2 QEMU 는 READ A 와 WRITE B 를 내며, 그 옆에 호스트의 Nginx 와 다른 프로세스가 있다. 네 갈래가 모두 가운데의 Host Block Layer 하나로 모인다. Host Block Layer 는 그 I/O 가 가상 머신 안에서 시작했는지 호스트 프로세스에서 시작했는지를 본질적으로 구분해 처리하는 계층이 아니라 들어온 것을 모두 Host block request 로 다루고, 그 요청들을 queue 에서 관리해 맨 아래 NVMe 로 dispatch 한다. 여기서 생기는 경쟁이 Storage Contention 이고 CPU 실행 시간을 두고 벌어지는 CPU Contention 과는 다투는 자원이 다르다는 것은 본문 문단이 맡는다.
## Elements and evidence
- **VM1 QEMU** (service): 가상 머신 하나의 QEMU 프로세스. 게스트의 I/O 가 여기서 호스트 파일 I/O 가 된다. Evidence: L7029L7029, L7041L7044.
- **VM2 QEMU** (service): 다른 가상 머신의 QEMU 프로세스. Evidence: L7031L7031, L7046L7048.
- **Nginx** (service): 호스트에서 직접 도는 프로세스. Evidence: L7033L7033.
- **Host 기타** (service): 그 밖의 호스트 프로세스. Evidence: L7035L7038.
- **Host Block Layer** (service): 어디서 왔는지 구분하지 않고 모두 Host block request 로 다루는 계층. queue 에서 관리해 device 로 내보낸다. Evidence: L7031L7031, L7017L7022, L7054L7054, L7058L7060.
- **NVMe** (device): 네 갈래가 결국 함께 쓰는 하나의 물리 device. Evidence: L7031L7031, L7019L7019, L7066L7069.
## Relationships
- **VM1 QEMU → Host Block Layer:** data. Evidence: L7029L7031.
- **VM2 QEMU → Host Block Layer:** data. Evidence: L7031L7031.
- **Nginx → Host Block Layer:** data. Evidence: L7031L7033.
- **Host 기타 → Host Block Layer:** data. Evidence: L7031L7035.
- **Host Block Layer → NVMe:** dispatch. Evidence: L7031L7031, L7054L7054.
@@ -0,0 +1,26 @@
# 가상 머신 둘과 호스트 프로세스가 하나의 NVMe 로 모인다
# Question: 서로 다른 곳에서 시작한 I/O 는 어디에서 하나로 모여 device 로 나가는가
direction: down
n0: "VM1 QEMU" {
shape: rectangle
}
n1: "VM2 QEMU" {
shape: rectangle
}
n2: "Nginx" {
shape: rectangle
}
n3: "Host 기타" {
shape: rectangle
}
n4: "Host Block Layer" {
shape: rectangle
}
n5: "NVMe" {
shape: rectangle
}
n0 -> n4
n1 -> n4
n2 -> n4
n3 -> n4
n4 -> n5: "dispatch"
@@ -0,0 +1,54 @@
<?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="vms-sharing-one-nvme" name="가상 머신 둘과 호스트 프로세스가 하나의 NVMe 로 모인다">
<mxGraphModel dx="895" dy="580" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="895" pageHeight="1169" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="n_vm1-qemu" value="VM1 QEMU&lt;br/&gt;WRITE X&lt;br/&gt;READ Y&lt;br/&gt;WRITE Z" tooltip="가상 머신 하나의 QEMU 프로세스. 게스트의 I/O 가 여기서 호스트 파일 I/O 가 된다. | Evidence: L7029-L7029, L7041-L7044" 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="105.0" as="geometry"/>
</mxCell>
<mxCell id="n_vm2-qemu" value="VM2 QEMU&lt;br/&gt;READ A&lt;br/&gt;WRITE B" tooltip="다른 가상 머신의 QEMU 프로세스. | Evidence: L7031-L7031, L7046-L7048" 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="280.0" y="68.5" width="150.0" height="88.0" as="geometry"/>
</mxCell>
<mxCell id="n_nginx" value="Nginx" tooltip="호스트에서 직접 도는 프로세스. | Evidence: L7033-L7033" 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="490.0" y="80.5" width="150.0" height="64.0" as="geometry"/>
</mxCell>
<mxCell id="n_host-other" value="Host 기타" tooltip="그 밖의 호스트 프로세스. | Evidence: L7035-L7038" 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="700.0" y="80.5" width="150.0" height="64.0" as="geometry"/>
</mxCell>
<mxCell id="n_host-block-layer" value="Host Block Layer&lt;br/&gt;Host block request&lt;br/&gt;blk-mq" tooltip="어디서 왔는지 구분하지 않고 모두 Host block request 로 다루는 계층. queue 에서 관리해 device 로 내보낸다. | Evidence: L7031-L7031, L7017-L7022, L7054-L7054, L7058-L7060" 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="380.0" y="261.0" width="160.0" height="88.0" as="geometry"/>
</mxCell>
<mxCell id="n_nvme" value="NVMe" tooltip="네 갈래가 결국 함께 쓰는 하나의 물리 device. | Evidence: L7031-L7031, L7019-L7019, L7066-L7069" 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="385.0" y="445.0" width="150.0" height="90.0" as="geometry"/>
</mxCell>
<mxCell id="e_vm1-to-block-layer" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_vm1-qemu" target="n_host-block-layer">
<mxGeometry relative="1" as="geometry">
<mxPoint x="289.0" y="185.0" as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="e_vm2-to-block-layer" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_vm2-qemu" target="n_host-block-layer">
<mxGeometry relative="1" as="geometry">
<mxPoint x="403.0" y="180.8" as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="e_nginx-to-block-layer" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_nginx" target="n_host-block-layer">
<mxGeometry relative="1" as="geometry">
<mxPoint x="517.0" y="174.8" as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="e_host-other-to-block-layer" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_host-other" target="n_host-block-layer">
<mxGeometry relative="1" as="geometry">
<mxPoint x="631.0" y="174.8" as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="e_block-layer-to-nvme" value="dispatch" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_host-block-layer" target="n_nvme">
<mxGeometry relative="1" as="geometry">
<mxPoint x="484.0" y="397.0" as="offset"/>
</mxGeometry>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
@@ -0,0 +1,766 @@
{
"type": "excalidraw",
"version": 2,
"source": "techviz-harness",
"elements": [
{
"id": "edge-vm1-to-block-layer",
"type": "arrow",
"x": 145.0,
"y": 165.0,
"width": 288.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": 1225083499,
"version": 1,
"versionNonce": 1066380271,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false,
"points": [
[
0.0,
0.0
],
[
0.0,
48.0
],
[
288.0,
48.0
],
[
288.0,
96.0
]
],
"lastCommittedPoint": null,
"startBinding": {
"elementId": "node-vm1-qemu",
"focus": 0,
"gap": 4
},
"endBinding": {
"elementId": "node-host-block-layer",
"focus": 0,
"gap": 4
},
"startArrowhead": null,
"endArrowhead": "arrow",
"elbowed": true
},
{
"id": "edge-vm2-to-block-layer",
"type": "arrow",
"x": 355.0,
"y": 156.5,
"width": 96.0,
"height": 104.5,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": null,
"roundness": null,
"seed": 948207587,
"version": 1,
"versionNonce": 1141556592,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false,
"points": [
[
0.0,
0.0
],
[
0.0,
52.25
],
[
96.0,
52.25
],
[
96.0,
104.5
]
],
"lastCommittedPoint": null,
"startBinding": {
"elementId": "node-vm2-qemu",
"focus": 0,
"gap": 4
},
"endBinding": {
"elementId": "node-host-block-layer",
"focus": 0,
"gap": 4
},
"startArrowhead": null,
"endArrowhead": "arrow",
"elbowed": true
},
{
"id": "edge-nginx-to-block-layer",
"type": "arrow",
"x": 469.0,
"y": 144.5,
"width": 96.0,
"height": 116.5,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": null,
"roundness": null,
"seed": 414555304,
"version": 1,
"versionNonce": 162956757,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false,
"points": [
[
96.0,
0.0
],
[
96.0,
58.25
],
[
0.0,
58.25
],
[
0.0,
116.5
]
],
"lastCommittedPoint": null,
"startBinding": {
"elementId": "node-nginx",
"focus": 0,
"gap": 4
},
"endBinding": {
"elementId": "node-host-block-layer",
"focus": 0,
"gap": 4
},
"startArrowhead": null,
"endArrowhead": "arrow",
"elbowed": true
},
{
"id": "edge-host-other-to-block-layer",
"type": "arrow",
"x": 487.0,
"y": 144.5,
"width": 288.0,
"height": 116.5,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": null,
"roundness": null,
"seed": 1681052111,
"version": 1,
"versionNonce": 495490748,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false,
"points": [
[
288.0,
0.0
],
[
288.0,
58.25
],
[
0.0,
58.25
],
[
0.0,
116.5
]
],
"lastCommittedPoint": null,
"startBinding": {
"elementId": "node-host-other",
"focus": 0,
"gap": 4
},
"endBinding": {
"elementId": "node-host-block-layer",
"focus": 0,
"gap": 4
},
"startArrowhead": null,
"endArrowhead": "arrow",
"elbowed": true
},
{
"id": "edge-block-layer-to-nvme",
"type": "arrow",
"x": 460.0,
"y": 349.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": 1129222860,
"version": 1,
"versionNonce": 1630472271,
"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-host-block-layer",
"focus": 0,
"gap": 4
},
"endBinding": {
"elementId": "node-nvme",
"focus": 0,
"gap": 4
},
"startArrowhead": null,
"endArrowhead": "arrow",
"elbowed": true
},
{
"id": "edge-label-block-layer-to-nvme",
"type": "text",
"x": 439.0,
"y": 385.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": 181877595,
"version": 1,
"versionNonce": 1808212730,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false,
"fontSize": 13,
"fontFamily": 5,
"text": "dispatch",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": null,
"originalText": "dispatch",
"autoResize": true,
"lineHeight": 1.25
},
{
"id": "node-vm1-qemu",
"type": "rectangle",
"x": 70.0,
"y": 60.0,
"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": 845950122,
"version": 1,
"versionNonce": 605509420,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false
},
{
"id": "node-label-vm1-qemu",
"type": "text",
"x": 80.0,
"y": 70.0,
"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": 1628887011,
"version": 1,
"versionNonce": 506408877,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false,
"fontSize": 15,
"fontFamily": 5,
"text": "VM1 QEMU\nWRITE X\nREAD Y\nWRITE Z",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": null,
"originalText": "VM1 QEMU\nWRITE X\nREAD Y\nWRITE Z",
"autoResize": true,
"lineHeight": 1.25
},
{
"id": "node-vm2-qemu",
"type": "rectangle",
"x": 280.0,
"y": 68.5,
"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": 1622552226,
"version": 1,
"versionNonce": 1610483505,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false
},
{
"id": "node-label-vm2-qemu",
"type": "text",
"x": 290.0,
"y": 78.5,
"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": 892438862,
"version": 1,
"versionNonce": 41324946,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false,
"fontSize": 15,
"fontFamily": 5,
"text": "VM2 QEMU\nREAD A\nWRITE B",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": null,
"originalText": "VM2 QEMU\nREAD A\nWRITE B",
"autoResize": true,
"lineHeight": 1.25
},
{
"id": "node-nginx",
"type": "rectangle",
"x": 490.0,
"y": 80.5,
"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": 721013300,
"version": 1,
"versionNonce": 1520646945,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false
},
{
"id": "node-label-nginx",
"type": "text",
"x": 500.0,
"y": 90.5,
"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": 117243520,
"version": 1,
"versionNonce": 936748041,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false,
"fontSize": 15,
"fontFamily": 5,
"text": "Nginx",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": null,
"originalText": "Nginx",
"autoResize": true,
"lineHeight": 1.25
},
{
"id": "node-host-other",
"type": "rectangle",
"x": 700.0,
"y": 80.5,
"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": 1719667036,
"version": 1,
"versionNonce": 1594842564,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false
},
{
"id": "node-label-host-other",
"type": "text",
"x": 710.0,
"y": 90.5,
"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": 376776079,
"version": 1,
"versionNonce": 223147487,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false,
"fontSize": 15,
"fontFamily": 5,
"text": "Host 기타",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": null,
"originalText": "Host 기타",
"autoResize": true,
"lineHeight": 1.25
},
{
"id": "node-host-block-layer",
"type": "rectangle",
"x": 380.0,
"y": 261.0,
"width": 160.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": 923862125,
"version": 1,
"versionNonce": 1842771359,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false
},
{
"id": "node-label-host-block-layer",
"type": "text",
"x": 390.0,
"y": 271.0,
"width": 140.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": 117879901,
"version": 1,
"versionNonce": 785252073,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false,
"fontSize": 15,
"fontFamily": 5,
"text": "Host Block Layer\nHost block request\nblk-mq",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": null,
"originalText": "Host Block Layer\nHost block request\nblk-mq",
"autoResize": true,
"lineHeight": 1.25
},
{
"id": "node-nvme",
"type": "rectangle",
"x": 385.0,
"y": 445.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": 1447108727,
"version": 1,
"versionNonce": 491109462,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false
},
{
"id": "node-label-nvme",
"type": "text",
"x": 395.0,
"y": 455.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": 502338443,
"version": 1,
"versionNonce": 1220402361,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false,
"fontSize": 15,
"fontFamily": 5,
"text": "NVMe",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": null,
"originalText": "NVMe",
"autoResize": true,
"lineHeight": 1.25
}
],
"appState": {
"gridSize": 10,
"viewBackgroundColor": "#ffffff",
"currentItemFontFamily": 5
},
"files": {}
}
@@ -0,0 +1,31 @@
{
"harness_version": "0.2.0",
"spec_id": "vms-sharing-one-nvme",
"spec_version": "1.1",
"spec_sha256": "43a75d5863edb3e1585ebf97e76f7bd9bf773c90836d4f8f91936eb8a557bd28",
"source_context": {
"document": "docs/virtualization/final/document.md",
"document_sha256": "181e2b3cc8a45bae81e7e8193d026937c4d586ae4495d3b2c323eb7e6abcfadd",
"anchor": {
"kind": "heading",
"value": "159. 여러 VM이 하나의 NVMe를 공유하면",
"line": 7026
}
},
"outputs": [
"vms-sharing-one-nvme.svg",
"vms-sharing-one-nvme.drawio",
"vms-sharing-one-nvme.mmd",
"vms-sharing-one-nvme.d2",
"vms-sharing-one-nvme.excalidraw",
"vms-sharing-one-nvme.alt.md"
],
"lint_issue_count": 5,
"assumption_count": 0,
"assumptions_allowed": false,
"composition_profile": "component-flow",
"reference_ids": [
"payment-event-flow"
],
"diagram_only": true
}
@@ -0,0 +1,14 @@
%% 가상 머신 둘과 호스트 프로세스가 하나의 NVMe 로 모인다
%% question: 서로 다른 곳에서 시작한 I/O 는 어디에서 하나로 모여 device 로 나가는가
flowchart TB
n0["VM1 QEMU"]
n1["VM2 QEMU"]
n2["Nginx"]
n3["Host 기타"]
n4["Host Block Layer"]
n5["NVMe"]
n0 --> n4
n1 --> n4
n2 --> n4
n3 --> n4
n4 -->|"dispatch"| n5
@@ -0,0 +1,94 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="895" height="580" viewBox="0 0 895 580" role="img" aria-labelledby="diagram-title diagram-description">
<title id="diagram-title">가상 머신 둘과 호스트 프로세스가 하나의 NVMe 로 모인다</title>
<desc id="diagram-description">위쪽에 네 출발점이 나란히 있다. VM1 QEMU 는 WRITE X 와 READ Y, WRITE Z 를 내고 VM2 QEMU 는 READ A 와 WRITE B 를 내며, 그 옆에 호스트의 Nginx 와 다른 프로세스가 있다. 네 갈래가 모두 가운데의 Host Block Layer 하나로 모인다. Host Block Layer 는 그 I/O 가 가상 머신 안에서 시작했는지 호스트 프로세스에서 시작했는지를 본질적으로 구분해 처리하는 계층이 아니라 들어온 것을 모두 Host block request 로 다루고, 그 요청들을 queue 에서 관리해 맨 아래 NVMe 로 dispatch 한다. 여기서 생기는 경쟁이 Storage Contention 이고 CPU 실행 시간을 두고 벌어지는 CPU Contention 과는 다투는 자원이 다르다는 것은 본문 문단이 맡는다.</desc>
<metadata>{&quot;techviz&quot;:{&quot;spec_version&quot;:&quot;1.1&quot;,&quot;id&quot;:&quot;vms-sharing-one-nvme&quot;,&quot;profile&quot;:&quot;component-flow&quot;},&quot;source_context&quot;:{&quot;document&quot;:&quot;docs/virtualization/final/document.md&quot;,&quot;document_sha256&quot;:&quot;181e2b3cc8a45bae81e7e8193d026937c4d586ae4495d3b2c323eb7e6abcfadd&quot;,&quot;anchor&quot;:{&quot;kind&quot;:&quot;heading&quot;,&quot;value&quot;:&quot;159. 여러 VM이 하나의 NVMe를 공유하면&quot;,&quot;line&quot;:7026}},&quot;evidence_policy&quot;:&quot;Each factual element cites source lines or is marked assumption.&quot;,&quot;diagram_only&quot;: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="895" height="580" />
<polyline class="edge kind-data style-solid emphasis-normal" points="145.0,165.0 145.0,213.0 433.0,213.0 433.0,261.0" data-evidence="7029-7031" />
<polyline class="edge kind-data style-solid emphasis-normal" points="355.0,156.5 355.0,208.8 451.0,208.8 451.0,261.0" data-evidence="7031-7031" />
<polyline class="edge kind-data style-solid emphasis-normal" points="565.0,144.5 565.0,202.8 469.0,202.8 469.0,261.0" data-evidence="7031-7033" />
<polyline class="edge kind-data style-solid emphasis-normal" points="775.0,144.5 775.0,202.8 487.0,202.8 487.0,261.0" data-evidence="7031-7035" />
<polyline class="edge kind-data style-solid emphasis-normal" points="460.0,349.0 460.0,397.0 460.0,397.0 460.0,445.0" data-evidence="7031-7031,7054-7054" />
<rect class="edge-label-bg" x="448.2" y="383.0" width="71.6" height="22" rx="3" />
<text class="edge-label" x="484.0" y="398.0">dispatch</text>
<g id="node-vm1-qemu">
<rect class="node-shape kind-service emphasis-normal role-source" data-evidence="7029-7029,7041-7044" x="70.0" y="60.0" width="150.0" height="105.0" rx="7" />
<text class="node-label" x="145.0" y="87.0">VM1 QEMU</text>
<line class="node-detail-divider" x1="84.0" y1="108.0" x2="206.0" y2="108.0" />
<text class="node-detail" x="86.0" y="125.0">WRITE X</text>
<text class="node-detail" x="86.0" y="141.0">READ Y</text>
<text class="node-detail" x="86.0" y="157.0">WRITE Z</text>
</g>
<g id="node-vm2-qemu">
<rect class="node-shape kind-service emphasis-normal role-source" data-evidence="7031-7031,7046-7048" x="280.0" y="68.5" width="150.0" height="88.0" rx="7" />
<text class="node-label" x="355.0" y="95.5">VM2 QEMU</text>
<line class="node-detail-divider" x1="294.0" y1="116.5" x2="416.0" y2="116.5" />
<text class="node-detail" x="296.0" y="133.5">READ A</text>
<text class="node-detail" x="296.0" y="149.5">WRITE B</text>
</g>
<g id="node-nginx">
<rect class="node-shape kind-service emphasis-normal role-source" data-evidence="7033-7033" x="490.0" y="80.5" width="150.0" height="64.0" rx="7" />
<text class="node-label" x="565.0" y="110.5">Nginx</text>
</g>
<g id="node-host-other">
<rect class="node-shape kind-service emphasis-normal role-source" data-evidence="7035-7038" x="700.0" y="80.5" width="150.0" height="64.0" rx="7" />
<text class="node-label" x="775.0" y="110.5">Host 기타</text>
</g>
<g id="node-host-block-layer">
<rect class="node-shape kind-service emphasis-primary role-queue" data-evidence="7031-7031,7017-7022,7054-7054,7058-7060" x="380.0" y="261.0" width="160.0" height="88.0" rx="7" />
<text class="node-label" x="460.0" y="288.0">Host Block Layer</text>
<line class="node-detail-divider" x1="394.0" y1="309.0" x2="526.0" y2="309.0" />
<text class="node-detail" x="396.0" y="326.0">Host block request</text>
<text class="node-detail" x="396.0" y="342.0">blk-mq</text>
</g>
<g id="node-nvme">
<rect class="node-shape kind-device emphasis-normal role-sink" data-evidence="7031-7031,7019-7019,7066-7069" x="385.0" y="458.0" width="150.0" height="64.0" /><ellipse class="node-shape kind-device emphasis-normal role-sink" cx="460.0" cy="458.0" rx="75.0" ry="13.0" /><path class="storage-bottom" d="M 385.0 522.0 A 75.0 13.0 0 0 0 535.0 522.0" />
<text class="node-label" x="460.0" y="488.0">NVMe</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.0 KiB