{ "version": "1.1", "id": "vms-sharing-one-nvme", "title": "가상 머신 둘과 호스트 프로세스가 하나의 NVMe 로 모인다", "question": "서로 다른 곳에서 시작한 I/O 는 어디에서 하나로 모여 device 로 나가는가", "type": "flow", "direction": "TB", "audience": [ "가상 머신 위에서 본 저장소 지연의 원인을 가르려는 사람", "Storage Contention 과 CPU Contention 을 나눠 재려는 사람" ], "summary": "VM1 QEMU 와 VM2 QEMU, Nginx, 호스트의 다른 프로세스에서 시작한 I/O 가 모두 하나의 Host Block Layer queue 로 들어가 NVMe 로 dispatch 된다.", "alt": "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 과는 다투는 자원이 다르다는 것은 본문 문단이 맡는다.", "source_context": { "document": "docs/virtualization/final/document.md", "document_sha256": "181e2b3cc8a45bae81e7e8193d026937c4d586ae4495d3b2c323eb7e6abcfadd", "anchor": { "kind": "heading", "value": "159. 여러 VM이 하나의 NVMe를 공유하면", "line": 7026 } }, "composition": { "profile": "component-flow", "diagram_only": true, "reference_ids": [ "payment-event-flow" ], "rationale": "이 절이 주장하는 것은 서로 다른 네 곳에서 시작한 요청이 한 자리로 모인다는 것이다. query-fanout 은 하나가 여러 저장소로 갈라지는 문법이라 방향이 반대이고, orchestrator-workers 는 가운데가 아래의 워커들에게 일을 나눠 주는 그림이라 「모인다」가 「나눈다」로 뒤집힌다. comparison 은 네 출발점을 나란히 늘어놓기만 해서 하나의 queue 로 모인다는 사실이 사라진다.", "focus_node": "host-block-layer" }, "groups": [], "nodes": [ { "id": "vm1-qemu", "label": "VM1 QEMU", "kind": "service", "role": "source", "shape": "box", "details": [ "WRITE X", "READ Y", "WRITE Z" ], "description": "가상 머신 하나의 QEMU 프로세스. 게스트의 I/O 가 여기서 호스트 파일 I/O 가 된다.", "evidence": [ { "start_line": 7029, "end_line": 7029 }, { "start_line": 7041, "end_line": 7044 } ], "assumption": false }, { "id": "vm2-qemu", "label": "VM2 QEMU", "kind": "service", "role": "source", "shape": "box", "details": [ "READ A", "WRITE B" ], "description": "다른 가상 머신의 QEMU 프로세스.", "evidence": [ { "start_line": 7031, "end_line": 7031 }, { "start_line": 7046, "end_line": 7048 } ], "assumption": false }, { "id": "nginx", "label": "Nginx", "kind": "service", "role": "source", "shape": "box", "description": "호스트에서 직접 도는 프로세스.", "evidence": [ { "start_line": 7033, "end_line": 7033 } ], "assumption": false }, { "id": "host-other", "label": "Host 기타", "kind": "service", "role": "source", "shape": "box", "description": "그 밖의 호스트 프로세스.", "evidence": [ { "start_line": 7035, "end_line": 7038 } ], "assumption": false }, { "id": "host-block-layer", "label": "Host Block Layer", "kind": "service", "role": "queue", "shape": "box", "emphasis": "primary", "details": [ "Host block request", "blk-mq" ], "description": "어디서 왔는지 구분하지 않고 모두 Host block request 로 다루는 계층. queue 에서 관리해 device 로 내보낸다.", "evidence": [ { "start_line": 7031, "end_line": 7031 }, { "start_line": 7017, "end_line": 7022 }, { "start_line": 7054, "end_line": 7054 }, { "start_line": 7058, "end_line": 7060 } ], "assumption": false }, { "id": "nvme", "label": "NVMe", "kind": "device", "role": "sink", "shape": "cylinder", "description": "네 갈래가 결국 함께 쓰는 하나의 물리 device.", "evidence": [ { "start_line": 7031, "end_line": 7031 }, { "start_line": 7019, "end_line": 7019 }, { "start_line": 7066, "end_line": 7069 } ], "assumption": false } ], "edges": [ { "id": "vm1-to-block-layer", "from": "vm1-qemu", "to": "host-block-layer", "label": "", "kind": "data", "style": "solid", "order": 1, "evidence": [ { "start_line": 7029, "end_line": 7031 } ], "assumption": false }, { "id": "vm2-to-block-layer", "from": "vm2-qemu", "to": "host-block-layer", "label": "", "kind": "data", "style": "solid", "order": 2, "evidence": [ { "start_line": 7031, "end_line": 7031 } ], "assumption": false }, { "id": "nginx-to-block-layer", "from": "nginx", "to": "host-block-layer", "label": "", "kind": "data", "style": "solid", "order": 3, "evidence": [ { "start_line": 7031, "end_line": 7033 } ], "assumption": false }, { "id": "host-other-to-block-layer", "from": "host-other", "to": "host-block-layer", "label": "", "kind": "data", "style": "solid", "order": 4, "evidence": [ { "start_line": 7031, "end_line": 7035 } ], "assumption": false }, { "id": "block-layer-to-nvme", "from": "host-block-layer", "to": "nvme", "label": "dispatch", "kind": "data", "style": "solid", "order": 5, "evidence": [ { "start_line": 7031, "end_line": 7031 }, { "start_line": 7054, "end_line": 7054 } ], "assumption": false } ], "legend": [], "metadata": { "rationale": "component-flow 를 고른 이유는 이 절이 답하는 물음이 「여러 곳에서 온 요청이 어디에서 하나가 되는가」이기 때문이고, 그 수렴은 자리로만 보인다. 네 출발점의 화살표에는 라벨을 달지 않았다 — 넷이 같은 「Host block request」라는 것이 이 그림의 논지인데 같은 라벨을 네 번 겹쳐 놓으면 라벨끼리 먹힌다. 그래서 그 이름은 모이는 노드의 details 로 올렸다. blk-mq 도 노드가 아니라 details 다 — §160 이 그리는 것은 CPU 넷이 자기 queue 를 갖는 다른 수렴이라 이 그림의 네 출발점과 같은 축에 놓을 수 없다. I/O Scheduler 와 NVMe Driver 는 §161 과 §164 가 대는 사실인데 이 앵커의 준비된 문맥(§158~§160) 밖이라 넣지 않았다. §159 의 「Host Process READ C」도 붙이지 않았다 — 그 프로세스가 Nginx 인지 Host 기타인지 SSOT 가 말하지 않는다." } }