refactor: 문서 개선 중

This commit is contained in:
donghyeon-ka
2026-09-21 14:30:55 +09:00
parent c93cdea150
commit 805a18f486
1497 changed files with 525837 additions and 59152 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -14,11 +14,11 @@
"long_description": "위에서 아래로 읽는다. 맨 위 Guest Userspace 구역에 PostgreSQL / Keycloak 이 있고 write() 가 게스트 커널로 내려간다. Guest Kernel 구역 안에서 VFS 가 먼저 받고, ext4 / XFS 가 파일을 block 공간에 배치하고, Guest Page Cache 가 그 내용을 dirty page 로 받아 두었다가 writeback 으로 Guest Block Layer 에 내려보낸다. Guest Block Layer 는 그것을 WRITE 나 FLUSH 요청으로 바꿔 /dev/vda 에 걸고, virtio-blk Frontend 가 그 요청을 virtqueue 에 게시한다. virtqueue 아래의 VM Boundary 부터는 QEMU 와 호스트 계층이라 이 그림에서 잘랐다. 완료가 같은 virtqueue 로 되돌아오는 역방향은 SSOT 의 다른 절이 대는 사실이라 이 그림에 넣지 않았고 본문 문단이 맡는다.",
"source_context": {
"document": "docs/virtualization/final/document.md",
"document_sha256": "181e2b3cc8a45bae81e7e8193d026937c4d586ae4495d3b2c323eb7e6abcfadd",
"document_sha256": "8c4ecc64c8cea9a4450ed7131fdd9cb2048dc092b66cd969f6346ed77887c210",
"anchor": {
"kind": "heading",
"value": "128. 전체 구조",
"line": 5804
"line": 5817
}
},
"composition": {
@@ -38,8 +38,8 @@
"description": "게스트 사용자 공간. 저장장치를 직접 다루지 않는다.",
"evidence": [
{
"start_line": 5807,
"end_line": 5809
"start_line": 5820,
"end_line": 5822
}
],
"assumption": false
@@ -51,8 +51,8 @@
"description": "게스트 커널 공간. VM Boundary 위쪽 계층이 전부 여기 있다.",
"evidence": [
{
"start_line": 5815,
"end_line": 5833
"start_line": 5828,
"end_line": 5846
}
],
"assumption": false
@@ -69,16 +69,16 @@
"description": "파일 연산을 요청하는 게스트 프로세스.",
"evidence": [
{
"start_line": 5807,
"end_line": 5809
"start_line": 5820,
"end_line": 5822
},
{
"start_line": 5885,
"end_line": 5885
"start_line": 5898,
"end_line": 5898
},
{
"start_line": 5811,
"end_line": 5813
"start_line": 5824,
"end_line": 5826
}
],
"assumption": false,
@@ -97,12 +97,12 @@
"description": "열린 파일이 어느 filesystem 구현에 속하는지 보고 연산을 넘기는 공통 계층.",
"evidence": [
{
"start_line": 5815,
"end_line": 5817
"start_line": 5828,
"end_line": 5830
},
{
"start_line": 5905,
"end_line": 5905
"start_line": 5918,
"end_line": 5918
}
],
"assumption": false
@@ -117,8 +117,8 @@
"description": "파일과 디렉터리를 block 공간에 배치하는 게스트 filesystem.",
"evidence": [
{
"start_line": 5819,
"end_line": 5819
"start_line": 5832,
"end_line": 5832
}
],
"assumption": false
@@ -133,8 +133,8 @@
"description": "게스트 RAM 에 있는 cache. write() 가 성공해도 데이터는 아직 여기까지만 확정된다.",
"evidence": [
{
"start_line": 5821,
"end_line": 5821
"start_line": 5834,
"end_line": 5834
}
],
"assumption": false
@@ -149,8 +149,8 @@
"description": "파일 세계의 요청을 block device 세계의 요청으로 바꾸는 계층.",
"evidence": [
{
"start_line": 5825,
"end_line": 5825
"start_line": 5838,
"end_line": 5838
}
],
"assumption": false
@@ -165,12 +165,12 @@
"description": "게스트 Linux 가 하나의 block device 로 인식하는 이름.",
"evidence": [
{
"start_line": 5829,
"end_line": 5829
"start_line": 5842,
"end_line": 5842
},
{
"start_line": 5879,
"end_line": 5879
"start_line": 5892,
"end_line": 5892
}
],
"assumption": false
@@ -185,12 +185,12 @@
"description": "그 가상 block device 를 제어하는 게스트 커널 driver.",
"evidence": [
{
"start_line": 5831,
"end_line": 5831
"start_line": 5844,
"end_line": 5844
},
{
"start_line": 5789,
"end_line": 5789
"start_line": 5802,
"end_line": 5802
}
],
"assumption": false
@@ -206,8 +206,8 @@
"description": "요청이 실리는 자리. 바로 아래가 VM Boundary 다.",
"evidence": [
{
"start_line": 5833,
"end_line": 5835
"start_line": 5846,
"end_line": 5848
}
],
"assumption": false
@@ -224,12 +224,12 @@
"order": 1,
"evidence": [
{
"start_line": 5811,
"end_line": 5813
"start_line": 5824,
"end_line": 5826
},
{
"start_line": 5890,
"end_line": 5890
"start_line": 5903,
"end_line": 5903
}
],
"assumption": false
@@ -244,8 +244,8 @@
"order": 2,
"evidence": [
{
"start_line": 5817,
"end_line": 5819
"start_line": 5830,
"end_line": 5832
}
],
"assumption": false
@@ -260,8 +260,8 @@
"order": 3,
"evidence": [
{
"start_line": 5819,
"end_line": 5821
"start_line": 5832,
"end_line": 5834
}
],
"assumption": false
@@ -276,8 +276,8 @@
"order": 4,
"evidence": [
{
"start_line": 5823,
"end_line": 5825
"start_line": 5836,
"end_line": 5838
}
],
"assumption": false
@@ -292,8 +292,8 @@
"order": 5,
"evidence": [
{
"start_line": 5827,
"end_line": 5829
"start_line": 5840,
"end_line": 5842
}
],
"assumption": false
@@ -308,8 +308,8 @@
"order": 6,
"evidence": [
{
"start_line": 5829,
"end_line": 5831
"start_line": 5842,
"end_line": 5844
}
],
"assumption": false
@@ -324,8 +324,8 @@
"order": 7,
"evidence": [
{
"start_line": 5831,
"end_line": 5833
"start_line": 5844,
"end_line": 5846
}
],
"assumption": false
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -4,7 +4,7 @@
"title": "Guest 의 메모리 접근 한 번은 이름이 두 번 바뀌며 네 계층을 지난다",
"question": "Guest 프로세스가 읽은 주소는 어느 계층을 차례로 지나 어떤 물리 메모리에 닿는가",
"type": "data-flow",
"direction": "TB",
"direction": "LR",
"audience": [
"가상 머신에 준 RAM 이 Host RAM 에서 무엇을 뜻하는지 가르려는 사람",
"KVM/QEMU 메모리 경로를 처음 읽는 사람"
@@ -14,11 +14,11 @@
"long_description": "위에서 아래로 읽는다. 맨 위 Guest 구역 안에 Keycloak · PostgreSQL 이 있고 그 접근은 GVA 로 Guest TLB 에 들어간다. TLB 에 변환이 없으면 TLB Miss 로 Guest Page Table 을 조회하고 거기서 GVA 가 GPA 가 된다. 그 GPA 는 VM Boundary 를 넘어 Guest 구역 밖의 EPT 로 가고 EPT 가 GPA 를 HPA 로 바꾼다. HPA 는 Host RAM 구역의 Host Physical Memory 에 닿고, 그 physical page 는 맨 아래에서 NUMA Node 0 또는 NUMA Node 1 가운데 한쪽에 놓인다. 상자 안 작은 글씨는 그 단계가 어느 계층인지와 무엇을 무엇으로 바꾸는지를 적은 것이다. 어느 단계에서 접근이 완료되지 못할 때 무엇이 일어나는지와 QEMU 가 이 경로를 어떻게 준비하는지는 본문의 문단과 표가 맡는다.",
"source_context": {
"document": "docs/virtualization/final/document.md",
"document_sha256": "181e2b3cc8a45bae81e7e8193d026937c4d586ae4495d3b2c323eb7e6abcfadd",
"document_sha256": "8c4ecc64c8cea9a4450ed7131fdd9cb2048dc092b66cd969f6346ed77887c210",
"anchor": {
"kind": "heading",
"value": "87. 최종 기준 그림",
"line": 4029
"line": 4042
}
},
"composition": {
@@ -38,8 +38,8 @@
"description": "VM Boundary 안쪽. Guest Userspace 와 Guest Kernel 이 여기 있고 GVA 가 GPA 가 되는 첫 번째 변환이 여기서 끝난다.",
"evidence": [
{
"start_line": 4034,
"end_line": 4053
"start_line": 4047,
"end_line": 4066
}
],
"assumption": false
@@ -51,8 +51,8 @@
"description": "변환이 끝난 주소가 닿는 실제 서버 메모리.",
"evidence": [
{
"start_line": 4069,
"end_line": 4079
"start_line": 4082,
"end_line": 4092
}
],
"assumption": false
@@ -69,8 +69,8 @@
"description": "Guest 안에서 메모리를 읽는 프로세스. 여기서 GVA 가 출발한다.",
"evidence": [
{
"start_line": 4034,
"end_line": 4038
"start_line": 4047,
"end_line": 4051
}
],
"assumption": false,
@@ -88,8 +88,8 @@
"description": "최근 변환 결과를 담아 두는 CPU 안의 캐시.",
"evidence": [
{
"start_line": 4043,
"end_line": 4045
"start_line": 4056,
"end_line": 4058
}
],
"assumption": false,
@@ -107,8 +107,8 @@
"description": "GVA 를 GPA 로 바꾸는 첫 번째 변환 표.",
"evidence": [
{
"start_line": 4048,
"end_line": 4053
"start_line": 4061,
"end_line": 4066
}
],
"assumption": false,
@@ -127,8 +127,8 @@
"description": "VM Boundary 바깥에서 GPA 를 HPA 로 바꾸는 두 번째 변환 표. KVM 과 CPU 가 맡는다.",
"evidence": [
{
"start_line": 4055,
"end_line": 4067
"start_line": 4068,
"end_line": 4080
}
],
"assumption": false,
@@ -147,8 +147,8 @@
"description": "HPA 가 가리키는 실제 서버 메모리.",
"evidence": [
{
"start_line": 4069,
"end_line": 4072
"start_line": 4082,
"end_line": 4085
}
],
"assumption": false
@@ -163,8 +163,8 @@
"description": "physical page 가 놓일 수 있는 한쪽 NUMA node.",
"evidence": [
{
"start_line": 4073,
"end_line": 4075
"start_line": 4086,
"end_line": 4088
}
],
"assumption": false
@@ -179,8 +179,8 @@
"description": "physical page 가 놓일 수 있는 다른 쪽 NUMA node.",
"evidence": [
{
"start_line": 4073,
"end_line": 4075
"start_line": 4086,
"end_line": 4088
}
],
"assumption": false
@@ -196,8 +196,8 @@
"style": "solid",
"evidence": [
{
"start_line": 4036,
"end_line": 4043
"start_line": 4049,
"end_line": 4056
}
],
"assumption": false
@@ -211,8 +211,8 @@
"style": "solid",
"evidence": [
{
"start_line": 4043,
"end_line": 4048
"start_line": 4056,
"end_line": 4061
}
],
"assumption": false
@@ -226,8 +226,8 @@
"style": "solid",
"evidence": [
{
"start_line": 4053,
"end_line": 4062
"start_line": 4066,
"end_line": 4075
}
],
"assumption": false
@@ -241,8 +241,8 @@
"style": "solid",
"evidence": [
{
"start_line": 4062,
"end_line": 4071
"start_line": 4075,
"end_line": 4084
}
],
"assumption": false
@@ -256,8 +256,8 @@
"style": "solid",
"evidence": [
{
"start_line": 4071,
"end_line": 4075
"start_line": 4084,
"end_line": 4088
}
],
"assumption": false
@@ -271,8 +271,8 @@
"style": "solid",
"evidence": [
{
"start_line": 4071,
"end_line": 4075
"start_line": 4084,
"end_line": 4088
}
],
"assumption": false
@@ -280,6 +280,6 @@
],
"legend": [],
"metadata": {
"rationale": "data-flow 로 고른 이유는 이 절이 답하는 물음이 「주소 하나가 어디를 지나 어디에 닿는가」이기 때문이다. LR 로 세우면 캔버스가 1889px 에 종횡비 5.37 이라 문서 폭에서 글자가 읽히지 않아 TB 로 세웠다. Guest Userspace · Guest Kernel · KVM / CPU · Host RAM 넷을 모두 group 으로 두면 group 이름이 그 경계를 넘는 엣지 라벨에 먹혀 「Guest Kerne」·「KVM / CP」로 잘렸다. 그래서 경계는 VM Boundary 를 사이에 둔 Guest 와 Host RAM 둘만 상자로 두고, 나머지 두 계층 이름은 그 자리 노드의 details 로 내렸다. SSOT 가 같은 절에 그린 관리 경로(virsh · libvirt · QEMU · KVM)와 자원 압박 경로는 다른 물음이라 이 그림에 지 않았다. §87 이 EPT 옆에 적어 둔 「EPT Violation 가능」과 Guest Page Table 옆의 「Guest #PF 가능」도 뺐다 — 이 기록의 본문은 정상 경로를 세우고, 세 계층의 fault 는 짝이 되는 기록이 맡는다. 마지막 Physical RAM 상자는 Host Physical Memory 와 뜻이 겹쳐 빼고 NUMA node 둘을 끝으로 두었다."
"rationale": "data-flow 로 고른 이유는 이 절이 답하는 물음이 「주소 하나가 어디를 지나 어디에 닿는가」이기 때문이다. 현재 renderer에서는 LR이 Guest → EPT → Host RAM 변환 사슬과 마지막 NUMA 분기를 겹침 없이 읽게 한다. Guest Userspace · Guest Kernel · KVM / CPU · Host RAM 넷을 모두 group 으로 두면 경계 라벨이 엣지와 충돌하므로, VM Boundary 를 사이에 둔 Guest 와 Host RAM 둘만 상자로 두고 나머지 계층은 node details 로 남겼다. SSOT 의 관리 경로와 fault 경로는 다른 물음이라 이 그림에 지 않다."
}
}
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,237 @@
{
"version": "1.1",
"id": "memory-pressure-reclaim-swap-oom",
"title": "Host memory pressure가 storage와 OOM으로 갈라지는 경로",
"question": "Host memory pressure가 생겼을 때 reclaim 이후 어떤 경로가 swap I/O와 storage contention으로 이어지고, 확보 실패는 어디서 OOM으로 이어지는가",
"type": "data-flow",
"direction": "LR",
"audience": [
"VM host 메모리 압박을 추적하는 개발자",
"swap과 OOM의 발생 계층을 구분하려는 운영자"
],
"summary": "Host memory pressure는 reclaim으로 시작한다. clean file-backed page는 버렸다가 필요할 때 storage에서 다시 읽을 수 있고 anonymous page는 Host swap I/O로 이어질 수 있다. reclaim으로도 메모리를 확보하지 못하면 Host OOM 경로가 열린다.",
"alt": "Host memory pressure가 reclaim에서 clean page 재읽기, anonymous page의 Host swap과 storage contention, 메모리 확보 실패에 따른 Host OOM으로 갈라지는 흐름도. QEMU가 OOM victim이 될 때 VM 전체가 중단될 수 있다는 결과는 본문에서 설명한다.",
"long_description": "왼쪽에서 Host memory pressure가 reclaim으로 들어간다. reclaim 이후 clean file-backed page는 버렸다가 필요할 때 storage에서 다시 읽는 경로, anonymous page는 Host swap I/O를 통해 physical storage와 경쟁하는 경로, 필요한 메모리를 확보하지 못하면 Host OOM으로 가는 경로로 갈린다. storage contention이 application latency로 이어지는 결과와 Host OOM이 QEMU를 victim으로 고를 때 VM 전체가 중단될 수 있다는 결과는 본문에서 이어 설명한다.",
"source_context": {
"document": "docs/virtualization/final/document.md",
"document_sha256": "8c4ecc64c8cea9a4450ed7131fdd9cb2048dc092b66cd969f6346ed77887c210",
"anchor": {
"kind": "heading",
"value": "제2부 — 메모리 가상화",
"line": 1670
}
},
"composition": {
"profile": "component-flow",
"diagram_only": true,
"reference_ids": [
"payment-event-flow"
],
"rationale": "압박 하나가 reclaim 뒤 여러 자원 경로로 갈라지고 다시 storage contention과 OOM 결과로 이어지는 흐름이 핵심이다.",
"focus_node": "reclaim"
},
"groups": [],
"nodes": [
{
"id": "pressure",
"label": "Host memory pressure",
"kind": "resource",
"role": "source",
"description": "실제 Host RAM 수요가 available physical memory에 접근한 상태.",
"evidence": [
{
"start_line": 2818,
"end_line": 2831
}
],
"assumption": false
},
{
"id": "reclaim",
"label": "Reclaim",
"kind": "component",
"role": "service",
"emphasis": "primary",
"description": "Linux가 회수 가능한 page/cache를 처리하는 첫 대응.",
"evidence": [
{
"start_line": 2818,
"end_line": 2831
}
],
"assumption": false
},
{
"id": "clean-page",
"label": "clean file-backed page",
"kind": "data",
"role": "service",
"details": [
"discard · reread"
],
"description": "원본이 storage에 있어 RAM에서 제거하고 나중에 다시 읽을 수 있다.",
"evidence": [
{
"start_line": 2834,
"end_line": 2848
}
],
"assumption": false
},
{
"id": "host-swap",
"label": "Host swap I/O",
"kind": "storage",
"role": "store",
"details": [
"anonymous page"
],
"description": "anonymous page의 backing이 필요할 때 host swap I/O가 생길 수 있다.",
"evidence": [
{
"start_line": 2850,
"end_line": 2852
},
{
"start_line": 2856,
"end_line": 2886
}
],
"assumption": false
},
{
"id": "storage",
"label": "Storage contention",
"kind": "storage",
"role": "store",
"shape": "database",
"details": [
"Host swap",
"DB I/O",
"writeback"
],
"description": "여러 I/O가 한 physical device에 몰려 contention을 만든다.",
"evidence": [
{
"start_line": 2938,
"end_line": 2965
}
],
"assumption": false
},
{
"id": "host-oom",
"label": "Host OOM",
"kind": "result",
"role": "service",
"description": "reclaim 등으로도 필요한 memory 확보에 실패했을 때 Host kernel에서 발생할 수 있다.",
"evidence": [
{
"start_line": 3213,
"end_line": 3231
},
{
"start_line": 3249,
"end_line": 3265
}
],
"assumption": false
}
],
"edges": [
{
"id": "pressure-reclaim",
"from": "pressure",
"to": "reclaim",
"label": "pressure",
"kind": "control",
"evidence": [
{
"start_line": 2818,
"end_line": 2831
}
],
"assumption": false
},
{
"id": "reclaim-clean",
"from": "reclaim",
"to": "clean-page",
"label": "file-backed",
"kind": "control",
"evidence": [
{
"start_line": 2834,
"end_line": 2848
}
],
"assumption": false
},
{
"id": "clean-storage",
"from": "clean-page",
"to": "storage",
"label": "reread",
"kind": "data",
"style": "dashed",
"evidence": [
{
"start_line": 2834,
"end_line": 2848
}
],
"assumption": false
},
{
"id": "reclaim-swap",
"from": "reclaim",
"to": "host-swap",
"label": "anonymous",
"kind": "data",
"evidence": [
{
"start_line": 2850,
"end_line": 2852
}
],
"assumption": false
},
{
"id": "swap-storage",
"from": "host-swap",
"to": "storage",
"label": "swap",
"kind": "data",
"evidence": [
{
"start_line": 2871,
"end_line": 2886
},
{
"start_line": 2940,
"end_line": 2946
}
],
"assumption": false
},
{
"id": "reclaim-oom",
"from": "reclaim",
"to": "host-oom",
"label": "확보 실패",
"kind": "control",
"style": "dashed",
"evidence": [
{
"start_line": 3213,
"end_line": 3231
}
],
"assumption": false
}
],
"legend": [],
"metadata": {
"rationale": "분기와 재합류를 모두 넣어 자동 배치가 교차하던 이전 안을 줄였다. 그림은 Host pressure가 reclaim 뒤 clean-page·Host swap·Host OOM으로 갈리는 구조까지만 보이고, application latency와 QEMU victim 결과는 본문에 남긴다."
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -14,7 +14,7 @@
"long_description": "왼쪽에서 오른쪽으로 읽는다. 왼쪽 끝이 밖에서 친 curl 이 만든 inbound packet 이고 forward 훅으로 들어간다. 첫 점선 상자가 DNAT 파일에 둔 base 체인이다. priority filter - 10 이라 먼저 돌고, 그 안의 ct state new accept 가 이 packet 을 통과시킨다. 다음 점선 상자가 libvirt 가 만든 ip libvirt_network 테이블의 guest_input 체인이다. 앞 체인의 accept 는 평가를 끝내지 않으므로 같은 packet 이 이 체인으로 이어진다. 그 안의 상자 둘은 같은 체인의 맨 앞과 맨 끝이다. 구멍을 넣기 전에는 맨 앞이 비어 있어 packet 이 ct state established,related accept 에 걸리지 못한 채 체인 끝 reject 에 닿았고 connection refused 로 끝났다. 그 reject 규칙의 카운터가 4 패킷 240 바이트다. insert 로 구멍을 넣으면 그 규칙이 맨 앞에 서서 같은 packet 을 먼저 받아 192.168.122.10 의 엣지 nginx 로 보낸다. 실선이 구멍을 넣은 뒤의 경로이고 점선이 넣기 전의 경로다. 이 그림은 그 규칙이 libvirt 네트워크를 다시 세우면 사라진다는 것은 말하지 않는다.",
"source_context": {
"document": "docs/virtualization/final/document.md",
"document_sha256": "60d902c7aed218ba637b48603a3bd0e6b193fea59c9de97ffdb8e05d5087aedd",
"document_sha256": "8c4ecc64c8cea9a4450ed7131fdd9cb2048dc092b66cd969f6346ed77887c210",
"anchor": {
"kind": "heading",
"value": "180. nftables 는 앞 체인의 `accept` 로 뒤 체인의 `reject` 를 막지 못한다",
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -14,11 +14,11 @@
"long_description": "참여자는 왼쪽부터 QEMU vCPU Thread, VM1 Memory Backing, Node 0 CPU, Node 0 RAM, Node 1 RAM 다섯이다. 메시지는 위에서 아래로 읽는다. 1번에서 vCPU thread 가 Node 0 CPU 에 pinning 되고, 2번에서 VM1 의 memory backing 이 Node 1 에 놓인다. 그 배치에서 3번의 memory 접근은 Node 1 RAM 으로 가고 이것이 remote access 다. Node 0 과 Node 1 사이에는 interconnect 가 있어 이 접근만 그 구간을 건넌다. 4번과 5번은 그 뒤에 일어나는 다른 사건이 아니라 배치를 고친 구성이다. memory placement 와 binding 까지 Node 0 으로 맞추면 같은 접근이 Node 0 RAM 으로 가고 local access 가 된다. 접근 비용이 실제로 얼마나 차이 나는지, 이 Host 가 몇 개의 node 를 가졌는지는 그림이 말하지 않는다.",
"source_context": {
"document": "docs/virtualization/final/document.md",
"document_sha256": "181e2b3cc8a45bae81e7e8193d026937c4d586ae4495d3b2c323eb7e6abcfadd",
"document_sha256": "8c4ecc64c8cea9a4450ed7131fdd9cb2048dc092b66cd969f6346ed77887c210",
"anchor": {
"kind": "heading",
"value": "75. vCPU와 NUMA의 연결",
"line": 3313
"line": 3326
}
},
"composition": {
@@ -45,8 +45,8 @@
"description": "Guest vCPU 가 Host 에서 갖는 실체. Host scheduler 가 이것을 logical CPU 에 올린다.",
"evidence": [
{
"start_line": 3315,
"end_line": 3324
"start_line": 3328,
"end_line": 3337
}
],
"assumption": false
@@ -63,16 +63,16 @@
"description": "그 가상 머신의 Host physical backing page 가 놓이는 자리.",
"evidence": [
{
"start_line": 3327,
"end_line": 3327
"start_line": 3340,
"end_line": 3340
},
{
"start_line": 3352,
"end_line": 3354
"start_line": 3365,
"end_line": 3367
},
{
"start_line": 3381,
"end_line": 3382
"start_line": 3394,
"end_line": 3395
}
],
"assumption": false
@@ -90,8 +90,8 @@
"description": "vCPU thread 가 실행되는 logical CPU. NUMA Node 0 에 속한다.",
"evidence": [
{
"start_line": 3322,
"end_line": 3330
"start_line": 3335,
"end_line": 3343
}
],
"assumption": false
@@ -108,12 +108,12 @@
"description": "Node 0 CPU 와 같은 node 의 RAM.",
"evidence": [
{
"start_line": 3288,
"end_line": 3296
"start_line": 3301,
"end_line": 3309
},
{
"start_line": 3373,
"end_line": 3382
"start_line": 3386,
"end_line": 3395
}
],
"assumption": false
@@ -130,12 +130,12 @@
"description": "다른 node 의 RAM. Node 0 CPU 가 읽으면 interconnect 를 건넌다.",
"evidence": [
{
"start_line": 3299,
"end_line": 3306
"start_line": 3312,
"end_line": 3319
},
{
"start_line": 3334,
"end_line": 3334
"start_line": 3347,
"end_line": 3347
}
],
"assumption": false
@@ -152,12 +152,12 @@
"style": "solid",
"evidence": [
{
"start_line": 3322,
"end_line": 3324
"start_line": 3335,
"end_line": 3337
},
{
"start_line": 3347,
"end_line": 3350
"start_line": 3360,
"end_line": 3363
}
],
"assumption": false
@@ -172,12 +172,12 @@
"style": "solid",
"evidence": [
{
"start_line": 3327,
"end_line": 3327
"start_line": 3340,
"end_line": 3340
},
{
"start_line": 3352,
"end_line": 3354
"start_line": 3365,
"end_line": 3367
}
],
"assumption": false
@@ -192,12 +192,12 @@
"style": "dashed",
"evidence": [
{
"start_line": 3329,
"end_line": 3339
"start_line": 3342,
"end_line": 3352
},
{
"start_line": 3356,
"end_line": 3357
"start_line": 3369,
"end_line": 3370
}
],
"assumption": false
@@ -212,12 +212,12 @@
"style": "solid",
"evidence": [
{
"start_line": 3361,
"end_line": 3366
"start_line": 3374,
"end_line": 3379
},
{
"start_line": 3381,
"end_line": 3382
"start_line": 3394,
"end_line": 3395
}
],
"assumption": false
@@ -232,12 +232,12 @@
"style": "solid",
"evidence": [
{
"start_line": 3288,
"end_line": 3296
"start_line": 3301,
"end_line": 3309
},
{
"start_line": 3373,
"end_line": 3382
"start_line": 3386,
"end_line": 3395
}
],
"assumption": false
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -4,7 +4,7 @@
"title": "control/setup path 는 QEMU 를 지나고 data path 는 backend 에 따라 QEMU 를 지나기도 안 지나기도 한다",
"question": "설정하는 경로와 packet 이 흐르는 경로는 어디에서 갈라지고, 그 두 갈래 가운데 어느 쪽이 QEMU 사용자 공간을 지나는가",
"type": "network",
"direction": "TB",
"direction": "LR",
"audience": [
"가상 머신 안의 요청이 느릴 때 계층을 가르려는 사람",
"vhost-net 이 무엇을 우회하는지 확인하려는 사람"
@@ -14,11 +14,11 @@
"long_description": "왼쪽에서 오른쪽으로 읽는다. 위쪽 줄이 control/setup path 다. virsh 에서 libvirt 와 QEMU 를 지나 virtio-net Device Model 에 닿고, 그 device model 이 virtual NIC 생성과 feature negotiation, virtqueue 설정, vhost-net backend 설정을 맡는다. 아래쪽 줄이 data path 다. Physical NIC 에서 Physical NIC Driver 와 Linux Bridge, TAP 까지 온다. 두 줄은 네 번째 열 다음에서 만난다. device model 은 점선으로 backend 둘을 설정하고, TAP 은 실선으로 그 둘 가운데 실제로 쓰는 쪽에 packet 을 넘긴다. vhost-net 을 쓰면 packet 은 Host Kernel 안에서 처리돼 QEMU 사용자 공간을 지나지 않고, QEMU virtio backend 를 쓰면 같은 packet 이 QEMU 사용자 공간을 지난다. 어느 쪽이든 virtqueue 에서 만나 Guest 로 올라간다. 이 Host 가 둘 중 어느 backend 를 쓰고 있는지는 그림이 말하지 않는다.",
"source_context": {
"document": "docs/virtualization/final/document.md",
"document_sha256": "181e2b3cc8a45bae81e7e8193d026937c4d586ae4495d3b2c323eb7e6abcfadd",
"document_sha256": "8c4ecc64c8cea9a4450ed7131fdd9cb2048dc092b66cd969f6346ed77887c210",
"anchor": {
"kind": "heading",
"value": "125. 최종 기준 구조",
"line": 5682
"line": 5695
}
},
"composition": {
@@ -44,12 +44,12 @@
"description": "VM/network 관리 CLI. packet datapath 에는 직접 참여하지 않는다.",
"evidence": [
{
"start_line": 5662,
"end_line": 5662
"start_line": 5675,
"end_line": 5675
},
{
"start_line": 5687,
"end_line": 5689
"start_line": 5700,
"end_line": 5702
}
],
"assumption": false
@@ -63,12 +63,12 @@
"description": "VM lifecycle 과 NIC/network configuration 을 관리한다.",
"evidence": [
{
"start_line": 5663,
"end_line": 5663
"start_line": 5676,
"end_line": 5676
},
{
"start_line": 5691,
"end_line": 5691
"start_line": 5704,
"end_line": 5704
}
],
"assumption": false
@@ -85,12 +85,12 @@
"description": "실제로 실행되는 프로세스. 이 안에 virtio-net device model 이 있다.",
"evidence": [
{
"start_line": 5666,
"end_line": 5666
"start_line": 5679,
"end_line": 5679
},
{
"start_line": 5693,
"end_line": 5693
"start_line": 5706,
"end_line": 5706
}
],
"assumption": false
@@ -111,12 +111,12 @@
"description": "control/setup path 가 닿는 자리. 장치를 만들고 backend 를 연결한다.",
"evidence": [
{
"start_line": 5666,
"end_line": 5666
"start_line": 5679,
"end_line": 5679
},
{
"start_line": 5695,
"end_line": 5700
"start_line": 5708,
"end_line": 5713
}
],
"assumption": false
@@ -130,8 +130,8 @@
"description": "밖에서 온 packet 이 처음 닿는 하드웨어.",
"evidence": [
{
"start_line": 5706,
"end_line": 5708
"start_line": 5719,
"end_line": 5721
}
],
"assumption": false
@@ -145,12 +145,12 @@
"description": "그 하드웨어를 제어하는 Host kernel driver.",
"evidence": [
{
"start_line": 5674,
"end_line": 5674
"start_line": 5687,
"end_line": 5687
},
{
"start_line": 5710,
"end_line": 5710
"start_line": 5723,
"end_line": 5723
}
],
"assumption": false
@@ -164,12 +164,12 @@
"description": "L2 software switch 와 L3 routing, 주소 변환이 놓이는 자리.",
"evidence": [
{
"start_line": 5672,
"end_line": 5673
"start_line": 5685,
"end_line": 5686
},
{
"start_line": 5712,
"end_line": 5712
"start_line": 5725,
"end_line": 5725
}
],
"assumption": false
@@ -186,12 +186,12 @@
"description": "VM 의 Ethernet frame 과 Host Linux networking 을 잇는 접점.",
"evidence": [
{
"start_line": 5671,
"end_line": 5671
"start_line": 5684,
"end_line": 5684
},
{
"start_line": 5714,
"end_line": 5714
"start_line": 5727,
"end_line": 5727
}
],
"assumption": false
@@ -208,12 +208,12 @@
"description": "반복되는 packet datapath 를 Host kernel 에서 처리하는 backend.",
"evidence": [
{
"start_line": 5669,
"end_line": 5669
"start_line": 5682,
"end_line": 5682
},
{
"start_line": 5716,
"end_line": 5716
"start_line": 5729,
"end_line": 5729
}
],
"assumption": false
@@ -230,12 +230,12 @@
"description": "vhost-net 을 쓰지 않을 때 datapath backend 를 맡는 쪽.",
"evidence": [
{
"start_line": 5668,
"end_line": 5668
"start_line": 5681,
"end_line": 5681
},
{
"start_line": 5742,
"end_line": 5742
"start_line": 5755,
"end_line": 5755
}
],
"assumption": false
@@ -252,12 +252,12 @@
"description": "Guest 와 Host backend 가 I/O buffer 를 주고받는 descriptor 기반 공유 큐.",
"evidence": [
{
"start_line": 5667,
"end_line": 5667
"start_line": 5680,
"end_line": 5680
},
{
"start_line": 5718,
"end_line": 5718
"start_line": 5731,
"end_line": 5731
}
],
"assumption": false
@@ -277,12 +277,12 @@
"description": "packet 이 올라가 애플리케이션 socket 에 닿는 쪽.",
"evidence": [
{
"start_line": 5664,
"end_line": 5665
"start_line": 5677,
"end_line": 5678
},
{
"start_line": 5720,
"end_line": 5726
"start_line": 5733,
"end_line": 5739
}
],
"assumption": false
@@ -298,12 +298,12 @@
"style": "solid",
"evidence": [
{
"start_line": 5662,
"end_line": 5663
"start_line": 5675,
"end_line": 5676
},
{
"start_line": 5684,
"end_line": 5691
"start_line": 5697,
"end_line": 5704
}
],
"assumption": false
@@ -312,17 +312,17 @@
"id": "libvirt-to-qemu",
"from": "libvirt",
"to": "qemu",
"label": "NIC/network configuration",
"label": "NIC config",
"kind": "control",
"style": "solid",
"evidence": [
{
"start_line": 5663,
"end_line": 5663
"start_line": 5676,
"end_line": 5676
},
{
"start_line": 5691,
"end_line": 5693
"start_line": 5704,
"end_line": 5706
}
],
"assumption": false
@@ -336,12 +336,12 @@
"style": "solid",
"evidence": [
{
"start_line": 5666,
"end_line": 5666
"start_line": 5679,
"end_line": 5679
},
{
"start_line": 5693,
"end_line": 5695
"start_line": 5706,
"end_line": 5708
}
],
"assumption": false
@@ -355,12 +355,12 @@
"style": "solid",
"evidence": [
{
"start_line": 5671,
"end_line": 5671
"start_line": 5684,
"end_line": 5684
},
{
"start_line": 5708,
"end_line": 5710
"start_line": 5721,
"end_line": 5723
}
],
"assumption": false
@@ -374,12 +374,12 @@
"style": "solid",
"evidence": [
{
"start_line": 5672,
"end_line": 5672
"start_line": 5685,
"end_line": 5685
},
{
"start_line": 5710,
"end_line": 5712
"start_line": 5723,
"end_line": 5725
}
],
"assumption": false
@@ -393,12 +393,12 @@
"style": "solid",
"evidence": [
{
"start_line": 5671,
"end_line": 5671
"start_line": 5684,
"end_line": 5684
},
{
"start_line": 5712,
"end_line": 5714
"start_line": 5725,
"end_line": 5727
}
],
"assumption": false
@@ -412,12 +412,12 @@
"style": "dashed",
"evidence": [
{
"start_line": 5700,
"end_line": 5700
"start_line": 5713,
"end_line": 5713
},
{
"start_line": 5716,
"end_line": 5716
"start_line": 5729,
"end_line": 5729
}
],
"assumption": false
@@ -431,12 +431,12 @@
"style": "dashed",
"evidence": [
{
"start_line": 5668,
"end_line": 5668
"start_line": 5681,
"end_line": 5681
},
{
"start_line": 5700,
"end_line": 5700
"start_line": 5713,
"end_line": 5713
}
],
"assumption": false
@@ -450,12 +450,12 @@
"style": "solid",
"evidence": [
{
"start_line": 5703,
"end_line": 5703
"start_line": 5716,
"end_line": 5716
},
{
"start_line": 5714,
"end_line": 5716
"start_line": 5727,
"end_line": 5729
}
],
"assumption": false
@@ -469,12 +469,12 @@
"style": "solid",
"evidence": [
{
"start_line": 5729,
"end_line": 5729
"start_line": 5742,
"end_line": 5742
},
{
"start_line": 5740,
"end_line": 5742
"start_line": 5753,
"end_line": 5755
}
],
"assumption": false
@@ -488,12 +488,12 @@
"style": "solid",
"evidence": [
{
"start_line": 5703,
"end_line": 5703
"start_line": 5716,
"end_line": 5716
},
{
"start_line": 5716,
"end_line": 5718
"start_line": 5729,
"end_line": 5731
}
],
"assumption": false
@@ -507,12 +507,12 @@
"style": "solid",
"evidence": [
{
"start_line": 5729,
"end_line": 5729
"start_line": 5742,
"end_line": 5742
},
{
"start_line": 5742,
"end_line": 5744
"start_line": 5755,
"end_line": 5757
}
],
"assumption": false
@@ -521,17 +521,17 @@
"id": "virtqueue-to-guest",
"from": "virtqueue",
"to": "guest",
"label": "virtio-net Frontend Driver",
"label": "frontend",
"kind": "data",
"style": "solid",
"evidence": [
{
"start_line": 5664,
"end_line": 5664
"start_line": 5677,
"end_line": 5677
},
{
"start_line": 5718,
"end_line": 5726
"start_line": 5731,
"end_line": 5739
}
],
"assumption": false
@@ -539,6 +539,6 @@
],
"legend": [],
"metadata": {
"rationale": "network 으로 고른 이유는 이 절이 답하는 물음이 「어느 경로가 어느 계층을 지나는가」이기 때문이다. 점선은 설정이고 실선은 packet 이다 — 색이 아니라 선 모양으로 갈랐다. Internet/Client 와 Socket, Keycloak 은 노드로 세우지 않고 양 끝 노드의 details 로 내렸다. §125 가 적은 세 직선을 그대로 세 장으로 옮기면 SSOT 와 같은 그림이 되고, 이 글이 필요로 하는 것은 그 셋이 어디를 공유하는가다. LR 로 그렸더니 캔버스가 2249px 가 되어 TB 로 바꿨다. 갈래 구간의 엇갈리는 두 이음에 라벨을 다 붙였더니 라벨끼리 겹치고(497px²) 하나가 device model 상자에 먹혀(316px²) check-figure-overlap 이 걸렸다. 그 구간에서는 vhost-net 쪽 두 이음에만 이름을 남기고 QEMU backend 쪽 두 이음은 선 모양(점선=설정·실선=packet)과 도착 상자 이름으로 읽게 두었다."
"rationale": "이 절이 세운 것은 control/setup path와 두 data path가 device model·backend·virtqueue에서 갈라지고 다시 만나는 구조다. component-flow와 LR를 사용하면 이 관계를 상자 충돌 없이 보존한다. NIC config는 NIC/network configuration의 축약이고 frontend는 도착 노드의 virtio-net Frontend Driver를 반복하지 않기 위한 edge 축약이다. backend 선택이라는 핵심 의미와 QEMU userspace 우회 여부는 node/edge 구조로 그대로 남는다."
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -4,7 +4,7 @@
"title": "세 fault 는 변환 사슬의 서로 다른 단계에서 갈라져 나가 서로 다른 계층이 받는다",
"question": "Guest Page Fault · EPT Violation · Host Page Fault 는 각각 어느 단계에서 갈라지고 누가 받는가",
"type": "flow",
"direction": "TB",
"direction": "LR",
"audience": [
"VM 안의 메모리 지연을 계층별로 가르려는 사람",
"fault 지표를 어느 쪽부터 볼지 정하려는 사람"
@@ -14,11 +14,11 @@
"long_description": "위에서 아래로 읽는다. 맨 위 왼쪽에 Guest Page Table 이 있고 거기서 갈래가 둘이다. 하나는 Guest Page Fault 로 Guest Kernel 에 빠지고, 다른 하나는 GPA 를 들고 아래 EPT 로 내려간다. EPT 에서도 갈래가 나뉘어 EPT Violation 은 VM Exit 을 거쳐 KVM 으로 간다. 맨 위 오른쪽에는 QEMU 의 Guest RAM Backing 이 따로 있고 그쪽 갈래인 Host Page Fault 는 Host Kernel 로 간다. 받는 자리의 이름이 그대로 계층 이름이다 — Guest Kernel · KVM · Host Kernel. QEMU 갈래가 EPT 갈래와 선으로 이어져 있지 않은 것은 근거 문서가 둘을 잇는 문장을 두지 않았기 때문이다. 다섯 원인이 무엇인지와 두 사건을 항목별로 견주는 것은 본문의 표가 맡는다.",
"source_context": {
"document": "docs/virtualization/final/document.md",
"document_sha256": "181e2b3cc8a45bae81e7e8193d026937c4d586ae4495d3b2c323eb7e6abcfadd",
"document_sha256": "8c4ecc64c8cea9a4450ed7131fdd9cb2048dc092b66cd969f6346ed77887c210",
"anchor": {
"kind": "heading",
"value": "48. Guest Page Fault와 EPT Violation 비교",
"line": 2480
"line": 2493
}
},
"composition": {
@@ -44,12 +44,12 @@
"description": "첫 번째 translation 단계. 여기서 접근을 완료할 수 없으면 Guest Page Fault 가 난다.",
"evidence": [
{
"start_line": 2443,
"end_line": 2445
"start_line": 2456,
"end_line": 2458
},
{
"start_line": 2484,
"end_line": 2485
"start_line": 2497,
"end_line": 2498
}
],
"assumption": false
@@ -63,12 +63,12 @@
"description": "Guest Page Fault 를 받는 계층.",
"evidence": [
{
"start_line": 2487,
"end_line": 2487
"start_line": 2500,
"end_line": 2500
},
{
"start_line": 2493,
"end_line": 2494
"start_line": 2506,
"end_line": 2507
}
],
"assumption": false
@@ -86,12 +86,12 @@
"description": "두 번째 translation 단계. Guest translation 이 성공한 뒤 여기서 막히면 EPT Violation 이 난다.",
"evidence": [
{
"start_line": 2465,
"end_line": 2469
"start_line": 2478,
"end_line": 2482
},
{
"start_line": 2484,
"end_line": 2485
"start_line": 2497,
"end_line": 2498
}
],
"assumption": false
@@ -105,12 +105,12 @@
"description": "EPT Violation 을 VM Exit 뒤에 받는 계층.",
"evidence": [
{
"start_line": 2471,
"end_line": 2473
"start_line": 2484,
"end_line": 2486
},
{
"start_line": 2487,
"end_line": 2487
"start_line": 2500,
"end_line": 2500
}
],
"assumption": false
@@ -127,12 +127,12 @@
"description": "Host 의 일반 userspace 메모리로 관리되는 자리. demand allocation 이나 reclaim, swap 때문에 여기서 fault 가 난다.",
"evidence": [
{
"start_line": 2504,
"end_line": 2504
"start_line": 2517,
"end_line": 2517
},
{
"start_line": 2514,
"end_line": 2519
"start_line": 2527,
"end_line": 2532
}
],
"assumption": false
@@ -146,8 +146,8 @@
"description": "Host Page Fault 를 받아 필요한 Host page 를 처리하는 계층.",
"evidence": [
{
"start_line": 2521,
"end_line": 2525
"start_line": 2534,
"end_line": 2538
}
],
"assumption": false
@@ -158,17 +158,17 @@
"id": "guest-fault-branch",
"from": "guest-page-table",
"to": "guest-kernel",
"label": "Guest Page Fault",
"label": "Guest #PF",
"kind": "event",
"style": "solid",
"evidence": [
{
"start_line": 2484,
"end_line": 2487
"start_line": 2497,
"end_line": 2500
},
{
"start_line": 2493,
"end_line": 2494
"start_line": 2506,
"end_line": 2507
}
],
"assumption": false
@@ -182,8 +182,8 @@
"style": "solid",
"evidence": [
{
"start_line": 2461,
"end_line": 2467
"start_line": 2474,
"end_line": 2480
}
],
"assumption": false
@@ -197,12 +197,12 @@
"style": "solid",
"evidence": [
{
"start_line": 2467,
"end_line": 2473
"start_line": 2480,
"end_line": 2486
},
{
"start_line": 2487,
"end_line": 2487
"start_line": 2500,
"end_line": 2500
}
],
"assumption": false
@@ -211,13 +211,13 @@
"id": "host-fault-branch",
"from": "qemu-backing",
"to": "host-kernel",
"label": "Host Page Fault",
"label": "Host #PF",
"kind": "event",
"style": "solid",
"evidence": [
{
"start_line": 2517,
"end_line": 2525
"start_line": 2530,
"end_line": 2538
}
],
"assumption": false
@@ -225,6 +225,6 @@
],
"legend": [],
"metadata": {
"rationale": "flow 로 고른 이유는 이 절이 답하는 물음이 「어느 단계에서 갈라져 누구에게 가는가」이기 때문이다. 세 fault 를 나란히 늘어놓으면 그것은 본문에 이미 있는 비교표와 같은 것이 되므로, 갈래가 사슬의 어느 자리에서 나오는지를 자리로 보이게 세웠다. EPT 에서 나온 갈래와 QEMU backing 에서 나온 갈래를 선으로 잇지 않은 것은 근거 문서가 EPT 가 얻은 HPA 와 QEMU backing 의 Host physical address 를 같은 것이라고 적지 않았기 때문이다. 없는 문장을 선으로 만들지 않았다. 처음에는 Guest 와 Host 를 group 상자로 두었는데, 레이아웃이 노드를 group 별로 모으지 않아 Host 상자가 Guest 노드까지 감싸 버렸다. 자리의 이름(Guest Kernel · KVM · Host Kernel)이 이미 계층을 말하므로 group 을 빼고 이름에 맡겼다."
"rationale": "flow 로 고른 이유는 이 절이 답하는 물음이 「어느 단계에서 갈라져 누구에게 가는가」이기 때문이다. 현재 renderer에서는 LR가 변환 사슬과 세 fault branch를 겹침 없이 보인다. Guest #PF 와 Host #PF 는 본문에서 설명하는 Guest Page Fault·Host Page Fault의 축약 라벨이고 의미를 줄이지 않는다. EPT Violation은 VM Exit까지 포함해 그대로 둔다. 관계는 선으로 만들지 않는다."
}
}
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,217 @@
{
"version": "1.1",
"id": "qcow2-mapping-clusters",
"title": "qcow2 L1·L2 매핑과 backing fallback",
"question": "게스트 오프셋은 qcow2 파일의 데이터 클러스터로 어떻게 매핑되고, L2 항목이 0일 때 왜 backing file이나 zero-fill로 갈리는가",
"type": "data-flow",
"direction": "LR",
"audience": [
"qcow2 overlay 내부를 이해하려는 개발자",
"virtual size와 실제 disk size 차이를 이해하려는 운영자"
],
"summary": "게스트 오프셋의 상위 비트가 L1, 중간 비트가 L2, 하위 비트가 클러스터 내부 위치를 고른다. L2 항목에 값이 있으면 현재 qcow2의 데이터 클러스터를 읽고, 0이면 backing file 유무에 따라 같은 위치를 읽거나 0으로 채운 클러스터를 돌려준다.",
"alt": "게스트 오프셋이 L1과 L2 표를 지나 현재 qcow2 데이터 클러스터로 가거나, L2 항목이 0일 때 backing file 또는 zero-fill로 갈라지는 qcow2 매핑도.",
"long_description": "게스트가 읽으려는 위치는 L1 표와 L2 표를 통해 파일 안 데이터 클러스터 오프셋으로 변환된다. 매핑된 값이 있으면 현재 qcow2의 데이터 클러스터를 읽는다. 값이 0이고 backing file이 있으면 base.qcow2의 같은 가상 위치를 읽고, backing file이 없으면 0으로 채운 64KB를 반환한다. backing file 경로는 qcow2 헤더의 문자열이므로 오버레이만 옮기면 부팅할 수 없다는 제한도 그림 끝에 반영한다.",
"source_context": {
"document": "docs/virtualization/final/document.md",
"document_sha256": "8c4ecc64c8cea9a4450ed7131fdd9cb2048dc092b66cd969f6346ed77887c210",
"anchor": {
"kind": "heading",
"value": "231. qcow2 파일 내부는 어떻게 생겼나 — 매핑표가 전부다",
"line": 14221
}
},
"composition": {
"profile": "component-flow",
"diagram_only": true,
"reference_ids": [
"payment-event-flow"
],
"rationale": "매핑의 핵심은 한 위치가 L1/L2를 지나 세 결과 중 하나로 흐르는 관계다.",
"focus_node": "l2"
},
"groups": [],
"nodes": [
{
"id": "guest-offset",
"label": "Guest offset",
"kind": "data",
"role": "source",
"details": [
"virtual disk position"
],
"description": "게스트가 읽으려는 가상 디스크 위치.",
"evidence": [
{
"start_line": 14317,
"end_line": 14325
}
],
"assumption": false
},
{
"id": "l1",
"label": "L1 table",
"kind": "component",
"role": "service",
"details": [
"상위 비트"
],
"description": "필요한 L2 표가 있는 클러스터 위치를 찾는다.",
"evidence": [
{
"start_line": 14312,
"end_line": 14337
}
],
"assumption": false
},
{
"id": "l2",
"label": "L2 entry",
"kind": "component",
"role": "service",
"emphasis": "primary",
"details": [
"중간 13비트"
],
"description": "현재 qcow2 데이터 클러스터 오프셋을 가지거나 0일 수 있다.",
"evidence": [
{
"start_line": 14320,
"end_line": 14347
}
],
"assumption": false
},
{
"id": "local-cluster",
"label": "현재 qcow2 data cluster",
"kind": "store",
"role": "store",
"shape": "database",
"description": "L2 값이 있을 때 같은 qcow2 파일 안에서 읽는 데이터 클러스터.",
"evidence": [
{
"start_line": 14267,
"end_line": 14272
},
{
"start_line": 14343,
"end_line": 14345
}
],
"assumption": false
},
{
"id": "backing",
"label": "base.qcow2",
"kind": "store",
"role": "store",
"shape": "database",
"details": [
"backing file"
],
"description": "L2 항목이 0이고 backing file이 있을 때 같은 가상 위치를 읽는 바닥 파일.",
"evidence": [
{
"start_line": 14343,
"end_line": 14354
}
],
"assumption": false
},
{
"id": "zero-fill",
"label": "zero-filled 64KB",
"kind": "result",
"role": "sink",
"description": "L2 항목이 0이고 backing file도 없을 때 만들어 반환하는 0 데이터.",
"evidence": [
{
"start_line": 14343,
"end_line": 14347
}
],
"assumption": false
}
],
"edges": [
{
"id": "offset-l1",
"from": "guest-offset",
"to": "l1",
"label": "상위 비트",
"kind": "data",
"evidence": [
{
"start_line": 14317,
"end_line": 14324
}
],
"assumption": false
},
{
"id": "l1-l2",
"from": "l1",
"to": "l2",
"label": "L2 cluster",
"kind": "data",
"evidence": [
{
"start_line": 14320,
"end_line": 14337
}
],
"assumption": false
},
{
"id": "l2-local",
"from": "l2",
"to": "local-cluster",
"label": "mapped",
"kind": "data",
"evidence": [
{
"start_line": 14343,
"end_line": 14345
}
],
"assumption": false
},
{
"id": "l2-backing",
"from": "l2",
"to": "backing",
"label": "0 + backing",
"kind": "data",
"style": "dashed",
"evidence": [
{
"start_line": 14343,
"end_line": 14350
}
],
"assumption": false
},
{
"id": "l2-zero",
"from": "l2",
"to": "zero-fill",
"label": "0 + no backing",
"kind": "result",
"style": "dashed",
"evidence": [
{
"start_line": 14343,
"end_line": 14347
}
],
"assumption": false
}
],
"legend": [],
"metadata": {
"rationale": "refcount·압축·스냅샷은 같은 파일 형식의 다른 질문이라 제외하고, 읽기 매핑과 backing fallback에만 집중했다."
}
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,224 @@
{
"version": "1.1",
"id": "qemu-block-backend-forms",
"title": "virtqueue 뒤 QEMU block backend의 세 형태",
"question": "Guest /dev/vda 요청이 VM boundary를 넘은 뒤 QEMU에서 qcow2, RAW, Host block device 중 어느 backend로 이어지는가",
"type": "architecture",
"direction": "LR",
"audience": [
"virtio-blk와 QEMU block backend 경계를 이해하려는 개발자",
"Guest disk 이름만으로 Host backend를 단정하지 않으려는 운영자"
],
"summary": "Guest /dev/vda의 I/O는 virtio-blk·virtqueue를 지나 Host userspace의 QEMU block backend로 넘어간다. backend는 qcow2 file, RAW file, Host block device 세 형태일 수 있다. 파일 backend는 Host filesystem을 경유하고, Host block device는 파일이 아닌 장치를 backend로 쓴다.",
"alt": "Guest의 /dev/vda가 virtio-blk와 virtqueue를 지나 QEMU block backend에 도착한 뒤 qcow2 파일, RAW 파일, Host block device 세 갈래로 나뉘는 구조도. 그 아래 Host block stack은 별도 Concept이 맡아 이 그림에서는 다시 합치지 않는다.",
"long_description": "왼쪽에서 Guest /dev/vda 요청이 virtio-blk와 virtqueue를 지나 VM boundary를 넘는다. QEMU block backend에서 qcow2 file, RAW file, Host block device 세 갈래로 나뉜다. qcow2와 RAW 노드에는 Host filesystem을 경유한다는 차이를, Host block device에는 파일 backend가 아니라는 차이를 적었다. 세 경로 아래의 Host block stack은 별도 Concept이 맡으므로 이 그림에서는 다시 합치지 않는다.",
"source_context": {
"document": "docs/virtualization/final/document.md",
"document_sha256": "8c4ecc64c8cea9a4450ed7131fdd9cb2048dc092b66cd969f6346ed77887c210",
"anchor": {
"kind": "heading",
"value": "제4부 — 스토리지 가상화",
"line": 5791
}
},
"composition": {
"profile": "component-flow",
"diagram_only": true,
"reference_ids": [
"payment-event-flow"
],
"rationale": "핵심은 Guest disk 요청이 QEMU block backend를 지나 세 형태로 갈라지는 방향성이다. zone 상자보다 branch가 읽히는 것이 중요해 component-flow를 쓴다.",
"focus_node": "backend"
},
"groups": [],
"nodes": [
{
"id": "vda",
"label": "/dev/vda",
"kind": "storage",
"role": "source",
"description": "Guest가 보는 virtual disk.",
"evidence": [
{
"start_line": 6355,
"end_line": 6363
}
],
"assumption": false
},
{
"id": "virtio-queue",
"label": "virtio-blk · virtqueue",
"kind": "component",
"role": "service",
"description": "Guest /dev/vda 요청을 virtio-blk가 받아 virtqueue에 싣는 경로를 한 노드로 묶었다.",
"evidence": [
{
"start_line": 6355,
"end_line": 6364
}
],
"assumption": false,
"details": [
"Guest",
"VM boundary 직전"
]
},
{
"id": "backend",
"label": "QEMU block backend",
"kind": "service",
"role": "service",
"emphasis": "primary",
"description": "Guest virtual I/O를 실제 Host backend에 연결한다.",
"evidence": [
{
"start_line": 6367,
"end_line": 6387
}
],
"assumption": false
},
{
"id": "qcow2",
"label": "qcow2 file",
"kind": "storage",
"role": "service",
"details": [
"file backend",
"mapping · metadata",
"→ Host filesystem"
],
"description": "sparse/COW 기능을 가진 file backend.",
"evidence": [
{
"start_line": 6419,
"end_line": 6460
},
{
"start_line": 6512,
"end_line": 6547
}
],
"assumption": false
},
{
"id": "raw",
"label": "RAW file",
"kind": "storage",
"role": "service",
"description": "상대적으로 단순한 offset 대응을 쓰는 file backend.",
"evidence": [
{
"start_line": 6512,
"end_line": 6547
}
],
"assumption": false,
"details": [
"file backend",
"offset 대응",
"→ Host filesystem"
]
},
{
"id": "block-device",
"label": "Host block device",
"kind": "storage",
"role": "service",
"details": [
"/dev/...",
"file 없음",
"→ Host block stack"
],
"description": "QEMU가 Host block device를 직접 backend로 사용할 수 있는 형태.",
"evidence": [
{
"start_line": 6551,
"end_line": 6575
}
],
"assumption": false
}
],
"edges": [
{
"id": "vda-virtio",
"from": "vda",
"to": "virtio-queue",
"label": "block I/O",
"kind": "request",
"evidence": [
{
"start_line": 6355,
"end_line": 6363
}
],
"assumption": false
},
{
"id": "vq-backend",
"from": "virtio-queue",
"to": "backend",
"label": "VM boundary",
"kind": "request",
"evidence": [
{
"start_line": 6362,
"end_line": 6374
}
],
"assumption": false
},
{
"id": "backend-qcow2",
"from": "backend",
"to": "qcow2",
"label": "qcow2 backend",
"kind": "data",
"evidence": [
{
"start_line": 6369,
"end_line": 6379
},
{
"start_line": 6419,
"end_line": 6460
}
],
"assumption": false
},
{
"id": "backend-raw",
"from": "backend",
"to": "raw",
"label": "RAW backend",
"kind": "data",
"evidence": [
{
"start_line": 6512,
"end_line": 6547
}
],
"assumption": false
},
{
"id": "backend-block",
"from": "backend",
"to": "block-device",
"label": "block-device backend",
"kind": "data",
"evidence": [
{
"start_line": 6551,
"end_line": 6575
}
],
"assumption": false
}
],
"legend": [],
"metadata": {
"rationale": "이 그림의 물음은 QEMU backend 세 형태의 경계 차이다. 세 경로가 결국 Host block stack으로 내려가는 것은 별도 Concept이 맡으므로, 재합류 노드는 빼고 각 backend detail에 다음 계층을 적었다."
}
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,230 @@
{
"version": "1.1",
"id": "two-l7-hops-entry-recursion",
"title": "L7 두 홉과 진입점 이중화 재귀",
"question": "요청은 왜 nginx와 Traefik이라는 두 L7 홉을 지나며, nginx를 두 대로 늘리면 왜 다시 selector가 필요한가",
"type": "architecture",
"direction": "LR",
"audience": [
"실험대의 nginx와 Traefik 역할을 구분하려는 개발자",
"진입점 이중화가 왜 새 SPOF를 만드는지 이해하려는 운영자"
],
"summary": "정상 요청은 브라우저에서 edge nginx, Traefik, Pod 순으로 흐르고 두 L7 홉은 서로 다른 라우팅 범위를 맡는다. edge nginx를 두 대로 늘리면 어느 nginx를 고를 selector가 필요해지고 그 selector가 다시 단일 장애점이 된다.",
"alt": "브라우저에서 edge nginx와 Traefik을 거쳐 Pod로 가는 두 L7 홉과, edge nginx를 두 대로 늘릴 때 앞단 selector가 새 단일 장애점이 되는 재귀를 함께 보여 주는 흐름도.",
"long_description": "왼쪽의 정상 경로는 브라우저가 TLS를 edge nginx에서 종료하고 고정 노드 IP로 전달한 뒤, Traefik이 동적으로 Pod를 고르는 흐름이다. 아래쪽 이중화 경로는 nginx-1과 nginx-2 앞에 selector를 두면 어느 nginx로 갈지 정할 수 있지만 selector 자체가 새 SPOF가 되어 같은 질문이 반복된다는 점을 보여 준다. VRRP·DNS·BGP·클라우드 LB 같은 재귀 종료 방식의 상세 비교는 본문 표에 남기고 그림에는 넣지 않는다.",
"source_context": {
"document": "docs/virtualization/final/document.md",
"document_sha256": "8c4ecc64c8cea9a4450ed7131fdd9cb2048dc092b66cd969f6346ed77887c210",
"anchor": {
"kind": "heading",
"value": "261. 진입점 자체가 죽으면 — 로드밸런서의 재귀 문제",
"line": 15796
}
},
"composition": {
"profile": "component-flow",
"diagram_only": true,
"reference_ids": [
"payment-event-flow"
],
"rationale": "핵심은 요청과 선택 경로의 방향성이다. 비교표보다 엣지를 지웠을 때 의미가 더 크게 사라지므로 component-flow로 그린다.",
"focus_node": "edge-nginx"
},
"groups": [],
"nodes": [
{
"id": "browser",
"label": "Browser",
"kind": "actor",
"role": "source",
"shape": "actor",
"description": "외부 요청의 시작점.",
"evidence": [
{
"start_line": 15860,
"end_line": 15864
}
],
"assumption": false
},
{
"id": "edge-nginx",
"label": "edge nginx",
"kind": "service",
"role": "service",
"emphasis": "primary",
"details": [
"L7",
"TLS 종료",
"고정 노드 선택"
],
"description": "클러스터 밖 진입점. TLS와 인증서, forwarded header, 어느 노드로 보낼지를 맡는다.",
"evidence": [
{
"start_line": 15860,
"end_line": 15882
}
],
"assumption": false
},
{
"id": "traefik",
"label": "Traefik",
"kind": "service",
"role": "service",
"details": [
"L7",
"Ingress",
"동적 Pod 선택"
],
"description": "클러스터 안에서 Service/Ingress 상태를 따라 어느 Pod로 보낼지 정한다.",
"evidence": [
{
"start_line": 15872,
"end_line": 15882
}
],
"assumption": false
},
{
"id": "pod",
"label": "Pod",
"kind": "runtime",
"role": "sink",
"description": "Traefik이 선택한 실제 워크로드.",
"evidence": [
{
"start_line": 15860,
"end_line": 15864
}
],
"assumption": false
},
{
"id": "selector",
"label": "앞단 selector",
"kind": "service",
"role": "service",
"details": [
"edge redundancy"
],
"description": "nginx를 여러 머신으로 이중화했을 때 어느 nginx로 갈지 정하는 새 앞단.",
"evidence": [
{
"start_line": 15941,
"end_line": 15945
}
],
"assumption": false
},
{
"id": "nginx-pair",
"label": "nginx-1 · nginx-2",
"kind": "service",
"role": "service",
"details": [
"서로 다른 머신"
],
"description": "진짜 이중화를 위해 머신을 늘린 edge nginx 쌍.",
"evidence": [
{
"start_line": 15934,
"end_line": 15944
}
],
"assumption": false
},
{
"id": "new-spof",
"label": "selector가 새 SPOF",
"kind": "result",
"role": "sink",
"description": "selector를 하나 더 두면 같은 단일 장애점 질문이 한 단계 앞으로 이동한다.",
"evidence": [
{
"start_line": 15941,
"end_line": 15952
}
],
"assumption": false
}
],
"edges": [
{
"id": "browser-edge",
"from": "browser",
"to": "edge-nginx",
"label": "HTTPS",
"kind": "request",
"evidence": [
{
"start_line": 15860,
"end_line": 15864
}
],
"assumption": false
},
{
"id": "edge-traefik",
"from": "edge-nginx",
"to": "traefik",
"label": "노드 선택 · HTTP",
"kind": "request",
"evidence": [
{
"start_line": 15872,
"end_line": 15882
}
],
"assumption": false
},
{
"id": "traefik-pod",
"from": "traefik",
"to": "pod",
"label": "Ingress routing",
"kind": "request",
"evidence": [
{
"start_line": 15860,
"end_line": 15882
}
],
"assumption": false
},
{
"id": "selector-pair",
"from": "selector",
"to": "nginx-pair",
"label": "nginx 선택",
"kind": "request",
"style": "dashed",
"evidence": [
{
"start_line": 15941,
"end_line": 15945
}
],
"assumption": false
},
{
"id": "selector-spof",
"from": "selector",
"to": "new-spof",
"label": "단일 selector",
"kind": "result",
"style": "dashed",
"evidence": [
{
"start_line": 15941,
"end_line": 15952
}
],
"assumption": false
}
],
"legend": [],
"metadata": {
"rationale": "두 L7 홉의 역할 차이와 진입점 이중화 재귀만 남겼다. VRRP의 상세 동작은 별도 본문 설명이므로 그림에서 제외한다."
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -14,11 +14,11 @@
"long_description": "참여자는 왼쪽부터 Guest kernel 의 virtio-balloon Driver, virtqueue, QEMU virtio-balloon Device, Host Memory Management 네 개다. 메시지는 위에서 아래로 읽는다. 1번은 Host Memory Management 가 QEMU 쪽 device 에 balloon target 조정을 보내는 것이고, 2번에서 그 요청이 virtqueue 로 내려가면서 VM Boundary 를 넘고, 3번에서 Guest kernel 의 driver 에 balloon target 이 닿는다. 여기서 방향이 바뀐다. 4번에서 driver 가 확보한 Guest page 를 virtqueue 에 올리고, 5번에서 그것이 다시 VM Boundary 를 넘어 QEMU 쪽 device 로 가고, 6번에서 device 가 Host Memory Management 에 회수 가능 backing 으로 넘긴다. target 을 올릴 때와 내릴 때 Guest 가 쓸 수 있는 memory 가 어느 쪽으로 움직이는지는 본문의 표가 맡는다.",
"source_context": {
"document": "docs/virtualization/final/document.md",
"document_sha256": "181e2b3cc8a45bae81e7e8193d026937c4d586ae4495d3b2c323eb7e6abcfadd",
"document_sha256": "8c4ecc64c8cea9a4450ed7131fdd9cb2048dc092b66cd969f6346ed77887c210",
"anchor": {
"kind": "heading",
"value": "65. virtio-balloon 구조",
"line": 3008
"line": 3021
}
},
"composition": {
@@ -45,12 +45,12 @@
"description": "Guest kernel 쪽 balloon driver. target 을 받아 Guest page 를 확보한다.",
"evidence": [
{
"start_line": 3013,
"end_line": 3015
"start_line": 3026,
"end_line": 3028
},
{
"start_line": 3046,
"end_line": 3046
"start_line": 3059,
"end_line": 3059
}
],
"assumption": false
@@ -64,8 +64,8 @@
"description": "Guest 와 QEMU 가 balloon 요청을 주고받는 공유 큐. VM Boundary 바로 안쪽이다.",
"evidence": [
{
"start_line": 3017,
"end_line": 3017
"start_line": 3030,
"end_line": 3030
}
],
"assumption": false
@@ -79,12 +79,12 @@
"description": "Host 쪽 balloon device. 경계를 넘어온 내용을 Host memory management 로 넘긴다.",
"evidence": [
{
"start_line": 3022,
"end_line": 3022
"start_line": 3035,
"end_line": 3035
},
{
"start_line": 3037,
"end_line": 3041
"start_line": 3050,
"end_line": 3054
}
],
"assumption": false
@@ -98,12 +98,12 @@
"description": "balloon target 을 정하는 쪽이자 회수 기회가 생기는 자리.",
"evidence": [
{
"start_line": 3025,
"end_line": 3025
"start_line": 3038,
"end_line": 3038
},
{
"start_line": 3034,
"end_line": 3037
"start_line": 3047,
"end_line": 3050
}
],
"assumption": false
@@ -120,8 +120,8 @@
"style": "solid",
"evidence": [
{
"start_line": 3034,
"end_line": 3041
"start_line": 3047,
"end_line": 3054
}
],
"assumption": false
@@ -136,12 +136,12 @@
"style": "solid",
"evidence": [
{
"start_line": 3017,
"end_line": 3022
"start_line": 3030,
"end_line": 3035
},
{
"start_line": 3041,
"end_line": 3046
"start_line": 3054,
"end_line": 3059
}
],
"assumption": false
@@ -156,12 +156,12 @@
"style": "solid",
"evidence": [
{
"start_line": 3013,
"end_line": 3017
"start_line": 3026,
"end_line": 3030
},
{
"start_line": 3043,
"end_line": 3046
"start_line": 3056,
"end_line": 3059
}
],
"assumption": false
@@ -176,8 +176,8 @@
"style": "solid",
"evidence": [
{
"start_line": 3046,
"end_line": 3050
"start_line": 3059,
"end_line": 3063
}
],
"assumption": false
@@ -192,8 +192,8 @@
"style": "solid",
"evidence": [
{
"start_line": 3017,
"end_line": 3025
"start_line": 3030,
"end_line": 3038
}
],
"assumption": false
@@ -208,12 +208,12 @@
"style": "solid",
"evidence": [
{
"start_line": 3022,
"end_line": 3028
"start_line": 3035,
"end_line": 3041
},
{
"start_line": 3077,
"end_line": 3079
"start_line": 3090,
"end_line": 3092
}
],
"assumption": false
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -14,11 +14,11 @@
"long_description": "왼쪽에서 오른쪽으로 읽는다. 왼쪽 끝에 Guest 가 있고 VM Exit 이 KVM 으로 제어권을 넘긴다. KVM 은 Exit Reason 을 확인해 두 갈래 중 하나를 고른다. 커널 안에서 처리할 수 있는 Exit 은 Kernel 쪽 KVM 커널 처리로 가고, userspace 처리가 필요한 Exit 만 KVM_RUN 이 반환되면서 Userspace 쪽 QEMU 로 간다. QEMU 는 필요한 장치 동작을 처리한 뒤 KVM_RUN 을 다시 호출한다. 두 갈래는 오른쪽 끝에서 다시 만나며 어느 쪽이든 VM Entry 를 지나 Guest 실행 재개로 돌아온다. 무엇이 Exit 을 부르는지와 Exit 이 잦을 때 무엇을 함께 보는지는 본문의 표와 문단이 맡는다.",
"source_context": {
"document": "docs/virtualization/final/document.md",
"document_sha256": "181e2b3cc8a45bae81e7e8193d026937c4d586ae4495d3b2c323eb7e6abcfadd",
"document_sha256": "8c4ecc64c8cea9a4450ed7131fdd9cb2048dc092b66cd969f6346ed77887c210",
"anchor": {
"kind": "heading",
"value": "9. VM Exit 이후 처리",
"line": 493
"line": 506
}
},
"composition": {
@@ -41,8 +41,8 @@
"description": "VM Exit 이 나기 직전까지 실행 중이던 Guest.",
"evidence": [
{
"start_line": 498,
"end_line": 501
"start_line": 511,
"end_line": 514
}
],
"assumption": false
@@ -60,12 +60,12 @@
"description": "VM Exit 을 받아 Exit Reason 을 확인하고 갈래를 고르는 자리.",
"evidence": [
{
"start_line": 495,
"end_line": 495
"start_line": 508,
"end_line": 508
},
{
"start_line": 502,
"end_line": 504
"start_line": 515,
"end_line": 517
}
],
"assumption": false
@@ -79,12 +79,12 @@
"description": "커널 안에서 끝낼 수 있는 Exit 을 처리하는 갈래. QEMU 까지 올라가지 않는다.",
"evidence": [
{
"start_line": 508,
"end_line": 510
"start_line": 521,
"end_line": 523
},
{
"start_line": 531,
"end_line": 531
"start_line": 544,
"end_line": 544
}
],
"assumption": false,
@@ -106,12 +106,12 @@
"description": "userspace 처리가 필요한 Exit 에서만 개입하는 갈래.",
"evidence": [
{
"start_line": 510,
"end_line": 516
"start_line": 523,
"end_line": 529
},
{
"start_line": 533,
"end_line": 533
"start_line": 546,
"end_line": 546
}
],
"assumption": false
@@ -125,12 +125,12 @@
"description": "두 갈래가 다시 만나는 자리. VM Exit 은 VM 종료가 아니라 여기로 돌아온다.",
"evidence": [
{
"start_line": 521,
"end_line": 524
"start_line": 534,
"end_line": 537
},
{
"start_line": 529,
"end_line": 529
"start_line": 542,
"end_line": 542
}
],
"assumption": false
@@ -146,12 +146,12 @@
"style": "solid",
"evidence": [
{
"start_line": 495,
"end_line": 495
"start_line": 508,
"end_line": 508
},
{
"start_line": 498,
"end_line": 502
"start_line": 511,
"end_line": 515
}
],
"assumption": false
@@ -165,12 +165,12 @@
"style": "solid",
"evidence": [
{
"start_line": 504,
"end_line": 510
"start_line": 517,
"end_line": 523
},
{
"start_line": 531,
"end_line": 531
"start_line": 544,
"end_line": 544
}
],
"assumption": false
@@ -184,12 +184,12 @@
"style": "solid",
"evidence": [
{
"start_line": 508,
"end_line": 512
"start_line": 521,
"end_line": 525
},
{
"start_line": 533,
"end_line": 533
"start_line": 546,
"end_line": 546
}
],
"assumption": false
@@ -203,12 +203,12 @@
"style": "solid",
"evidence": [
{
"start_line": 518,
"end_line": 524
"start_line": 531,
"end_line": 537
},
{
"start_line": 531,
"end_line": 531
"start_line": 544,
"end_line": 544
}
],
"assumption": false
@@ -222,12 +222,12 @@
"style": "solid",
"evidence": [
{
"start_line": 514,
"end_line": 524
"start_line": 527,
"end_line": 537
},
{
"start_line": 533,
"end_line": 533
"start_line": 546,
"end_line": 546
}
],
"assumption": false
@@ -237,4 +237,4 @@
"metadata": {
"rationale": "flow 로 고른 이유는 이 절이 답하는 물음이 「제어권이 어디까지 갔다가 어떻게 돌아오는가」이기 때문이다. Kernel·Userspace 를 경계 상자로 그리면 두 상자가 같은 열에서 겹쳐(check-figure-overlap 15750px²) 어느 노드가 어느 경계인지 오히려 흐려져 노드의 details 로 내렸다. 무엇이 Exit 을 부르는지(§8)와 Exit 이 잦을 때 무엇을 함께 보는지는 본문 표가 이미 맡고 있어 그림에 넣지 않았다."
}
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -4,7 +4,7 @@
"title": "가상 머신 둘과 호스트 프로세스가 하나의 NVMe 로 모인다",
"question": "서로 다른 곳에서 시작한 I/O 는 어디에서 하나로 모여 device 로 나가는가",
"type": "flow",
"direction": "TB",
"direction": "LR",
"audience": [
"가상 머신 위에서 본 저장소 지연의 원인을 가르려는 사람",
"Storage Contention 과 CPU Contention 을 나눠 재려는 사람"
@@ -14,11 +14,11 @@
"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",
"document_sha256": "8c4ecc64c8cea9a4450ed7131fdd9cb2048dc092b66cd969f6346ed77887c210",
"anchor": {
"kind": "heading",
"value": "159. 여러 VM이 하나의 NVMe를 공유하면",
"line": 7026
"line": 7039
}
},
"composition": {
@@ -46,12 +46,12 @@
"description": "가상 머신 하나의 QEMU 프로세스. 게스트의 I/O 가 여기서 호스트 파일 I/O 가 된다.",
"evidence": [
{
"start_line": 7029,
"end_line": 7029
"start_line": 7042,
"end_line": 7042
},
{
"start_line": 7041,
"end_line": 7044
"start_line": 7054,
"end_line": 7057
}
],
"assumption": false
@@ -69,12 +69,12 @@
"description": "다른 가상 머신의 QEMU 프로세스.",
"evidence": [
{
"start_line": 7031,
"end_line": 7031
"start_line": 7044,
"end_line": 7044
},
{
"start_line": 7046,
"end_line": 7048
"start_line": 7059,
"end_line": 7061
}
],
"assumption": false
@@ -88,8 +88,8 @@
"description": "호스트에서 직접 도는 프로세스.",
"evidence": [
{
"start_line": 7033,
"end_line": 7033
"start_line": 7046,
"end_line": 7046
}
],
"assumption": false
@@ -103,8 +103,8 @@
"description": "그 밖의 호스트 프로세스.",
"evidence": [
{
"start_line": 7035,
"end_line": 7038
"start_line": 7048,
"end_line": 7051
}
],
"assumption": false
@@ -123,20 +123,20 @@
"description": "어디서 왔는지 구분하지 않고 모두 Host block request 로 다루는 계층. queue 에서 관리해 device 로 내보낸다.",
"evidence": [
{
"start_line": 7031,
"end_line": 7031
"start_line": 7044,
"end_line": 7044
},
{
"start_line": 7017,
"end_line": 7022
"start_line": 7030,
"end_line": 7035
},
{
"start_line": 7054,
"end_line": 7054
"start_line": 7067,
"end_line": 7067
},
{
"start_line": 7058,
"end_line": 7060
"start_line": 7071,
"end_line": 7073
}
],
"assumption": false
@@ -150,16 +150,16 @@
"description": "네 갈래가 결국 함께 쓰는 하나의 물리 device.",
"evidence": [
{
"start_line": 7031,
"end_line": 7031
"start_line": 7044,
"end_line": 7044
},
{
"start_line": 7019,
"end_line": 7019
"start_line": 7032,
"end_line": 7032
},
{
"start_line": 7066,
"end_line": 7069
"start_line": 7079,
"end_line": 7082
}
],
"assumption": false
@@ -176,8 +176,8 @@
"order": 1,
"evidence": [
{
"start_line": 7029,
"end_line": 7031
"start_line": 7042,
"end_line": 7044
}
],
"assumption": false
@@ -192,8 +192,8 @@
"order": 2,
"evidence": [
{
"start_line": 7031,
"end_line": 7031
"start_line": 7044,
"end_line": 7044
}
],
"assumption": false
@@ -208,8 +208,8 @@
"order": 3,
"evidence": [
{
"start_line": 7031,
"end_line": 7033
"start_line": 7044,
"end_line": 7046
}
],
"assumption": false
@@ -224,8 +224,8 @@
"order": 4,
"evidence": [
{
"start_line": 7031,
"end_line": 7035
"start_line": 7044,
"end_line": 7048
}
],
"assumption": false
@@ -240,12 +240,12 @@
"order": 5,
"evidence": [
{
"start_line": 7031,
"end_line": 7031
"start_line": 7044,
"end_line": 7044
},
{
"start_line": 7054,
"end_line": 7054
"start_line": 7067,
"end_line": 7067
}
],
"assumption": false
@@ -253,6 +253,6 @@
],
"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 가 말하지 않는다."
"rationale": "component-flow 를 고른 이유는 이 절이 답하는 물음이 「여러 곳에서 온 요청이 어디에서 하나가 되는가」이기 때문이다. 현재 renderer에서는 LR로 두어 네 출발점이 Host Block Layer 로 수렴하는 선을 상자 충돌 없이 보이게 한다. 네 출발점의 화살표에는 같은 Host block request 라벨을 반복하지 않고 그 의미를 모이는 노드의 details 로 올렸다. blk-mq 와 I/O Scheduler 의 세부는 다른 절이 맡는다."
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -14,11 +14,11 @@
"long_description": "위에서 아래로 읽는다. 맨 위 PostgreSQL 이 fsync() 를 부르고 그 요구가 Guest Filesystem 으로 내려간다. Guest Block Layer 에서 FLUSH 등의 요청으로 바뀌고, virtio-blk 를 지나 VM 경계를 넘어 QEMU / Backend 로 간다. 거기서 Host Storage Stack 을 지나 맨 아래 Physical Storage 까지 그 의미가 전달되어야 완료가 성립한다. 여섯 번 손이 바뀌는 동안 한 곳이라도 그 의미를 지키지 않으면 게스트가 받은 완료 응답이 거짓이 된다. write() 완료와 writeback 완료, fsync/flush 완료, 전원 장애에도 안전한 durability 가 서로 다르다는 구분은 본문의 코드블록이 맡는다.",
"source_context": {
"document": "docs/virtualization/final/document.md",
"document_sha256": "181e2b3cc8a45bae81e7e8193d026937c4d586ae4495d3b2c323eb7e6abcfadd",
"document_sha256": "8c4ecc64c8cea9a4450ed7131fdd9cb2048dc092b66cd969f6346ed77887c210",
"anchor": {
"kind": "heading",
"value": "150. `fsync()`가 필요한 이유",
"line": 6723
"line": 6736
}
},
"composition": {
@@ -45,12 +45,12 @@
"description": "필요한 시점에 fsync() 를 불러 영속성 경계까지 반영을 요청하는 게스트 프로세스.",
"evidence": [
{
"start_line": 6742,
"end_line": 6742
"start_line": 6755,
"end_line": 6755
},
{
"start_line": 6726,
"end_line": 6734
"start_line": 6739,
"end_line": 6747
}
],
"assumption": false
@@ -64,8 +64,8 @@
"description": "fsync() 요구를 처음 받는 게스트 파일시스템.",
"evidence": [
{
"start_line": 6746,
"end_line": 6746
"start_line": 6759,
"end_line": 6759
}
],
"assumption": false
@@ -79,8 +79,8 @@
"description": "요구를 FLUSH 등의 block 요청으로 바꾸는 계층.",
"evidence": [
{
"start_line": 6749,
"end_line": 6751
"start_line": 6762,
"end_line": 6764
}
],
"assumption": false
@@ -94,8 +94,8 @@
"description": "그 요청을 VM 경계 너머로 옮기는 게스트 driver.",
"evidence": [
{
"start_line": 6753,
"end_line": 6753
"start_line": 6766,
"end_line": 6766
}
],
"assumption": false
@@ -109,12 +109,12 @@
"description": "요청을 받아 호스트 쪽 파일 I/O 나 block I/O 로 잇는 자리.",
"evidence": [
{
"start_line": 6756,
"end_line": 6756
"start_line": 6769,
"end_line": 6769
},
{
"start_line": 6686,
"end_line": 6686
"start_line": 6699,
"end_line": 6699
}
],
"assumption": false
@@ -128,12 +128,12 @@
"description": "호스트 Page Cache 와 파일시스템, Block Layer 가 놓인 구간.",
"evidence": [
{
"start_line": 6759,
"end_line": 6759
"start_line": 6772,
"end_line": 6772
},
{
"start_line": 6688,
"end_line": 6692
"start_line": 6701,
"end_line": 6705
}
],
"assumption": false
@@ -148,12 +148,12 @@
"description": "요구가 여기까지 전달되어야 정전 이후 생존을 말할 수 있다.",
"evidence": [
{
"start_line": 6762,
"end_line": 6765
"start_line": 6775,
"end_line": 6778
},
{
"start_line": 6729,
"end_line": 6729
"start_line": 6742,
"end_line": 6742
}
],
"assumption": false
@@ -170,8 +170,8 @@
"order": 1,
"evidence": [
{
"start_line": 6744,
"end_line": 6746
"start_line": 6757,
"end_line": 6759
}
],
"assumption": false
@@ -186,8 +186,8 @@
"order": 2,
"evidence": [
{
"start_line": 6746,
"end_line": 6749
"start_line": 6759,
"end_line": 6762
}
],
"assumption": false
@@ -202,8 +202,8 @@
"order": 3,
"evidence": [
{
"start_line": 6751,
"end_line": 6753
"start_line": 6764,
"end_line": 6766
}
],
"assumption": false
@@ -218,8 +218,8 @@
"order": 4,
"evidence": [
{
"start_line": 6753,
"end_line": 6756
"start_line": 6766,
"end_line": 6769
}
],
"assumption": false
@@ -234,8 +234,8 @@
"order": 5,
"evidence": [
{
"start_line": 6756,
"end_line": 6759
"start_line": 6769,
"end_line": 6772
}
],
"assumption": false
@@ -250,8 +250,8 @@
"order": 6,
"evidence": [
{
"start_line": 6759,
"end_line": 6765
"start_line": 6772,
"end_line": 6778
}
],
"assumption": false