Files
document-haness/studio-dump.json
T

1 line
108 KiB
JSON

"{\"b1cda8c4-d31d-4ae4-a4d9-4103a0ab3109\":{\"document\":{\"kind\":\"REFERENCE\",\"title\":\"지연 원인을 CPU 로 읽기 전에 storage 계층을 따로 잰다\",\"slug\":\"read-storage-before-blaming-cpu-for-latency\",\"summary\":\"요청이 느린데 CPU 사용률이 낮으면 CPU 는 원인이 아니라고 읽고 다음 후보로 넘어가기 쉬운데, 그 사이에 스토리지 대기는 어느 지표에도 나타나지 않는다. 개념 문서는 애플리케이션이 스토리지 완료를 기다리는 동안 CPU 사용률이 높지 않을 수 있다고 적고 CPU 30% 인데 요청 지연 2초인 조합을 들었다. 이 기준은 자원을 CPU 와 스토리지로 가르는 순서와, 그때 같은 시간축에 무엇을 남길지를 고정한다.\",\"relations\":[{\"id\":\"2b0dfdfb-b468-449c-b013-64b9bb0e7aa9\",\"targetId\":\"ecaaa37d-7b33-4cbf-9bc5-4c6caad14ee5\",\"reason\":\"이 기준이 함께 보라고 하는 호스트 쪽 지표가 어느 계층에서 나오는지를 그 글이 설명한다.\",\"order\":0},{\"id\":\"2f04f658-ceed-4f7d-a9a6-929d14cd753b\",\"targetId\":\"3b05c1f0-13e9-414a-97a6-2078fb4bab26\",\"reason\":\"게스트 안에서 잰 입출력 지연이 어디까지의 시간인지를 그 글이 설명한다.\",\"order\":1},{\"id\":\"02442d33-8b22-4265-93a4-e5f8fd6bd0bb\",\"targetId\":\"274c5636-33f6-4c19-a54b-d2a96af31289\",\"reason\":\"무엇 위에서 재고 있는지를 먼저 확정하는 기준이다. 이 기준의 호스트 쪽 관측은 그 확정을 전제한다.\",\"order\":2},{\"id\":\"ce15fdc4-8bdf-4d69-9828-5f38a937a3bd\",\"targetId\":\"63fa33e5-9957-4d28-8d3c-c75735a73bd6\",\"reason\":\"같은 형태의 판독 기준이고, 그쪽은 메모리 증상 안에서 계층을 가른다.\",\"order\":3}],\"id\":\"b1cda8c4-d31d-4ae4-a4d9-4103a0ab3109\",\"version\":3,\"updatedAt\":\"2026-09-09T07:05:07.396273Z\",\"purpose\":\"CPU 사용률 하나로 지연 원인을 자원에 귀속하면 스토리지 대기가 후보에서 빠진다. 개념 문서는 그 조합을 그대로 적었다. PostgreSQL 이 스토리지 완료를 기다리고 있으면 CPU 사용률이 높지 않을 수도 있어서, CPU 30% 인데 요청 지연 2초가 가능하다.\\n\\nCPU 경쟁과 스토리지 경쟁은 경쟁하는 자원이 다르다. 개념 문서는 앞의 것을 호스트 논리 CPU 실행 시간 경쟁으로, 뒤의 것을 IOPS 와 대역폭과 큐와 장치 처리시간 경쟁으로 갈라 두었다. 두 자원을 한 지표로 대신 읽으면 어느 쪽이 밀렸는지 갈리지 않는다.\",\"rules\":[{\"id\":\"308ce6da-886b-4ac5-868b-1cba74497174\",\"title\":\"CPU 사용률이 낮다는 이유로 스토리지를 후보에서 빼지 않는다\",\"body\":\"CPU 사용률이 낮은 것은 CPU 가 놀고 있다는 뜻일 수도 있고 무언가를 기다리고 있다는 뜻일 수도 있다. 개념 문서가 그린 경로는 HTTP Request 에서 Keycloak, PostgreSQL, fsync(), Storage 로 이어진다. 이 경로에서 PostgreSQL 이 스토리지 완료를 기다리면 CPU 는 그 시간 동안 일하지 않으므로, CPU 지표만으로 지연 원인을 판단하지 않는다.\",\"order\":0},{\"id\":\"2c8e83b9-e706-4ea8-aff3-e6026991cbde\",\"title\":\"CPU 경쟁과 스토리지 경쟁을 다른 자원 경쟁으로 세어 적는다\",\"body\":\"호스트 논리 CPU 실행 시간이 모자란 것과 IOPS 나 대역폭이나 큐나 장치 처리시간이 모자란 것은 다른 사건이다. 개념 문서는 여러 가상 머신이 같은 물리 NVMe 를 쓰면 VM1 에서 대량 입출력이 발생했을 때 VM2 의 스토리지 지연이 커질 수 있다고 적었다. 이때 VM2 의 vCPU 는 부족하지 않을 수 있어서, 두 경쟁을 한 칸에 적으면 조치할 대상이 정해지지 않는다.\",\"order\":1},{\"id\":\"fbd37343-9ca1-4546-a632-1f7469c8c98c\",\"title\":\"여덟 지표를 같은 시간축에 함께 남긴다\",\"body\":\"개념 문서가 스토리지 문제를 분석할 때 CPU 사용률과 함께 보라고 적은 것은 여덟이다.\\n\\nGuest I/O latency\\nHost I/O queue\\nHost storage latency\\nQEMU backend\\ncache mode\\nI/O Scheduler\\nNVMe\\n다른 VM 의 Storage load\\n\\n앞의 셋은 시간에 따라 변하는 값이라 같은 시각에 함께 찍어야 짝이 맞다. 뒤의 다섯은 그때의 구성이라 한 번 적어 두면 그 측정 구간 전체에 붙는다. 이 여덟을 이 호스트에서 한 시간축에 모아 찍은 기록은 아직 없다.\",\"order\":2},{\"id\":\"a3fb6aa3-bda7-40b5-bd2a-6408d66064b0\",\"title\":\"게스트 안에서 잰 값만으로 스토리지 성능을 결론내지 않는다\",\"body\":\"개념 문서의 Claim 6 은 스토리지 성능이 게스트 내부만으로 결정되지 않는다고 적었다. 영향을 주는 것으로 든 것은 QEMU 와 백엔드, 호스트 블록 큐, 입출력 스케줄러, NVMe, 캐시, 다른 가상 머신의 스토리지 부하다. 게스트 쪽 지표는 이 여섯이 모두 반영된 뒤의 값이므로, 게스트만 보면 값이 왜 그렇게 나왔는지는 알 수 없다.\",\"order\":3},{\"id\":\"ff34a17b-6ca4-4c70-8174-bcd743af7246\",\"title\":\"무엇을 볼지 정한 뒤에 명령을 고른다\",\"body\":\"명령 목록 자체는 규칙이 아니다. 무엇을 함께 볼지는 앞의 규칙들이 정하고 명령은 그 도구를 댈 뿐이라, 목록만 옮겨 적으면 무엇을 가르려고 그것을 보는지가 남지 않는다.\\n\\n장치 입출력 관측은 iostat -xz 1 로 한다. 읽기와 쓰기의 처리량과 IOPS 와 요청 지연과 큐 상태와 device utilization 성격의 지표가 여기서 나오고, 어떤 프로세스가 입출력을 발생시키는지는 iotop 으로 본다. 개념 문서가 양쪽에 나눠 적은 명령은 이렇다.\\n\\nGuest : lsblk · mount · df -h · cat /proc/mounts · iostat -xz 1\\nHost : virsh domblklist <VM_NAME> · qemu-img info 에 disk image 경로 · lsblk · /sys/block 아래 그 device 의 queue/scheduler · iostat -xz 1 · iotop\",\"order\":4}],\"applyWhen\":[{\"id\":\"7a52ea15-b164-44c2-bd78-85759b0bd5c0\",\"text\":\"요청 지연이나 처리량 저하의 원인을 자원에 귀속하려는 판독 전부\",\"order\":0},{\"id\":\"ca127a78-8471-432d-ac65-2eb24725d591\",\"text\":\"CPU 사용률이 낮은데 요청 지연이 큰 구간\",\"order\":1},{\"id\":\"4773788e-9148-46d3-a411-7b48336d2368\",\"text\":\"데이터베이스가 요청 경로에 들어 있는 서비스\",\"order\":2},{\"id\":\"88e66b92-b9fd-42de-9175-01a0595bfe60\",\"text\":\"가상 머신 여러 대가 한 물리 장치를 함께 쓰는 환경\",\"order\":3}],\"exceptions\":[{\"id\":\"7f237fa6-0ea7-427e-9901-50fc2b0290c4\",\"text\":\"이 기준은 어느 자원인지를 좁힐 뿐 원인을 확정하지 않는다. 스토리지 지표가 깨끗하게 나와도 CPU 쪽을 배제하려면 vCPU 경쟁과 steal time 을 따로 본다. 그것은 CPU 가상화 쪽 기준이 받는다.\",\"order\":0},{\"id\":\"e4fcac30-2ede-4496-81d9-753852985a00\",\"text\":\"iostat -xz 1 이 보여 주는 device utilization 성격의 지표는 NVMe 처럼 병렬성이 큰 장치에서 포화도로 그대로 읽히지 않는다. 개념 문서가 NVMe 는 높은 병렬성과 큐 깊이를 지원한다고 적었다.\",\"order\":1},{\"id\":\"9b3955f0-d235-454b-96c4-bb804b9d2d8e\",\"text\":\"호스트에서 잰 값은 어느 가상 머신의 입출력인지 갈라 주지 않는다. 개념 문서는 Host Block Layer 가 그 입출력을 가상 머신 안의 프로세스가 시작했는지 호스트 프로세스가 시작했는지 본질적으로 구분해서 처리하는 계층이 아니라고 밝혔다. 가상 머신별로 가르려면 iotop 이나 게스트 쪽 관측을 같은 시각에 함께 찍는다.\",\"order\":2},{\"id\":\"7cb5e998-af8d-45e5-8169-691daa6e2707\",\"text\":\"이 저장소에는 이 기준으로 원인을 실제로 가른 측정이 없다. 여기 적은 것은 개념 문서가 서술한 판독 순서이고 이 호스트에서 확인된 값이 아니다.\",\"order\":3}],\"examples\":[{\"id\":\"b3d0489e-6155-4997-add8-f93a2f817db2\",\"text\":\"CPU 30% · 요청 지연 2초 : 스토리지 대기가 후보에 남는 조합\",\"order\":0},{\"id\":\"8bcae565-79cb-4d70-a070-4dae0fd40e36\",\"text\":\"CPU Contention : 호스트 논리 CPU 실행 시간 경쟁\",\"order\":1},{\"id\":\"00472506-3572-4670-b39d-67def27d7fb1\",\"text\":\"Storage Contention : IOPS · 대역폭 · 큐 · 장치 처리시간 경쟁\",\"order\":2},{\"id\":\"4d895dc6-0d52-48dc-bead-e6e0670779f2\",\"text\":\"같은 시각에 함께 찍는 값 : Guest I/O latency · Host I/O queue · Host storage latency\",\"order\":3},{\"id\":\"7d8aa554-3c56-45bb-aec6-8387585b4469\",\"text\":\"측정 구간에 한 번 적어 두는 구성 : QEMU backend · cache mode · I/O Scheduler · NVMe\",\"order\":4},{\"id\":\"5f3eb6c3-3800-4a02-866f-c05fc2b277fb\",\"text\":\"이 호스트에서 잰 값 : x\",\"order\":5}],\"projectId\":\"8a638411-2b21-4202-8bf0-720180d77897\",\"topicId\":\"23314be2-7350-44f5-8989-575c3b40898f\",\"variantIds\":[],\"verifiedOn\":null},\"currentValidation\":null,\"latestPreview\":null,\"currentPublication\":null,\"dependencyRevision\":\"444e9ef9ef36aa9a05d45dc6e7268dbc\",\"nextAction\":\"VALIDATE\"},\"decbab8d-9c98-45c6-b5b8-e6162e407299\":{\"document\":{\"kind\":\"REFERENCE\",\"title\":\"빨라진 구성이 durability contract 를 지운 것은 아닌지 먼저 가른다\",\"slug\":\"a-speedup-that-removed-durability-is-not-an-optimization\",\"summary\":\"durability 는 전원이 나가도 데이터가 남아 있는 영속성을 말한다. 쓰기 지연이 줄면 그 변경은 성능 개선으로 기록되지만, 무엇을 더 이상 보장하지 않게 됐는지는 아무 데도 적히지 않는다. 개념 문서는 fsync() 를 없애서 빨라졌다면 그것이 최적화가 아니라 durability contract 를 제거한 것일 수 있다고 적었다. 게스트가 요청한 FLUSH 에 완료로 응답했는데 데이터는 호스트 RAM 에만 있는 상태도 성능 문제가 아니라 correctness 문제로 놓았다. 이 기준은 빨라진 구성을 평가할 때 어느 단계가 빠졌는지를 먼저 적게 한다.\",\"relations\":[{\"id\":\"0d91a0eb-6416-4c6f-bc61-a15dd610d4e1\",\"targetId\":\"8fafaeea-8746-4537-87a4-679a3a91aa4c\",\"reason\":\"이 기준이 갈라 적으라고 하는 네 단계를 그 글이 설명한다.\",\"order\":0},{\"id\":\"47aa4725-b02e-4094-a8bf-6df3773e8345\",\"targetId\":\"892b6087-8961-486c-b4d9-d3dd46bc2605\",\"reason\":\"영속성을 어느 백엔드 위에서 이야기하는지 그 글이 설명한다.\",\"order\":1},{\"id\":\"6ea2c1ea-dc82-4478-bc2e-2ca2c7564994\",\"targetId\":\"480f86ac-86fb-4290-9bf5-584763aa1363\",\"reason\":\"이 기준이 걸리는 설정 값을 이 환경에서 읽는 물음이다.\",\"order\":2},{\"id\":\"16df0e16-daa4-42cc-bf15-e1ea13f4f8d8\",\"targetId\":\"b1cda8c4-d31d-4ae4-a4d9-4103a0ab3109\",\"reason\":\"같은 스토리지 판독에 쓰는 기준이고, 그쪽은 자원을 가르고 이쪽은 보장을 가른다.\",\"order\":3}],\"id\":\"decbab8d-9c98-45c6-b5b8-e6162e407299\",\"version\":3,\"updatedAt\":\"2026-09-09T07:05:15.788108Z\",\"purpose\":\"스토리지 설정을 바꿔 쓰기 지연이 줄었을 때 그것을 성능 항목으로만 적으면 어떤 실패에 노출됐는지가 기록에 남지 않는다. 개념 문서가 가장 위험한 상황으로 든 것은 거짓 완료다. 게스트가 WRITE 와 FLUSH 를 요청했는데 데이터는 호스트 RAM 에 있고 물리 스토리지에는 옛 데이터가 있는 상태인데도 게스트에게 FLUSH 완료라고 응답하는 경우다. 그러면 PostgreSQL 은 영속성이 확보되었다고 판단하고, 직후 호스트 전원이 나가면 RAM 의 데이터가 사라진다.\\n\\n이 기준은 빨라진 것을 되돌리라고 하지 않는다. 무엇이 빨라졌는지 옆에 어느 보장이 사라졌는지를 같이 적게 한다.\\n\\n이것을 완료의 네 단계를 설명하는 개념 기록 안의 각주로 두지 않고 따로 뺀 것은 이것이 필요해지는 때가 다르기 때문이다. 개념을 읽을 때가 아니라 설정을 고르고 성능 개선을 평가할 때 걸리는데, 각주로 두면 다음에 같은 판단을 하는 사람이 그때 이것을 찾지 못한다.\",\"rules\":[{\"id\":\"ce608949-b194-4296-87c2-b27d99d0e77a\",\"title\":\"완료를 네 단계로 갈라 어디까지 보장되는지 적는다\",\"body\":\"개념 문서는 write() 완료와 writeback 완료와 fsync/flush 완료와 전원 장애에도 안전한 durability 를 서로 다른 것으로 놓았다. write(fd, data, size) 가 성공했다는 것만으로 정전 이후 생존이 보장되지 않으므로, 필요한 시점에 fsync(fd) 를 불러 변경 내용을 필요한 영속성 경계까지 반영하도록 요청한다. 어떤 구성을 적을 때 이 네 단계 중 어디까지가 보장되는지를 함께 적는다.\",\"order\":0},{\"id\":\"5691fa59-ab48-47f9-958f-4dc686357b7b\",\"title\":\"빨라진 구성에서 사라진 경계를 이름으로 적는다\",\"body\":\"무엇이 몇 밀리초 줄었는지보다 먼저, 그 변경이 위 네 단계 중 어느 것을 건너뛰게 했는지를 적는다. 개념 문서가 그린 전달 경로는 게스트가 요구한 durability 에서 시작한다. 거기서 Guest Filesystem, Guest Block Layer, virtio, QEMU 와 backend, Host Storage, Device 로 이어진다. 이 경로는 어디에서 의미가 끊겨도 결과가 같아서, 끊긴 곳을 짚지 못하면 그 구성이 무엇을 보장하는지 다음 사람이 다시 조사해야 한다.\",\"order\":1},{\"id\":\"5bf0552a-e62f-44a1-9b16-e3277a93c3ce\",\"title\":\"거짓 완료를 성능 항목으로 분류하지 않는다\",\"body\":\"FLUSH 완료 응답을 받은 쪽은 그 데이터가 살아남는다고 가정하고 그 다음 동작을 한다. PostgreSQL 이라면 COMMIT 을 성공으로 처리한다. 이 가정이 틀린 상태는 느려지는 것이 아니라 응답한 내용이 사실과 다른 것이므로, 개념 문서는 이것을 durability contract 가 깨지는 correctness 문제로 적었다.\",\"order\":2},{\"id\":\"2fe9e3f5-d3aa-4b7b-bf87-3dcef9f4b5e9\",\"title\":\"캐시 모드를 이름만 보고 안전과 위험으로 가르지 않는다\",\"body\":\"개념 문서는 none 을 캐시 자체가 없음으로, writeback 을 무조건 위험으로 읽는 것이 부정확하다고 적었다. cache=writeback 을 골라도 정상적인 스택이라면 게스트의 fsync 와 FLUSH 는 virtio FLUSH 와 QEMU 와 백엔드를 지나 호스트의 sync 와 flush 로 전달된다. 필요한 완료 확인 뒤에 게스트로 완료가 돌아간다. 개념 문서는 정확한 표현을 이렇게 적었다 — writeback caching 에서는 volatile cache 가 존재할 수 있으므로 Guest 의 flush/fsync semantics 가 전체 backend/storage stack 에서 올바르게 보존되는지가 중요하다.\\n\\n그래서 이 기준은 어느 캐시 모드를 쓰라고 말하지 않는다. 개념 문서는 두 설정의 뜻과 각각의 오해를 갈라 놓기만 했고 어느 쪽으로 정했다고 적지 않았으며 감수한 비용도 적지 않았다. 이 호스트가 지금 무엇을 쓰고 있는지도 아직 읽지 않았다.\",\"order\":3},{\"id\":\"dfd9d46f-01b8-4087-8ca7-3e02338b6cec\",\"title\":\"DB 부하에서는 애플리케이션이 쓰는 영속성 규약을 함께 적는다\",\"body\":\"PostgreSQL 은 WAL 등의 durability protocol 을 사용하며 필요한 시점에 스토리지 동기화를 수행한다. 가상 머신의 스토리지 계층이 flush 와 fsync 의 의미를 제대로 보존하지 않으면 애플리케이션이 전제한 영속성과 실제 스토리지 동작이 어긋난다. 그래서 설정만 적지 않고 그 위에서 도는 애플리케이션이 무엇을 언제 동기화하는지도 같이 적는다.\",\"order\":4}],\"applyWhen\":[{\"id\":\"bc21dbed-fa00-466e-bb00-da681a5ab78a\",\"text\":\"스토리지 관련 설정이나 코드를 바꿔 쓰기 지연이나 커밋 지연이 줄었을 때\",\"order\":0},{\"id\":\"59e0851b-a164-4823-8e14-e072408dc690\",\"text\":\"QEMU 의 캐시 모드를 고르기 전\",\"order\":1},{\"id\":\"618b460d-7127-474c-b2b8-3c7c8b6b9905\",\"text\":\"Direct I/O 를 켜거나 끄기 전\",\"order\":2},{\"id\":\"9369fbca-5b4c-4248-a712-9e02a5c8b521\",\"text\":\"fsync() 호출을 더하거나 뺄 때\",\"order\":3},{\"id\":\"16483f28-078e-4c7e-9f23-e15e29e12013\",\"text\":\"파일시스템의 마운트 옵션을 고를 때\",\"order\":4}],\"exceptions\":[{\"id\":\"c3da7ac4-a54e-425e-b16e-b920bee2af9d\",\"text\":\"영속성을 실제로 포기해도 되는 데이터가 있다. 다시 만들 수 있는 캐시나 파생 파일이 그렇고, 그때는 빨라진 것이 정당한 선택이다. 이 기준이 요구하는 것은 포기를 막는 것이 아니라 무엇을 포기했는지 적게 하는 것이다.\",\"order\":0},{\"id\":\"23e28a7e-56f5-45c3-bbb7-9fa2e5eefbfd\",\"text\":\"cache=none 이나 Direct I/O 를 골랐다고 영속성이 확보되지도 않는다. 개념 문서는 Direct I/O 의 핵심이 페이지 캐시 우회이며 호스트 페이지 캐시를 우회하는 것이 즉시 durable media 반영을 뜻하지 않는다고 적었고, 그 뒤에 스토리지 컨트롤러나 장치가 휘발성 쓰기 캐시를 가질 수 있다고 덧붙였다.\",\"order\":1},{\"id\":\"d63ca81f-01f5-4e00-97bd-aa210039bd6a\",\"text\":\"개념 문서는 실제 ordering 과 durability semantics 가 더 복잡하다고 밝혔다. 그래서 이 기준으로 특정 설정이 안전하다는 결론을 내지 않는다. 실제 운영에서는 장치의 flush 와 FUA semantics, power-loss protection 여부도 함께 본다.\",\"order\":2},{\"id\":\"4785beb6-aaf5-4da4-b3bb-74b26767abff\",\"text\":\"이 저장소에는 영속성을 실제로 재 본 실험이 없다. 여기 적은 것은 개념 문서가 서술한 구분이고 이 호스트에서 확인된 동작이 아니다.\",\"order\":3}],\"examples\":[{\"id\":\"f852e749-8efb-4e87-a2c7-a80b2aeab4a4\",\"text\":\"write() 완료 : 애플리케이션 호출이 돌아온 상태\",\"order\":0},{\"id\":\"35af21be-d1b6-48a0-b859-7eefd6484593\",\"text\":\"writeback 완료 : 캐시에서 아래로 내려간 것까지다\",\"order\":1},{\"id\":\"d8436315-4d46-4f63-8633-3bd58801a135\",\"text\":\"fsync/flush 완료 : 여기서도 장치 쪽 휘발성 캐시가 뒤에 남을 수 있다\",\"order\":2},{\"id\":\"bacda41a-a62e-4c4f-b62e-8dce11613f39\",\"text\":\"전원 장애에도 안전한 상태 : 위 셋과 다른 단계로 센다\",\"order\":3},{\"id\":\"da5528b0-2714-42ad-acde-dd07aaece6a1\",\"text\":\"빨라졌다 : 어느 단계를 건너뛰었는지 옆에 적는다\",\"order\":4},{\"id\":\"d1576a6f-51c2-4430-8ef6-d4dcc05d19d6\",\"text\":\"포기해도 되는 데이터 : 다시 만들 수 있는 캐시 · 파생 파일\",\"order\":5},{\"id\":\"a78ebaed-4f24-4a65-9464-fac5c985237a\",\"text\":\"이 호스트에서 잰 영속성 측정 : x\",\"order\":6}],\"projectId\":\"8a638411-2b21-4202-8bf0-720180d77897\",\"topicId\":\"23314be2-7350-44f5-8989-575c3b40898f\",\"variantIds\":[],\"verifiedOn\":null},\"currentValidation\":null,\"latestPreview\":null,\"currentPublication\":null,\"dependencyRevision\":\"438f172f323f410f213e0b98bbeaa764\",\"nextAction\":\"VALIDATE\"},\"274c5636-33f6-4c19-a54b-d2a96af31289\":{\"document\":{\"kind\":\"REFERENCE\",\"title\":\"Guest 안에서 본 disk 로 backend 를 단정하지 않는다\",\"slug\":\"confirm-the-disk-backend-on-the-host\",\"summary\":\"백엔드는 게스트가 디스크로 쓰는 그 장치를 호스트 쪽에서 실제로 대 주는 것을 말한다. 게스트에 로그인해 lsblk 를 돌리면 /dev/vda 와 그 파티션이 나오고, 거기까지는 물리 머신과 같은 화면이다. 개념 문서는 그 이름이 호스트의 실제 SSD 를 뜻하지 않으며 아래에 qcow2 파일과 RAW 파일과 호스트 블록 장치 셋이 올 수 있다고 적었다. 이 기준은 용량과 성능과 영속성을 판단하기 전에 호스트 쪽에서 무엇에 붙어 있는지를 먼저 확정하게 한다.\",\"relations\":[{\"id\":\"cf477fb9-5985-440e-8140-d768b66d1423\",\"targetId\":\"892b6087-8961-486c-b4d9-d3dd46bc2605\",\"reason\":\"이 기준이 가르라고 하는 세 갈래를 그 글이 설명한다.\",\"order\":0},{\"id\":\"6460c28d-288e-4b67-b1c3-1d71005efee0\",\"targetId\":\"3b05c1f0-13e9-414a-97a6-2078fb4bab26\",\"reason\":\"게스트가 본 그 디스크 이름이 어디에서 나왔는지를 그 글이 설명한다.\",\"order\":1},{\"id\":\"63b438c7-e5f1-42c9-a7b5-56be9709655a\",\"targetId\":\"1f76d54b-bb17-4174-8128-0c2d71d4256f\",\"reason\":\"이 기준의 첫 확인을 이 환경에서 실제로 돌리는 물음이다.\",\"order\":2},{\"id\":\"2c8a217c-05f8-4c89-b519-830886db4b9c\",\"targetId\":\"ae69e2e1-1a3b-4ae3-ae2a-572af07b95d7\",\"reason\":\"백엔드가 파일로 확정됐을 때 형식과 크기를 읽는 물음이다.\",\"order\":3},{\"id\":\"094c4021-c616-4842-8253-80360d1e05f9\",\"targetId\":\"eead2379-94fe-463a-9e5e-06a01bf2105d\",\"reason\":\"그 파일이 놓인 파일시스템과 블록 장치까지 잇는 물음이다.\",\"order\":4},{\"id\":\"a655320c-025c-4722-b5c0-4c3ad2019c62\",\"targetId\":\"b1cda8c4-d31d-4ae4-a4d9-4103a0ab3109\",\"reason\":\"그 기준의 호스트 쪽 관측은 여기서 확정한 백엔드를 전제한다.\",\"order\":5}],\"id\":\"274c5636-33f6-4c19-a54b-d2a96af31289\",\"version\":3,\"updatedAt\":\"2026-09-09T07:04:09.036186Z\",\"purpose\":\"게스트 안의 이름을 호스트의 저장 구조로 읽으면 그 위에서 하는 판단이 모두 어긋난다. 개념 문서는 게스트 리눅스가 /dev/vda 를 하나의 블록 장치로 인식하지만 그것이 호스트의 실제 SSD 를 뜻하지는 않는다고 적었다. 같은 대상을 호스트 관점에서 보면 파일 하나이고 게스트 관점에서 보면 디스크이며, 둘 다 맞다.\\n\\n무엇 위에서 재고 있는지가 정해지지 않으면 용량 판독과 성능 판독이 서로 다른 대상을 가리킨다. 게스트가 100 GB 를 본다는 사실과 호스트가 그만큼 쓰고 있다는 사실은 다른 이야기이고, 형식이 무엇인지에 따라 확인할 항목도 달라진다.\\n\\n개념 문서가 실제 서버에서 확인하라고 적어 둔 물음 가운데 넷이 이 확정 위에서 돌아간다. 백엔드가 무엇인지, 형식이 무엇인지, 크기가 얼마나 벌어져 있는지, 그 아래가 어느 장치인지다. 넷마다 같은 절차를 되풀이해 적지 않고 한 편으로 두었으므로 각 물음이 여기를 가리킨다.\",\"rules\":[{\"id\":\"49ceed5f-d89b-4c02-ad14-b96deafcd5ad\",\"title\":\"게스트의 장치 이름을 호스트의 저장 장치로 옮겨 읽지 않는다\",\"body\":\"물리 머신에서는 /dev/sda 나 /dev/nvme0n1 이 보이고 virtio-blk 를 쓰는 가상 머신에서는 /dev/vda 나 /dev/vdb 가 보인다. 이름이 그렇게 갈릴 뿐 게스트 쪽 화면은 두 경우 모두 블록 장치 하나와 그 파티션으로 보인다. 게스트에서 얻은 이름은 게스트가 인식한 것까지 말해 주고 그 아래 구조는 말해 주지 않는다.\",\"order\":0},{\"id\":\"f6fe1235-c2c9-4573-b6da-f9565a2150a7\",\"title\":\"Target 과 Source 를 이어 붙인 뒤에 판독을 시작한다\",\"body\":\"게스트에서 lsblk 로 보이는 블록 장치와 파티션을 적고, 호스트에서 virsh domblklist <VM_NAME> 로 Target 과 Source 를 적는다. 개념 문서가 든 예시에서는 Target vda 에 Source /var/lib/libvirt/images/vm1.qcow2 가 붙어 있었다. 이 대응이 있어야 게스트의 /dev/vda 에서 virtio-blk 와 QEMU 를 지나 그 파일까지 이어진다. 그 경로는 개념 문서가 예시로 붙여 둔 이름이고 이 호스트에서 읽은 값이 아니다 — 두 명령을 이 호스트에서 돌린 출력은 아직 없다.\",\"order\":1},{\"id\":\"b574e792-e437-4123-a0c2-f1fb35f08df5\",\"title\":\"Source 가 파일인지 호스트 블록 장치인지 확정한다\",\"body\":\"개념 문서는 백엔드가 반드시 파일일 필요는 없다고 적고 게스트의 /dev/vda 아래에 호스트의 /dev/nvme0n1p3 이 오는 구성을 들었다. 파일이면 qemu-img info 에 그 경로를 주어 형식을 읽고, 호스트 블록 장치면 qcow2 쪽 확인 항목은 애초에 걸리지 않는다.\",\"order\":2},{\"id\":\"3367631f-fbdf-4e76-83b3-d19f50b256ed\",\"title\":\"용량은 virtual size 와 실제 할당을 갈라 읽는다\",\"body\":\"qcow2 는 가상 디스크 크기와 실제 호스트 할당량이 다를 수 있다. 개념 문서가 든 그림은 게스트가 보는 공간 100 GB 에 호스트 실제 할당 3GB 였다. 게스트가 데이터를 기록하면서 Actual 이 1GB 에서 10GB 로, 다시 40GB 로 늘 수 있다는 예도 함께 보였다. qemu-img info 를 볼 때 virtual size 와 실제 allocation 을 구분해서 봐야 한다.\",\"order\":3},{\"id\":\"a7a05e15-2a12-47e9-a22c-e619833152de\",\"title\":\"파일이면 그 파일이 놓인 호스트 파일시스템과 블록 장치까지 적는다\",\"body\":\"백엔드가 qcow2 파일이면 QEMU 는 결국 호스트 리눅스에 파일 입출력을 요청한다. 그 요청은 Host Filesystem 과 Host Block Layer 와 NVMe Driver 를 지나 Physical NVMe 로 내려간다. 게스트의 스토리지 스택 아래에 호스트의 스토리지 스택이 한 번 더 있으므로, 어느 파일시스템과 어느 블록 장치 위에 있는지를 lsblk 로 함께 적는다.\",\"order\":4}],\"applyWhen\":[{\"id\":\"5ab911ff-3028-4179-bf39-36aaa6996aec\",\"text\":\"가상 머신의 디스크 성능이나 용량이나 영속성을 판단하기 전\",\"order\":0},{\"id\":\"71fe22a8-4d70-4496-9520-60a47aa02888\",\"text\":\"게스트 안에서 잰 스토리지 수치를 해석하기 전\",\"order\":1},{\"id\":\"644244c7-bb57-445f-b1ac-716ee87a599e\",\"text\":\"저장 공간 계획을 세우거나 이미지를 옮기기 전\",\"order\":2},{\"id\":\"8db9ec55-c90e-41ad-b1c9-7b61f8c8ccfc\",\"text\":\"가상 머신 여러 대가 같은 물리 장치를 쓰는지 확인할 때\",\"order\":3}],\"exceptions\":[{\"id\":\"5451ba2d-e620-45a6-b5c2-a4e95b76de88\",\"text\":\"백엔드가 호스트 블록 장치면 qcow2 쪽 확인은 걸리지 않는다. 가상 크기와 실제 할당량의 차이도, 매핑과 메타데이터 처리도 그 구성에는 없다.\",\"order\":0},{\"id\":\"13fbe12f-79b5-47c4-9efd-86ac00af02f2\",\"text\":\"백엔드를 확정해도 성능은 예측되지 않는다. 개념 문서는 RAW 는 무조건 빠르고 qcow2 는 무조건 느리다는 식으로 일반화하지 말라고 못박고, 실제 성능이 캐시 모드와 스토리지 백엔드와 부하 패턴과 큐 깊이와 스냅숏 체인과 그 아래 파일시스템과 물리 장치에 영향을 받는다고 들었다. 이 기준은 무엇 위에서 재고 있는지까지만 확정한다.\",\"order\":1},{\"id\":\"1d5b3c16-ec4c-4776-9cef-b720ddb35c4e\",\"text\":\"이 저장소에는 이 절차를 실제로 돌린 출력이 없다. 여기 적은 것은 개념 문서가 서술한 확인 순서이고 이 호스트에서 나온 값이 아니다.\",\"order\":2}],\"examples\":[{\"id\":\"4b635b7e-ca4d-47d4-a7a6-395b6c9ea381\",\"text\":\"게스트에서 보이는 것 : /dev/vda 와 그 파티션\",\"order\":0},{\"id\":\"4ed0810e-095e-4afb-9d44-d2ca72881f5f\",\"text\":\"호스트에서 이어 붙일 것 : Target 과 Source 의 대응\",\"order\":1},{\"id\":\"944df503-f646-4cb5-9418-4955437eea3d\",\"text\":\"Source 의 세 갈래 : qcow2 파일 · RAW 파일 · 호스트 블록 장치\",\"order\":2},{\"id\":\"33ac8513-1da6-44ab-815a-911bfab61a8d\",\"text\":\"용량 : virtual size 와 실제 allocation 을 따로 적는다\",\"order\":3},{\"id\":\"c2e2ccce-e88f-4b3b-af84-a28dfd580321\",\"text\":\"파일이면 더 볼 것 : 그 파일이 올라간 파일시스템과 블록 장치\",\"order\":4},{\"id\":\"c58d2908-a3bc-4377-a5ed-a070fe674071\",\"text\":\"이 호스트에서 돌린 출력 : x\",\"order\":5}],\"projectId\":\"8a638411-2b21-4202-8bf0-720180d77897\",\"topicId\":\"23314be2-7350-44f5-8989-575c3b40898f\",\"variantIds\":[],\"verifiedOn\":null},\"currentValidation\":null,\"latestPreview\":null,\"currentPublication\":null,\"dependencyRevision\":\"309fae91b7b60c2ae317f258e8be2c77\",\"nextAction\":\"VALIDATE\"},\"1f76d54b-bb17-4174-8128-0c2d71d4256f\":{\"document\":{\"kind\":\"QUESTION\",\"title\":\"이 호스트의 /dev/vda 는 어떤 backend 에 붙어 있는가\",\"slug\":\"vm-disk-backend-mapping\",\"summary\":\"게스트 안에서 보이는 디스크 이름 하나는 서로 다른 세 가지 호스트 구성 위에서 똑같이 나온다. qcow2 파일일 수도 있고, RAW 파일일 수도 있고, Host block device 를 그대로 붙인 것일 수도 있다. 그래서 이 물음은 성능이나 용량을 재기 전에 이 호스트의 가상 머신이 그중 어느 구성인지부터 출력으로 확정한다. 여기서 갈린 결과가 이 주제의 다음 물음 둘이 이 환경에 적용되는지를 정한다.\",\"relations\":[{\"id\":\"018df3b3-d650-447f-b2d7-a60e5eb95b97\",\"targetId\":\"892b6087-8961-486c-b4d9-d3dd46bc2605\",\"reason\":\"이 물음이 가르려는 세 갈래를 그 글이 설명한다.\",\"order\":0},{\"id\":\"5b51776b-55ce-422d-97cb-f33c6d64bbc7\",\"targetId\":\"3b05c1f0-13e9-414a-97a6-2078fb4bab26\",\"reason\":\"게스트가 보는 그 이름이 어디에서 나왔는지를 그 글이 설명한다.\",\"order\":1},{\"id\":\"79b479c8-a376-408b-b263-fba141bf3038\",\"targetId\":\"274c5636-33f6-4c19-a54b-d2a96af31289\",\"reason\":\"이 확인을 반복 적용할 기준으로 편 글이고, 이 물음이 그 기준의 첫 항목을 이 호스트에서 채운다.\",\"order\":2},{\"id\":\"ffb1c1f6-08ed-42ea-89b4-f339f0e5879e\",\"targetId\":\"ae69e2e1-1a3b-4ae3-ae2a-572af07b95d7\",\"reason\":\"Source 가 파일로 나왔을 때 이어지는 물음이다.\",\"order\":3},{\"id\":\"5e1c6bc0-d0d1-404a-aaf0-7cdf36a2f3c2\",\"targetId\":\"eead2379-94fe-463a-9e5e-06a01bf2105d\",\"reason\":\"같은 조건에서 그 파일 아래를 잇는 물음이다.\",\"order\":4}],\"id\":\"1f76d54b-bb17-4174-8128-0c2d71d4256f\",\"version\":3,\"updatedAt\":\"2026-09-09T07:05:23.946523Z\",\"facts\":[{\"id\":\"4c4690ff-99d2-43b5-ac5f-ca330b46a2f3\",\"text\":\"§135 는 virtio-blk 를 쓰는 가상 머신에서 /dev/vda 나 /dev/vdb 같은 이름이 흔히 보인다고 적었다. Guest Linux 는 그 이름을 하나의 block device 로 인식하지만, 그것이 호스트의 실제 SSD 를 뜻하지는 않는다.\",\"order\":0},{\"id\":\"c4c0cc5e-002d-428b-b847-84eb545c34ea\",\"text\":\"backend 가 반드시 파일일 필요도 없다. §145 는 그 아래에 qcow2 파일과 RAW 파일과 Host block device 셋이 올 수 있다고 갈라 적고, 게스트에 그 이름이 있다는 정보만으로는 backend 구조를 알 수 없다고 못박았다.\",\"order\":1},{\"id\":\"1ab4130b-a4e7-46a7-ad61-f95fcc61fb49\",\"text\":\"§140 은 가상 머신 경계를 넘으면 QEMU 가 등장한다고 그렸다. QEMU 의 virtio-blk Device Model 과 Block Backend 가 게스트의 virtual I/O 를 호스트 backend 에 연결한다.\",\"order\":2},{\"id\":\"1a204c31-a4b6-461c-a4bd-6e652f60e2b1\",\"text\":\"확인 방법으로는 §146 과 §175 OQ-1 이 같은 둘을 든다. 게스트에서는 lsblk 를 돌리고, 호스트에서는 virsh domblklist <VM_NAME> 을 돌린다.\",\"order\":3},{\"id\":\"0a192018-2f32-4c1a-970c-e443010b32ee\",\"text\":\"§146 의 예시 출력은 Target vda 에 Source /var/lib/libvirt/images/vm1.qcow2 가 붙은 한 행이었다. 그 대응이 나오면 게스트의 /dev/vda 에서 virtio-blk 와 QEMU 를 지나 그 파일까지 이어진다. 그 경로는 대응이 어떻게 읽히는지 보이려고 든 예시이지 이 호스트에서 읽은 값이 아니다.\",\"order\":4},{\"id\":\"98cf1c4e-cb58-41d4-b7a7-dbd8b53b819a\",\"text\":\"이 호스트의 가상 머신이 어떤 Source 에 붙어 있는지를 적은 기록은 개념 문서에 없다.\",\"order\":5}],\"assumptions\":[{\"id\":\"6c53d065-630b-424c-a06d-31ee1ca42cd2\",\"text\":\"호스트와 각 게스트에 붙어 명령을 돌릴 수 있다고 본다.\",\"order\":0},{\"id\":\"32025f0a-e84b-4266-9323-d50466417830\",\"text\":\"이 환경의 가상 머신이 libvirt 로 정의되어 있어서 virsh 가 그 가상 머신을 안다고 전제한다. §146 과 §175 OQ-1 이 확인 방법으로 virsh 명령을 든 것이 근거이고, 이 호스트에서 확인하지는 않았다.\",\"order\":1},{\"id\":\"5236a696-9160-49ef-898f-6d55cf964c84\",\"text\":\"확인하는 동안 디스크 구성이 바뀌지 않는다고 본다.\",\"order\":2}],\"unknowns\":[{\"id\":\"89a21edb-0a2f-4e9d-8f78-267ab6edc831\",\"text\":\"이 호스트의 각 가상 머신에 디스크가 몇 개 붙어 있는지.\",\"order\":0},{\"id\":\"3054cd80-4cc8-48a2-b0a5-69e04b03284f\",\"text\":\"각 Target 의 Source 가 무엇인지.\",\"order\":1},{\"id\":\"b1e92af9-5557-456f-9148-60baf75659cf\",\"text\":\"그 Source 가 파일인지 Host block device 인지.\",\"order\":2}],\"constraints\":[{\"id\":\"a91eec62-9e17-42a5-b083-f559223227df\",\"text\":\"이 물음은 backend 가 무엇인지까지 답한다. 형식과 크기는 다음 물음이 받고, 성능은 여기서 판정하지 않는다.\",\"order\":0},{\"id\":\"f221e935-b2ee-4290-9f73-6efea0a5f901\",\"text\":\"게스트 안에서 본 이름은 답이 되지 않는다. §145 가 그 추론을 명시적으로 막았기 때문이다.\",\"order\":1},{\"id\":\"f9808448-5fa3-4615-b3e5-23ff654522a8\",\"text\":\"이 호스트에서 얻은 출력이 없으므로 다른 장비의 디스크 구성을 근거로 삼지 않는다.\",\"order\":2}],\"options\":[{\"id\":\"4c30d848-185e-4405-9fca-17c9431b686b\",\"title\":\"게스트와 호스트를 한 번씩 돌고 한 표로 남긴다\",\"description\":\"§146 이 든 두 명령을 그대로 돌린다. 각 게스트에서 lsblk 로 보이는 block device 와 파티션을 받고, 호스트에서 가상 머신마다 virsh domblklist <VM_NAME> 을 돌려 Target 과 Source 를 받는다. 두 출력이 있으면 게스트가 보는 이름과 호스트의 실제 대상이 가상 머신마다 한 행으로 이어진다.\\n\\n로그인할 수 없는 가상 머신이 있으면 그쪽은 호스트 출력만 얻고, 게스트가 그 디스크를 어떤 파티션으로 나눠 쓰는지는 비게 된다.\",\"order\":0},{\"id\":\"29f76039-a958-47e6-9c8b-96cfc4726678\",\"title\":\"호스트 쪽 출력만 먼저 받는다\",\"description\":\"virsh domblklist 는 호스트에서만 돌아가고 Target 과 Source 를 한 번에 주기 때문에, 가상 머신에 로그인하지 않아도 backend 경로가 확정된다. 이어지는 두 물음이 요구하는 이미지 경로도 바로 얻는다.\\n\\n대신 게스트가 그 디스크를 어떤 이름과 파티션으로 보고 있는지가 빠진다. 나중에 게스트 안에서 잰 스토리지 수치를 이 표에 붙이려면 그때 대응을 다시 확인해야 한다.\",\"order\":1},{\"id\":\"71b5ce8d-d0fc-4129-b970-6da953b88961\",\"title\":\"libvirt 정의 전문을 받아 disk 요소를 읽는다 — 제외\",\"description\":\"virsh dumpxml 은 disk 요소에 source 와 driver 를 함께 담고 있어서 backend 경로와 cache 설정을 한 번에 볼 수 있다.\\n\\n§175 는 dumpxml 을 cache mode 를 확인하는 항목에 두었고, 연결 확인에는 §146 과 같은 domblklist 를 들었다. 여기서 dumpxml 을 쓰면 한 출력으로 두 물음이 닫히게 되어 어느 확인이 무엇을 근거로 끝났는지가 흐려진다. cache 값은 그 물음이 받는다.\",\"order\":2}],\"nextValidation\":\"1. 각 게스트에서 lsblk 를 돌려 보이는 block device 와 파티션을 적는다.\\n2. 호스트에서 가상 머신마다 virsh domblklist <VM_NAME> 을 돌려 Target 과 Source 를 적는다.\\n3. 가상 머신마다 한 행으로 정리하고 두 명령의 출력을 그대로 증거로 둔다.\\n\\n닫는 조건 : 가상 머신마다 Target 과 Source 의 대응이 출력으로 확정되면 닫는다. Source 가 파일이면 형식과 실제 사용량을 묻는 물음과 그 파일이 올라간 Host block device 를 묻는 물음으로 이어진다. Host block device 면 §145 가 적은 그 갈래가 이 환경이라고 적고, qcow2 를 전제한 후속 물음 둘은 이 호스트에 적용되지 않는다고 함께 적는다.\",\"resolution\":null,\"projectId\":\"8a638411-2b21-4202-8bf0-720180d77897\",\"questionStatus\":\"OPEN\",\"topicId\":\"23314be2-7350-44f5-8989-575c3b40898f\",\"variantIds\":[]},\"currentValidation\":null,\"latestPreview\":null,\"currentPublication\":null,\"dependencyRevision\":\"d67004571fa438399642e11b5fc56196\",\"nextAction\":\"VALIDATE\"},\"ae69e2e1-1a3b-4ae3-ae2a-572af07b95d7\":{\"document\":{\"kind\":\"QUESTION\",\"title\":\"이 disk image 는 qcow2 인가 RAW 인가, 그리고 Guest 가 보는 크기와 Host 가 실제로 쓰는 크기는 얼마나 다른가\",\"slug\":\"disk-image-format-and-actual-host-usage\",\"summary\":\"게스트가 100 GB 짜리 디스크를 보고 있어도 호스트에서 그 파일이 차지하는 공간은 훨씬 작을 수 있다. 개념 문서는 그 차이를 예시 숫자로만 들어 두고, 세 명령이 각각 다른 의미의 크기를 보여 준다고 적었다. 이 물음은 이 호스트의 이미지마다 형식과 세 크기를 한 표로 적는 데서 끝난다. 형식과 세 값이 채워지면 저장 공간 계획의 근거가 생기고, 그 뒤의 성능 판단은 여기서 하지 않는다.\",\"relations\":[{\"id\":\"c8797b86-2e5a-4358-a8f4-598e861c963f\",\"targetId\":\"892b6087-8961-486c-b4d9-d3dd46bc2605\",\"reason\":\"형식이 어디에서 갈리고 각 형식이 무엇을 더 하는지를 그 글이 설명한다.\",\"order\":0},{\"id\":\"b7d7e302-fc22-4f36-a539-32b6b018ad70\",\"targetId\":\"274c5636-33f6-4c19-a54b-d2a96af31289\",\"reason\":\"이 물음이 그 기준의 형식 확인과 용량 확인 항목을 이 호스트에서 채운다.\",\"order\":1},{\"id\":\"f926f284-3592-43a6-b3d1-ab5dbbd3e3f3\",\"targetId\":\"1f76d54b-bb17-4174-8128-0c2d71d4256f\",\"reason\":\"명령을 돌릴 경로를 그 물음이 확정한다.\",\"order\":2},{\"id\":\"e47f0dfb-22de-45d5-8fdd-584f82c75b46\",\"targetId\":\"eead2379-94fe-463a-9e5e-06a01bf2105d\",\"reason\":\"같은 경로를 받아 그 파일 아래를 잇는 물음이다.\",\"order\":3}],\"id\":\"ae69e2e1-1a3b-4ae3-ae2a-572af07b95d7\",\"version\":3,\"updatedAt\":\"2026-09-09T07:05:31.972597Z\",\"facts\":[{\"id\":\"f4cb4ec1-be50-4ac5-8f6c-700f6466238b\",\"text\":\"개념 문서는 같은 대상이 호스트에서는 파일 하나이고 게스트에서는 /dev/vda 라는 디스크이며 둘 다 맞다고 놓았다.\",\"order\":0},{\"id\":\"ff545a19-851d-4f3c-9531-1818e92434f2\",\"text\":\"qcow2 는 가상 디스크 크기와 실제 호스트 할당량이 다를 수 있다. 개념 문서가 든 그림은 게스트가 보는 공간 100 GB 에 호스트 실제 할당 3GB 였다.\",\"order\":1},{\"id\":\"b9dc54d8-28bc-41ef-984e-e71e164be303\",\"text\":\"게스트가 데이터를 기록하면서 실제 사용량이 늘 수 있다. 개념 문서는 Virtual 100GB 를 유지한 채 Actual 이 1GB 에서 10GB 로, 다시 40GB 로 가는 예를 보였다.\",\"order\":2},{\"id\":\"c8f422c9-addf-415c-af3c-458365715d12\",\"text\":\"개념 문서는 확인 명령으로 qemu-img info vm1.qcow2 를 들고 virtual size 와 실제 allocation 을 구분해서 봐야 한다고 적었다.\",\"order\":3},{\"id\":\"f4ac541e-baeb-410d-9557-f5083a32c4f3\",\"text\":\"RAW 는 qcow2 보다 구조가 단순하다. qcow2 는 Copy-on-Write 와 sparse allocation 과 스냅숏 등에 유리하지만 매핑과 메타데이터 처리가 있고, RAW 는 상대적으로 직접적인 offset 대응으로 간다.\",\"order\":4},{\"id\":\"46b8aa36-4627-44ac-bf6e-a54d87b18c40\",\"text\":\"형식만으로 성능을 가르지 말라고 개념 문서가 못박았다. RAW 는 무조건 빠르고 qcow2 는 무조건 느리다는 일반화를 막고, 실제 성능이 캐시 모드와 스토리지 백엔드와 부하 패턴과 큐 깊이와 스냅숏 체인과 그 아래 파일시스템과 물리 장치에 영향을 받는다고 들었다.\",\"order\":5},{\"id\":\"bedb2050-e84d-4604-a4c0-8e44e498da60\",\"text\":\"개념 문서가 이 확인에 적어 둔 명령은 셋이다. 세 명령이 보여주는 의미가 서로 다를 수 있으므로 비교하라고 했다.\\n형식과 크기 : qemu-img info\\n실제 점유량 : du -h\\n파일 크기 : ls -lh\",\"order\":6},{\"id\":\"12958f33-978d-4346-99a9-030021d94a65\",\"text\":\"이 호스트의 이미지 형식도 크기도 적힌 기록이 없다.\",\"order\":7}],\"assumptions\":[{\"id\":\"08680940-38f9-4040-9980-0e79da007fdc\",\"text\":\"이미지 경로가 앞선 물음에서 확정된다고 본다. 경로가 없으면 세 명령을 어디에 돌릴지 정해지지 않는다.\",\"order\":0},{\"id\":\"2c7d0cc2-99c0-4486-8466-c76e6d78b005\",\"text\":\"백엔드가 파일이라고 전제한다. 호스트 블록 장치로 나오면 이 물음 자체가 이 호스트에 적용되지 않는다.\",\"order\":1},{\"id\":\"51412721-c956-45c2-9ff2-4f3031d52949\",\"text\":\"세 명령을 같은 시점에 돌린다고 본다. 실제 사용량은 게스트가 기록하면서 늘 수 있어서, 시점이 벌어지면 한 표에 서로 다른 시각의 값이 들어간다.\",\"order\":2}],\"unknowns\":[{\"id\":\"1a6a6b3c-ad81-47ee-bc20-120a6026b0fc\",\"text\":\"가상 머신마다 디스크 이미지가 qcow2 인지 RAW 인지.\",\"order\":0},{\"id\":\"5a6f0119-ef3f-4f41-9270-7b79f5499eb5\",\"text\":\"qemu-img info 가 보여 주는 virtual size 와 disk size 가 각각 얼마인지.\",\"order\":1},{\"id\":\"30cdb660-d283-412d-8846-ebe7d619b3cc\",\"text\":\"du -h 의 실제 점유량과 ls -lh 의 파일 크기가 그 값들과 얼마나 벌어져 있는지.\",\"order\":2}],\"constraints\":[{\"id\":\"b4692931-741c-44bd-a0e9-fd688800e595\",\"text\":\"성능 결론을 여기서 내지 않는다. 형식만으로 일반화하지 말라고 개념 문서가 적었고, 성능은 부하와 지연을 재는 다른 물음들이 받는다.\",\"order\":0},{\"id\":\"8105d78e-9b27-41dd-8230-1ab64c5af695\",\"text\":\"한 시점의 세 값만 적는다. 그 차이가 시간에 따라 어떻게 자라는지는 별도 측정으로 넘긴다.\",\"order\":1},{\"id\":\"23c8cfc4-140e-4617-96ad-11b1f13ceb56\",\"text\":\"앞선 물음이 Source 를 확정하기 전에는 이 확인을 시작할 수 없다.\",\"order\":2},{\"id\":\"38b799c6-bbf9-4273-a061-41aeaf3362c6\",\"text\":\"개념 문서는 형식을 묻는 물음과 세 크기를 묻는 물음을 따로 적었는데 여기서는 하나로 받았다. 형식은 qemu-img info 출력의 첫 줄이고 세 값을 견주는 일의 전제라 같은 출력으로 함께 닫힌다. 같은 측정으로 닫히는 물음을 두 편으로 두지 않는다.\",\"order\":3}],\"options\":[{\"id\":\"712ad7e7-a147-413b-932f-a58ee6400443\",\"title\":\"이미지마다 세 명령을 이어서 돌리고 한 표로 남긴다\",\"description\":\"같은 경로에 qemu-img info 와 du -h 와 ls -lh 를 이어서 돌린다. 형식과 virtual size 와 disk size 는 첫 출력에서 읽고 그 옆에 나머지 두 값을 붙이면, 세 값의 차이가 한 행에서 보인다. 세 명령이 붙어 돌아가므로 시점 차이도 거의 없다.\\n\\n이미지가 많으면 출력이 길어지므로 가상 머신 이름과 경로를 행 머리에 둔다.\",\"order\":0},{\"id\":\"35a9d8f4-4277-4f5f-8a36-b5ee346bab97\",\"title\":\"형식만 먼저 읽고 크기 비교는 뒤로 미룬다\",\"description\":\"qemu-img info 한 번이면 file format 이 나오므로 qcow2 인지 RAW 인지가 먼저 갈리고, RAW 로 나오면 확인할 항목이 줄어든다. 개념 문서가 두 물음으로 나눠 적은 모양이 이 순서다.\\n\\n대신 같은 경로에 두 번 접근하게 되고, 두 시점 사이에 게스트가 기록하면 크기 값이 형식을 읽은 시점의 상태와 어긋난다.\",\"order\":1},{\"id\":\"3fae0cc7-b4b3-4eb6-a570-99b162ab9356\",\"title\":\"파일시스템 사용량 합계로 대신한다 — 제외\",\"description\":\"이미지가 놓인 파일시스템을 df -h 로 보면 파일을 하나씩 재지 않아도 전체 점유가 나온다.\\n\\n그 값에는 이미지가 아닌 파일도 함께 들어가 있어서 이미지 하나가 얼마를 쓰는지 갈라 주지 않는다. 개념 문서가 비교하라고 한 것은 같은 이미지에 대한 세 값이고, 합계로는 virtual size 와의 차이도 나오지 않는다.\",\"order\":2}],\"nextValidation\":\"1. 앞선 물음이 확정한 Source 경로마다 qemu-img info 를 돌려 file format 과 virtual size 와 disk size 를 적는다.\\n2. 같은 경로에 du -h 와 ls -lh 를 돌려 두 값을 나란히 적는다.\\n3. 가상 머신이 여럿이면 이미지마다 한 행으로 한 표에 모으고 세 명령의 출력을 그대로 증거로 둔다.\\n\\n닫는 조건 : 이미지마다 형식과 세 크기가 한 표로 적히면 닫는다. qcow2 이고 세 값이 벌어져 있으면 개념 문서가 예시로만 든 차이가 이 호스트에서 얼마인지 확정한 것이고, 그 차이가 자라는 모습은 별도 측정으로 넘긴다. RAW 이면 그 갈래가 이 환경이라고 적고 sparse 인지 아닌지만 세 값의 차이로 판정한다.\",\"resolution\":null,\"projectId\":\"8a638411-2b21-4202-8bf0-720180d77897\",\"questionStatus\":\"OPEN\",\"topicId\":\"23314be2-7350-44f5-8989-575c3b40898f\",\"variantIds\":[]},\"currentValidation\":null,\"latestPreview\":null,\"currentPublication\":null,\"dependencyRevision\":\"20904956459257ea7f23e6a3aa924ce5\",\"nextAction\":\"VALIDATE\"},\"480f86ac-86fb-4290-9bf5-584763aa1363\":{\"document\":{\"kind\":\"QUESTION\",\"title\":\"이 VM 들의 QEMU disk cache mode 는 무엇인가\",\"slug\":\"qemu-disk-cache-mode\",\"summary\":\"이름만 보고 none 을 캐시가 아예 없는 구성으로, writeback 을 무조건 위험한 구성으로 읽으면 부정확하다고 개념 문서가 못박았다. 다만 그 문서는 두 설정이 Host Page Cache 와 완료·flush 의 뜻을 어떻게 바꾸는지까지만 갈라 놓았고, 이 호스트의 가상 머신들이 지금 어느 값으로 돌고 있는지는 적지 않았다. 그래서 이 물음은 값을 고르지 않는다. 지금 걸려 있는 값을 읽고, 그 값이 이 환경에서 무엇을 뜻하는지까지 적는다.\",\"relations\":[{\"id\":\"6f0b3904-f1f3-42b0-b4c5-13a2700cdfca\",\"targetId\":\"8fafaeea-8746-4537-87a4-679a3a91aa4c\",\"reason\":\"cache 값이 바꾸는 것이 그 네 가지 가운데 어디까지인지를 그 개념이 가른다.\",\"order\":0},{\"id\":\"cdfdb78a-ac42-418c-a890-dcef4443eb37\",\"targetId\":\"decbab8d-9c98-45c6-b5b8-e6162e407299\",\"reason\":\"읽은 값을 근거로 설정을 바꾸려 할 때 그 기준을 지나야 한다.\",\"order\":1},{\"id\":\"3153c446-124a-48e1-9e46-eb1ab8e0ec78\",\"targetId\":\"1f76d54b-bb17-4174-8128-0c2d71d4256f\",\"reason\":\"그 물음이 확정한 Source 를 같은 행에 두어야 이 값이 어떤 backend 위의 값인지 읽힌다.\",\"order\":2}],\"id\":\"480f86ac-86fb-4290-9bf5-584763aa1363\",\"version\":2,\"updatedAt\":\"2026-09-09T07:02:49.251918Z\",\"facts\":[{\"id\":\"d6a9a6ce-fd51-4426-8f08-4074edb87b17\",\"text\":\"§153 은 QEMU/libvirt 디스크에서 대표적으로 볼 수 있는 설정으로 cache=none 과 cache=writeback 둘을 들었다. 이름만 보고 none = cache 자체가 없음 · writeback = 무조건 위험으로 해석하면 부정확하다. 핵심은 QEMU 가 Host Page Cache 와 write completion/flush semantics 를 어떤 방식으로 사용할 것인가라고 적었다.\",\"order\":0},{\"id\":\"079aaa7a-57d5-4a84-b87e-d08e33cc0bce\",\"text\":\"§154 는 cache=none 을 개념적으로 Host Page Cache 를 우회하는 방향의 I/O 구성으로 놓았다. 이중 caching 은 줄일 수 있지만, Host Page Cache 우회가 무조건 즉시 durable media 반영을 뜻하지는 않는다.\",\"order\":1},{\"id\":\"0904a276-e523-45a0-823f-31bfdf3e9944\",\"text\":\"§155 는 cache=writeback 을 Host Page Cache 를 사용할 수 있는 구성으로 놓았다. 일반 write 는 Host RAM 에서 빠르게 completion 될 수 있고, 나중에 Host RAM 에서 Storage 로 내려간다. 이 설정이어도 Guest 의 fsync()/FLUSH 가 무시되지는 않는다. 정상적인 stack 이라면 durability 요구가 Guest fsync/FLUSH 에서 virtio FLUSH, QEMU/backend, Host sync/flush, Storage 를 지난다. 거기서 필요한 완료 확인을 받아 Guest completion 까지 전달되어야 한다고 적었다.\",\"order\":2},{\"id\":\"e13c4336-d681-43ea-98e3-5bd0d3f31202\",\"text\":\"§156 은 그래서 정확한 표현을 이렇게 적었다. writeback caching 에서는 volatile cache 가 존재할 수 있으므로, Guest 의 flush/fsync semantics 가 전체 backend/storage stack 에서 올바르게 보존되는지가 중요하다.\",\"order\":3},{\"id\":\"1491adc4-3304-4bbf-8bbb-009c894e854e\",\"text\":\"§147 은 Guest buffered I/O 와 Host file-backed disk 와 Host Page Cache 를 함께 쓰는 구성을 들었다. 그러면 같은 데이터가 Guest RAM 과 Host RAM 양쪽에 cache 될 수 있다고 밝혔다.\",\"order\":4},{\"id\":\"7fdf7326-543c-4c50-b191-75d6175dd29e\",\"text\":\"§175 OQ-4 는 확인 방법으로 virsh dumpxml <VM_NAME> 을 들고, disk driver 설정의 cache 관련 값을 확인하라고 했다.\",\"order\":5},{\"id\":\"8768b5d2-86dd-4cb5-9e09-be2aef39782d\",\"text\":\"이 호스트에 어느 값이 걸려 있는지도, 어느 값을 쓰기로 정했다는 기록도 개념 문서에 없다.\",\"order\":6}],\"assumptions\":[{\"id\":\"c813f733-89cb-47e7-9079-6a73f180fb76\",\"text\":\"호스트에 붙어 가상 머신마다 virsh dumpxml <VM_NAME> 을 돌릴 수 있다고 본다.\",\"order\":0},{\"id\":\"712a346b-a4aa-4258-82e4-8c61966ecab3\",\"text\":\"받아 낸 XML 이 지금 돌고 있는 가상 머신에 실제로 적용된 설정과 같다고 전제한다. 정의만 고치고 다시 시작하지 않은 가상 머신이 있으면 이 전제가 깨진다.\",\"order\":1},{\"id\":\"fb5b1075-ab64-45c8-a546-ae69a39453df\",\"text\":\"가상 머신에 디스크가 여럿이면 디스크마다 값이 다를 수 있어서 요소 단위로 읽는다.\",\"order\":2},{\"id\":\"2aecfe6f-274e-4f24-aa95-106ea7a58c67\",\"text\":\"값이 적혀 있지 않은 디스크에도 실제로 적용되는 동작이 있다고 전제한다. 개념 문서는 값이 없을 때 무엇이 적용되는지를 다루지 않았다.\",\"order\":3}],\"unknowns\":[{\"id\":\"00535a23-7863-4c78-aa43-a26c7b0f8529\",\"text\":\"각 가상 머신의 disk driver 에 cache 값이 명시되어 있는지.\",\"order\":0},{\"id\":\"653a0ccf-71e1-4a7b-ba62-55b2f8ab310b\",\"text\":\"명시되어 있다면 그 값이 none 인지 writeback 인지, 아니면 §153 이 들지 않은 다른 값인지.\",\"order\":1},{\"id\":\"a57c97b7-1f1b-4bce-8f4e-186215e69883\",\"text\":\"명시가 없을 때 이 환경에서 실제로 적용되는 값이 무엇이고 그것을 무엇으로 읽는지. 개념 문서가 확인 방법을 적지 않아서 실행하는 쪽이 정한다.\",\"order\":2},{\"id\":\"a15e9f68-6153-4f74-9646-c592b651bc88\",\"text\":\"디스크가 여럿인 가상 머신에서 값이 디스크마다 갈리는지.\",\"order\":3},{\"id\":\"f98caf12-b97b-41d6-b671-f77ba1b788e0\",\"text\":\"읽은 값이 §147 이 그린 이중 caching 이 이 호스트에 있는지를 가르는지. 게스트 쪽 I/O 가 buffered 인지까지 함께 봐야 답이 나온다.\",\"order\":4}],\"constraints\":[{\"id\":\"6924c630-82e7-4d32-b022-030e0bc37f22\",\"text\":\"이 물음은 cache 값을 정하지 않는다. §156 은 이름만 보고 단정하지 말라고 적었을 뿐 어느 쪽을 쓰라고 하지 않았고, 감수할 비용도 개념 문서에 없다. 무엇으로 둘지는 값을 읽고 fsync 쪽 측정이 나온 뒤에 정한다.\",\"order\":0},{\"id\":\"5596af38-f9c2-4cd3-aec8-4ceda33090b5\",\"text\":\"이 프로젝트는 어느 쪽으로 둘지를 아직 결정으로 올리지 않고 미결로 남겨 두었다. 권고가 없는 서술을 결정으로 올리면 그런 설정이 있다는 것을 그것을 고른 이유로 바꾸는 셈이기 때문이다. 게다가 지금 걸려 있는 값조차 확인되지 않았고, 그 확인이 이 물음이다.\",\"order\":1},{\"id\":\"d21c433b-6cf6-4617-8c0b-1c5d149dc1e0\",\"text\":\"읽은 값 하나로는 §156 이 요구한 것 — Guest 의 flush/fsync semantics 가 전체 chain 에서 보존되는가 — 에 답하지 못한다.\",\"order\":2},{\"id\":\"d5109fba-dbd2-4555-bc26-f46f8b83d7ea\",\"text\":\"읽는 동안 디스크 설정을 바꾸지 않는다. 바꾸고 받은 XML 은 지금 구성의 값이 아니기 때문이다.\",\"order\":3},{\"id\":\"3646c8e3-e0a5-4105-8826-a6b2ab9d499f\",\"text\":\"이 호스트에서 확인한 값이 없으므로 다른 장비의 기본값을 이 호스트의 값으로 옮겨 적지 않는다.\",\"order\":4}],\"options\":[{\"id\":\"11eadbbe-7be4-479e-8728-88c7b9af78ff\",\"title\":\"가상 머신마다 dumpxml 을 받아 disk 요소를 출력 그대로 인용한다\",\"description\":\"받은 XML 에서 disk 요소를 잘라 그대로 남기면 그 값뿐 아니라 driver 이름과 Source 도 한 번에 확정된다. 값이 적혀 있지 않은 디스크는 적혀 있지 않다는 상태로 보인다.\\n\\n가상 머신이 늘면 출력도 그만큼 늘어난다.\",\"order\":0},{\"id\":\"9fec08fa-3c7e-471c-a3de-ecc0ff3e706b\",\"title\":\"cache 값만 뽑아 가상 머신·디스크 단위 한 행으로 정리한다\",\"description\":\"가상 머신 이름, 디스크의 Target, Source, 형식, cache 값을 한 행에 두면 어느 backend 위의 어떤 값인지가 표 하나로 읽힌다. Source 와 형식은 backend 를 묻는 두 물음이 이미 확정한다.\\n\\n뽑아 적는 동안 원본 XML 을 남기지 않으면 나중에 다시 대조할 수 없으므로, 원본도 함께 남긴다.\",\"order\":1},{\"id\":\"fb6e7a65-4ec3-4e69-8afd-2946d21ba5aa\",\"title\":\"제외 — 두 값을 바꿔 가며 성능 차이를 재 본다\",\"description\":\"none 과 writeback 을 번갈아 걸고 지연을 재면 이 환경에서 무엇이 걸려 있는지 바로 보인다. 그러나 지금 필요한 답은 현재 값 하나이고, 설정을 바꾸는 순간 그 뒤에 읽은 값은 지금 구성의 값이 아니다. 값을 바꿔 비교하는 실험은 durability 쪽 기준을 지난 뒤에 따로 잡는다.\",\"order\":2}],\"nextValidation\":\"1. 가상 머신마다 virsh dumpxml <VM_NAME> 을 돌리고 disk 요소의 driver 설정을 출력 그대로 남긴다 (§175 OQ-4).\\n2. cache 관련 값을 가상 머신·디스크 단위로 한 행씩 적는다. 값이 적혀 있지 않으면 없다고 적는다.\\n3. 같은 행에 그 디스크의 Source 와 형식을 둔다. 두 값은 앞의 두 물음이 확정한다.\\n\\n닫는 조건 : 가상 머신마다 cache 값이 출력으로 확정되면 닫는다. cache=none 이면 §154 의 서술이, cache=writeback 이면 §155 의 서술이 이 호스트에 적용된다고 적는다. 어느 쪽이든 §156 이 요구한 flush/fsync semantics 보존 여부는 이 값만으로 답하지 않는다고 함께 적는다. 값이 명시되어 있지 않으면 그 사실을 적고, 실제 적용 값을 확인할 방법은 다음 검증으로 넘긴다.\",\"resolution\":null,\"projectId\":\"8a638411-2b21-4202-8bf0-720180d77897\",\"questionStatus\":\"OPEN\",\"topicId\":\"23314be2-7350-44f5-8989-575c3b40898f\",\"variantIds\":[]},\"currentValidation\":null,\"latestPreview\":null,\"currentPublication\":null,\"dependencyRevision\":\"c63c93a12fcf3e963051b0d3dc1feb6d\",\"nextAction\":\"VALIDATE\"},\"eead2379-94fe-463a-9e5e-06a01bf2105d\":{\"document\":{\"kind\":\"QUESTION\",\"title\":\"disk image 는 최종적으로 어느 Host block device 위에 있는가\",\"slug\":\"host-block-device-under-the-disk-image\",\"summary\":\"게스트의 디스크가 호스트에서는 파일 하나라는 것까지는 백엔드를 묻는 앞 물음이 확정한다. 그 파일은 다시 호스트 파일시스템 위에 있고 그 파일시스템은 어느 파티션과 물리 장치 위에 있어서, 이미지 경로에서 시작해 그 아래를 마운트와 파티션과 장치 이름까지 따라 내려가야 한다. 두 가상 머신의 이미지가 같은 장치를 쓰는지도 여기서 갈린다.\",\"relations\":[{\"id\":\"5ad442a3-37b3-4724-b500-47c2107fceff\",\"targetId\":\"ecaaa37d-7b33-4cbf-9bc5-4c6caad14ee5\",\"reason\":\"이미지 파일 아래에서 무엇이 그 입출력을 받는지를 그 개념이 설명한다.\",\"order\":0},{\"id\":\"b4186b5b-3c18-43b7-b09e-f0c3533a3aaf\",\"targetId\":\"892b6087-8961-486c-b4d9-d3dd46bc2605\",\"reason\":\"백엔드가 파일인지 장치인지에 따라 여기서 따라 내려갈 경로의 시작이 달라진다.\",\"order\":1},{\"id\":\"e8bfb4d0-31ab-4198-b5ad-781b93e2da38\",\"targetId\":\"1f76d54b-bb17-4174-8128-0c2d71d4256f\",\"reason\":\"그 물음이 확정한 Source 경로가 이 확인의 입력이다.\",\"order\":2},{\"id\":\"3b81a5d1-2962-47bf-a7dc-833264e56f73\",\"targetId\":\"274c5636-33f6-4c19-a54b-d2a96af31289\",\"reason\":\"게스트 쪽 이름에서 물리 장치를 추정하지 않고 호스트에서 따라 내려가는 순서를 그 기준이 요구한다.\",\"order\":3}],\"id\":\"eead2379-94fe-463a-9e5e-06a01bf2105d\",\"version\":2,\"updatedAt\":\"2026-09-09T07:03:34.594347Z\",\"facts\":[{\"id\":\"2cc2f7d4-75ef-4000-9b86-6a96e6735b4d\",\"text\":\"§141 은 백엔드가 qcow2 파일이면 QEMU 가 결국 호스트 리눅스에 파일 입출력을 요청한다고 적었다.\\n그 요청은 Host Kernel, Host Filesystem, Host Block Layer, NVMe Driver 를 지나 Physical NVMe 로 내려간다. 게스트의 스토리지 스택 아래에 호스트의 스토리지 스택이 한 번 더 존재할 수 있다.\",\"order\":0},{\"id\":\"433d3831-b8b7-40c2-8410-0377e5b88172\",\"text\":\"§158 은 qcow2 와 RAW 의 파일 입출력이 호스트 파일시스템을 거쳐 실제 호스트 블록 입출력이 된다고 적고 경로를 이렇게 그렸다.\\nQEMU, vm1.qcow2, Host ext4/XFS, Host Block Layer, /dev/nvme0n1\",\"order\":1},{\"id\":\"682fb676-04a8-480b-b5c8-75eaad3fb8db\",\"text\":\"§158 은 Host Block Layer 가 그 입출력을 가상 머신 안의 PostgreSQL 이 냈는지 호스트 프로세스가 냈는지 본질적으로 구분해서 처리하는 계층이 아니라고 밝혔다. 모두 호스트의 블록 요청이다.\",\"order\":2},{\"id\":\"f4e2e4f8-c2f7-4999-91fa-2d6f109ceb4c\",\"text\":\"§159 는 VM1 의 QEMU 와 VM2 의 QEMU 와 Nginx 와 호스트의 다른 프로세스가 같은 Host Block Layer 를 지나 하나의 NVMe 로 간다고 그렸다.\",\"order\":3},{\"id\":\"222975ca-0deb-4fbc-9ef8-7c55829354fb\",\"text\":\"§175 OQ-5 는 확인 명령으로 lsblk 와 findmnt 를 들었다.\",\"order\":4},{\"id\":\"44c241b2-c2c1-47dd-a7d0-a5f99d162c54\",\"text\":\"§169 의 호스트 관측 명령 목록에도 lsblk 가 들어 있다.\",\"order\":5},{\"id\":\"5f569968-9b48-4231-8ace-6ed63086634d\",\"text\":\"§158 의 vm1.qcow2 와 /dev/nvme0n1 은 경로를 설명하려고 든 예시 이름이고 이 호스트에서 읽은 값이 아니다.\",\"order\":6},{\"id\":\"e01cb634-2184-45d9-bd00-966407822b14\",\"text\":\"이 호스트의 마운트 배치와 물리 장치 이름은 개념 문서에 없다.\",\"order\":7}],\"assumptions\":[{\"id\":\"4fd8d7cf-1165-43aa-a00c-08dd86557305\",\"text\":\"이미지 경로가 앞 물음에서 이미 확정되어 있다고 보고 그 경로에서 시작한다. 백엔드가 파일이 아니라 호스트 블록 장치면 이 확인은 장치 이름에서 시작한다.\",\"order\":0},{\"id\":\"4f80b0e7-50c3-41ee-9cf6-9a5bf57fc44b\",\"text\":\"확인하는 동안 마운트 구성과 이미지 위치가 바뀌지 않는다고 전제한다.\",\"order\":1},{\"id\":\"11f5e2f3-384b-4cb3-94dc-30f826ba4861\",\"text\":\"findmnt 가 보여 주는 source 이름이 물리 장치 이름과 다를 수 있다고 보고 lsblk 로 상하 관계까지 이어서 읽는다.\",\"order\":2},{\"id\":\"66df7977-d3e2-4ead-8c3b-8889a54f4b6f\",\"text\":\"호스트에 붙어 두 명령을 같은 시각에 돌릴 수 있다고 본다.\",\"order\":3}],\"unknowns\":[{\"id\":\"e9ce8356-1565-4c93-91cb-cb831b684f86\",\"text\":\"이미지가 놓인 디렉터리가 어느 마운트 지점에 속하는지.\",\"order\":0},{\"id\":\"1a65a002-d7b8-4bf7-8696-7d40b432c495\",\"text\":\"그 마운트가 어느 파티션 위에 있고 그 파티션이 어느 블록 장치에 속하는지.\",\"order\":1},{\"id\":\"76448e05-10fe-40f8-8226-18eca00ba50b\",\"text\":\"가상 머신들의 이미지가 같은 장치를 공유하는지 서로 다른 장치에 있는지.\",\"order\":2},{\"id\":\"89edca8e-bfb3-4c71-81ce-0fa75adf7bc2\",\"text\":\"이미지를 담은 파일시스템이 무엇인지. §158 은 ext4 와 XFS 를 예로 들었을 뿐 이 호스트의 값을 적지 않았다.\",\"order\":3},{\"id\":\"e17c2e97-35f8-4a11-8a15-ab8b004d488c\",\"text\":\"그 장치가 NVMe 인지 다른 종류인지. §163 이 SATA/SCSI device 를 따로 언급했으므로 확인 명령의 장치 이름도 그에 따라 달라진다.\",\"order\":4}],\"constraints\":[{\"id\":\"6fd0af37-73b7-4a99-9c57-b0372f22de12\",\"text\":\"확인만 한다. 마운트를 바꾸거나 이미지를 다른 장치로 옮기지 않는다. 배치를 바꾸면 지금 구성에서 무엇을 공유하는지 못 본다.\",\"order\":0},{\"id\":\"a25f7f8e-2299-4f56-9e56-bfac4e6f1feb\",\"text\":\"§158 이 예로 든 이름을 이 호스트의 값으로 옮겨 적지 않는다. 출력에 나온 이름만 적는다.\",\"order\":1},{\"id\":\"3ebdc332-0c46-470b-b760-120d74353da6\",\"text\":\"여기서 나온 장치 이름이 다음 두 물음의 입력이므로, 가상 머신마다 한 행으로 남기고 어느 이미지에서 나온 이름인지 함께 적는다.\",\"order\":2},{\"id\":\"2a0495d1-060f-46da-ab43-6ab858e7a07d\",\"text\":\"장치를 나눌지 말지는 이 물음이 정하지 않는다. 공유하고 있다는 사실과 그것이 지연을 실제로 밀어 올리는지는 다른 물음이 받는다.\",\"order\":3},{\"id\":\"959151f9-7bdf-4126-9a67-04e2af8901c7\",\"text\":\"백엔드를 묻는 앞 물음과 이어서 돌리게 되지만 한 물음으로 합치지 않았다. 그쪽은 게스트의 장치가 어느 Source 에 붙어 있는지까지 확정하고 이 물음은 그 경로가 놓인 물리 장치까지 내려간다. 닫는 명령이 다르고 그 출력이 여는 다음 물음도 다르다.\",\"order\":4}],\"options\":[{\"id\":\"88257a2c-5be4-43a4-8253-6e73660f1adf\",\"title\":\"이미지 경로마다 findmnt 로 마운트를 읽고 lsblk 로 그 아래를 잇는다\",\"description\":\"경로 하나에서 시작해 마운트, source device, 파티션, 상위 장치까지 한 줄기로 내려간다. §175 OQ-5 가 든 두 명령 그대로이고, 이미지가 여럿이면 경로마다 같은 순서를 되풀이한다.\\n\\n가상 머신이 늘어난 만큼 실행 횟수도 늘어난다.\",\"order\":0},{\"id\":\"6bcad7df-b293-4215-9834-3cb24716f303\",\"title\":\"호스트 전체의 lsblk 를 먼저 한 장 받고 그 위에 이미지 경로를 얹는다\",\"description\":\"장치와 파티션의 전체 모양을 먼저 확보한 뒤 findmnt 로 각 이미지가 그 가운데 어디에 붙는지 표시해서, 두 가상 머신이 같은 장치를 쓰는지가 한 장에서 바로 보이게 한다.\\n\\n이미지 경로와 마운트의 대응은 결국 findmnt 로 한 번 더 확인해야 한다.\",\"order\":1},{\"id\":\"9ae68891-2dc0-477d-a387-793f5ad958c1\",\"title\":\"제외 — 가상 머신 한 대만 확인하고 나머지는 뒤로 미룬다\",\"description\":\"한 대만 따라 내려가도 경로의 모양은 확인된다. 그러나 이 물음이 답해야 하는 것에는 가상 머신들이 같은 장치를 공유하는지가 들어 있는데, 그것은 한 대의 출력만으로는 갈리지 않는다.\",\"order\":2}],\"nextValidation\":\"1. 백엔드를 묻는 앞 물음이 확정한 Source 경로를 그대로 가져온다.\\n2. 경로마다 findmnt 로 그 경로가 속한 마운트와 source device 를 적는다 (§175 OQ-5).\\n3. 같은 경로에 lsblk 를 돌려 그 장치에 파티션이 어떻게 나뉘어 있고 어느 상위 장치에 속하는지 적는다.\\n4. 이미지 경로, 마운트, 파티션, 장치를 가상 머신마다 한 행으로 남긴다.\\n\\n닫는 조건 : 이미지마다 최종 블록 장치 이름이 확정되면 닫는다. 가상 머신들이 같은 장치를 공유하면 §159 가 그린 상황이 이 환경이라고 적고, 그것이 지연으로 이어지는지는 VM1 부하와 VM2 지연을 재는 물음이 받는다. 서로 다른 장치면 그 사실을 적고 그 물음의 전제가 이 환경에 없다고 함께 적는다. 어느 쪽이든 여기서 확정한 장치 이름을 I/O Scheduler 를 묻는 물음으로 넘긴다.\",\"resolution\":null,\"projectId\":\"8a638411-2b21-4202-8bf0-720180d77897\",\"questionStatus\":\"OPEN\",\"topicId\":\"23314be2-7350-44f5-8989-575c3b40898f\",\"variantIds\":[]},\"currentValidation\":null,\"latestPreview\":null,\"currentPublication\":null,\"dependencyRevision\":\"3928a006b4d6e997a3461ddd0c5a1ae1\",\"nextAction\":\"VALIDATE\"},\"3b05c1f0-13e9-414a-97a6-2078fb4bab26\":{\"document\":{\"kind\":\"CONCEPT\",\"title\":\"Guest 의 write() 가 virtqueue 에 실리기까지 — VFS · Filesystem · Page Cache · Block Layer · virtio-blk\",\"slug\":\"guest-block-io-path-to-virtqueue\",\"summary\":\"가상 머신 안의 PostgreSQL 이 파일에 데이터를 기록하면, 그 요청은 게스트 Linux 안에서만 여러 계층을 지난 뒤에야 가상 머신 경계에 닿는다. VFS 가 열린 파일을 어느 파일시스템 구현으로 보낼지 정하고, ext4 나 XFS 가 파일을 블록 공간에 배치하고, 페이지 캐시가 dirty page 로 받아 두었다가 나중에 writeback 한다. 이어서 블록 I/O 계층이 그 내용을 READ 와 WRITE, FLUSH 요청으로 바꾸고, 마지막에 virtio-blk 드라이버가 Virtio 블록 요청을 만들어 virtqueue 에 게시한다. 계층 이름과 게스트·호스트 구분을 아는 사람을 독자로 둔다. Keycloak 멀티 노드 실험을 가상 머신 두 대 위에서 돌리고 있다 보니, 이 경로를 알아 두면 게스트에서 본 저장소 지연을 애플리케이션 문제와 가상 머신 경계 아래의 문제로 갈라 볼 수 있다.\",\"relations\":[{\"id\":\"af1f8125-942c-4f43-b0e8-e21442e8119a\",\"targetId\":\"892b6087-8961-486c-b4d9-d3dd46bc2605\",\"reason\":\"이 글은 virtqueue 까지만 따라간다. 그 요청을 받은 QEMU 가 무엇에 연결돼 있는지는 그 글이 이어받는다.\",\"order\":0},{\"id\":\"207f784b-22c6-4115-a19f-97a34e5ba888\",\"targetId\":\"8fafaeea-8746-4537-87a4-679a3a91aa4c\",\"reason\":\"여기서는 write() 성공이 SSD 영속화가 아니라는 데까지만 적었다. 그 응답이 어디까지 보장하는지는 그 글이 나눈다.\",\"order\":1},{\"id\":\"cb91fb4e-f7e9-4e32-957b-7d22ddbd58b9\",\"targetId\":\"ecaaa37d-7b33-4cbf-9bc5-4c6caad14ee5\",\"reason\":\"게스트 블록 계층과 같은 이름의 계층이 호스트에도 한 번 더 있고, 그쪽에서는 다른 가상 머신의 요청과 섞인다.\",\"order\":2},{\"id\":\"3cb35f47-ab86-4788-8edb-7f338653cb9d\",\"targetId\":\"274c5636-33f6-4c19-a54b-d2a96af31289\",\"reason\":\"게스트에 /dev/vda 가 보인다는 관측이 그 규칙의 근거가 된다.\",\"order\":3},{\"id\":\"14bfe72e-9369-4d8c-bce2-a6a82e21248b\",\"targetId\":\"1f76d54b-bb17-4174-8128-0c2d71d4256f\",\"reason\":\"이 글은 게스트가 보는 이름까지만 말한다. 이 장비에서 그 이름이 무엇에 붙어 있는지는 아직 확인하지 않았다.\",\"order\":4},{\"id\":\"46f43cbb-c0e4-4214-a9c6-50088fd9fab1\",\"targetId\":\"5b1de31c-0495-4226-8607-73ed7284b314\",\"reason\":\"virtqueue 라는 같은 구조를 패킷 쪽에서 설명한 글이다.\",\"order\":5}],\"id\":\"3b05c1f0-13e9-414a-97a6-2078fb4bab26\",\"version\":4,\"updatedAt\":\"2026-09-09T07:06:51.7311Z\",\"bodyMarkdown\":\"## 애플리케이션은 저장장치를 직접 다루지 않는다\\n\\n가상 머신 안의 PostgreSQL 이나 Keycloak 같은 프로세스는 SSD 나 `virtio-blk` 를 직접 다루지 않는다. 파일에 데이터를 기록할 때 개념적으로 부르는 것은 시스템 콜 하나다.\\n\\n```c label=\\\"게스트 애플리케이션이 파일에 기록할 때 부르는 system call\\\"\\nwrite(fd, buffer, size);\\n```\\n\\n파일과 관련된 시스템 콜은 이것 말고도 여럿이다.\\n\\n```text label=\\\"대표적인 파일 관련 system call\\\"\\nopen()\\nread()\\nwrite()\\nclose()\\nfsync()\\n```\\n\\n이 시점에는 QEMU 도 `qcow2` 도 호스트의 NVMe 도 등장하지 않는다. 애플리케이션은 저장장치를 조작하는 것이 아니라 게스트 Linux 커널에 파일 연산을 요청하고, 그 뒤의 계층들이 그 요청을 하나씩 옮긴다.\\n\\n## VFS 가 어느 파일시스템으로 보낼지 정한다\\n\\nVFS(Virtual File System, 가상 파일 시스템)는 Linux 커널 안에서 여러 파일시스템을 같은 방식으로 쓸 수 있게 묶어 주는 공통 계층이다. 게스트가 `ext4` 를 쓰든 `XFS` 를 쓰든 애플리케이션이 부르는 `write()` 는 같고, 그 뒤를 VFS 가 갈라 준다. VFS 는 이 `fd` 가 어떤 파일인지 확인하고, 그 파일이 어떤 파일시스템에 속하는지 확인한 뒤 해당 파일시스템 구현으로 연산을 넘긴다.\\n\\n## ext4 와 XFS 가 파일을 블록 공간에 배치한다\\n\\nSSD 는 `/var/lib/postgresql/data` 같은 디렉터리 구조를 모른다. 저장장치가 보는 것은 번호가 붙은 블록 단위 공간인데 사람과 프로그램이 보는 것은 파일과 디렉터리 계층이다. 그 둘을 잇는 계층이 `ext4` 나 `XFS` 같은 파일시스템이다. 파일시스템이 관리하는 것은 파일 이름과 디렉터리 구조, 파일 크기, 소유자와 권한, 시각 정보, `inode` 와 메타데이터, 파일 데이터가 저장될 블록, 남은 공간, 파일시스템 일관성이다.\\n\\n`inode` 는 파일 메타데이터와 저장 위치 정보를 관리하는 자료구조다. 디렉터리 항목이 파일 이름을 `inode` 번호로 옮기고, 그 `inode` 가 소유자와 권한, 크기, 시각 정보, 파일 데이터가 저장된 블록 정보를 들고 있다. 그래서 파일 이름과 `inode` 는 같은 것이 아니다. 어디까지 들어갈지는 SSOT 가 직접 그어 두었다.\\n\\n> Storage 가상화를 이해하기 위해 inode 내부 구현까지 파고들 필요는 없지만, filesystem이 파일과 block을 연결한다는 점은 알아야 한다.\\n\\n이 글도 거기서 멈춘다.\\n\\n## write() 가 끝나도 SSD 에는 아직 없다\\n\\n일반적인 buffered I/O 에서는 `write()` 가 호출될 때마다 물리 SSD 까지 즉시 내려갈 필요가 없다. 애플리케이션이 넘긴 데이터는 메모리의 페이지 캐시에 먼저 닿는다. 저장장치에 `ABC` 가 있는데 애플리케이션이 `DEF` 를 추가하면 페이지 캐시에는 `ABCDEF` 가 들어가고 SSD 에는 아직 `ABC` 만 있다. 이렇게 저장장치보다 최신인 페이지를 dirty page 라고 한다. 이후 커널 writeback 이 그 내용을 파일시스템과 블록 계층을 거쳐 저장장치 쪽으로 내려보낸다.\\n\\n데이터가 페이지 캐시에 머무는 동안에도 호출이 성공으로 돌아오기 때문에, `write()` 성공과 물리 SSD 영속화 완료는 같은 사건이 아니다. 게스트 애플리케이션이 성공을 돌려받은 시점에 데이터가 어디에 있는지는 게스트 메모리까지만 확정된다.\\n\\n## 블록 I/O 계층이 파일 세계를 블록 장치 세계로 옮긴다\\n\\n파일시스템이 파일과 블록 할당을 관리하면, Linux 의 블록 I/O 서브시스템이 그 요청을 아래의 블록 장치 드라이버가 처리할 수 있는 I/O 요청으로 바꿔 전달한다. 파일시스템 세계에서 `/users/data.db` 의 오프셋 8192 에 4KB 를 쓰라고 내려온 것이, 블록 장치 세계에서는 `/dev/vda` 의 특정 위치에 `READ` 나 `WRITE`, `FLUSH` 를 거는 요청이 된다. 대표 요청은 넷이다 — `READ`, `WRITE`, `FLUSH`, `DISCARD`.\\n\\n이 계층 안에는 `bio` 와 `request`, `queue`, `blk-mq` 가 실제로 있다. SSOT 는 `blk-mq` 의 tag allocator 같은 내부 구현을 별도 문서로 미뤘고 이 글도 이름까지만 적는다.\\n\\n## /dev/vda 는 게스트가 보는 이름이다\\n\\n물리 머신에서는 `/dev/sda` 나 `/dev/nvme0n1` 같은 블록 장치가 보인다. `virtio-blk` 를 쓰는 가상 머신에서는 흔히 `/dev/vda` 와 `/dev/vdb` 처럼 보이고, 게스트 Linux 는 그것을 하나의 블록 장치로 인식한다. 게스트 안에서 `lsblk` 를 실행하면 그 장치와 파티션이 나온다.\\n\\n```bash label=\\\"게스트가 보는 block device\\\"\\nlsblk\\n```\\n\\n```text label=\\\"SSOT 가 든 출력 예시. 이 호스트에서 실행한 결과가 아니다\\\"\\nNAME SIZE TYPE MOUNTPOINT\\nvda 100G disk\\n├─vda1 1G part /boot\\n└─vda2 99G part /\\n```\\n\\n게스트에 이렇게 보여도 그 장치가 호스트의 실제 SSD 인지는 게스트 안에서 알 수 없다. 이 장비의 `/dev/vda` 가 무엇에 붙어 있는지도 아직 확인하지 않았다. 게스트가 보는 것은 `/dev/vda` 에서 파티션으로, 파티션에서 파일시스템으로, 파일시스템에서 마운트 지점으로 이어지는 연결까지다. `cd /var/lib/postgresql` 로 디렉터리를 옮기는 동작은 파일시스템 세계를 지나고, `lsblk` 의 `vda` 는 블록 장치 세계를 보여 준다.\\n\\n이름 둘이 가리키는 대상도 다르다. `/dev/vda` 가 게스트 Linux 에 보이는 블록 장치를 가리키고, `virtio-blk` 는 그 가상 블록 장치를 제어하는 게스트 커널 드라이버를 가리킨다. 네트워크 쪽의 `ens3` 와 `virtio-net` 이 같은 짝이다.\\n\\n## virtio-blk 가 요청을 virtqueue 에 게시한다\\n\\n게스트 블록 계층에서 `/dev/vda` 의 특정 위치에 데이터를 `WRITE` 하라는 요청이 내려오면, `virtio-blk` 드라이버가 그것을 Virtio 블록 요청으로 구성해 `virtqueue` 에 게시한다. 네트워크에서 TCP/IP 스택이 `virtio-net` 을 거쳐 `virtqueue` 로 내려가던 구조가 저장소 쪽에서도 반복된다.\\n\\n게스트 메모리 안에 I/O 버퍼가 있고 `virtqueue` 의 디스크립터가 그 버퍼를 가리키기 때문에, `virtqueue` 를 데이터가 흘러가는 관으로 보면 부정확하다. 게시되는 요청에는 `Operation` 이 `WRITE` 인지, `Sector` 가 어디인지, `Data Buffer` 가 게스트 메모리의 어디인지가 들어간다. 뜻을 풀면 `/dev/vda` 의 이 위치에 게스트 메모리의 이 버퍼를 기록하라는 요청이다.\\n\\n:::evidence key=\\\"guest-block-io-to-virtqueue-59870a6f\\\" alt=\\\"게스트 사용자 공간의 PostgreSQL / Keycloak 에서 시작한 write() 가 게스트 커널의 VFS, ext4 / XFS, Guest Page Cache, Guest Block Layer, /dev/vda, virtio-blk Frontend 를 차례로 지나 virtqueue 에 실리는 흐름도. Guest Page Cache 에서 Guest Block Layer 로 내려가는 화살표에 writeback 이, Guest Block Layer 에서 /dev/vda 로 가는 화살표에 WRITE / FLUSH 가 붙어 있고, 맨 아래 virtqueue 바로 밑이 VM Boundary 라 이 그림은 거기서 끝난다.\\\" caption=\\\" \\\" zoom=\\\"true\\\"\\n:::\\n\\n## 완료는 같은 virtqueue 로 돌아온다\\n\\n`WRITE` 요청은 아래로 내려가고 완료는 반대 방향으로 올라온다. NVMe 가 완료를 올리면 NVMe 드라이버와 호스트 블록 계층, QEMU 와 백엔드를 지나 `virtqueue` 완료가 된다. `virtio-blk` 가 그것을 받아 게스트 블록 계층으로 올린다. 그래서 `virtqueue` 는 요청을 내려보내는 통로이면서 완료를 올려보내는 통로이기도 하다.\\n\\n## 네트워크 가상화와 짝이 되는 이름들\\n\\n제3부의 네트워크 경로를 먼저 읽었다면 이름이 하나씩 대응된다.\\n\\n| Network | Storage |\\n|---|---|\\n| `virtio-net` | `virtio-blk` |\\n| packet | block I/O request |\\n| TX/RX virtqueue | I/O virtqueue |\\n| TAP / network backend | QEMU block backend |\\n| Linux Bridge/Route | Host filesystem/block stack |\\n| Physical NIC | Physical SSD/NVMe |\\n| Guest TCP/IP Stack | Guest VFS/Filesystem/Block Layer |\\n| send/recv | read/write/fsync |\\n\\nSSOT 는 이 표를 학습용 대응으로 두었고, 두 열의 각 요소가 `1:1` 로 같은 종류라는 데까지는 말하지 않는다.\\n\\n## 확인하지 못한 것과 범위 밖으로 둔 것\\n\\nSSOT 는 이 부에서 다룰 것과 미룰 것을 먼저 갈라 두었다. `qcow2` 내부의 L1/L2 테이블과 `blk-mq` 의 tag allocator, NVMe 의 submission/completion 큐는 필요할 때 별도 문서에서 다루기로 했다.\\n\\n이 호스트에서 잰 값은 하나도 없다. 게스트의 `/dev/vda` 가 호스트에서 무엇에 붙어 있는지는 SSOT 가 적어 둔 열린 질문 가운데 첫 번째인데, 아직 확인하지 않았다. 게스트의 파일시스템이 `ext4` 인지 `XFS` 인지도 SSOT 어디에도 없어서, 이 글은 두 파일시스템이 같은 계층을 지난다는 데까지만 말한다. 요청 수나 지연을 재려면 게스트의 `fsync()` 지연과 호스트 저장소 지연을 같은 시간축에서 함께 보는 관찰이 먼저 돌아야 한다.\",\"basisVersion\":\"QEMU/KVM 의 virtio-blk frontend 와 virtqueue · 게스트가 ext4 나 XFS 에 buffered I/O 로 쓰는 경우 · SSOT 가 커널과 QEMU 버전을 적지 않았다\",\"projectId\":\"8a638411-2b21-4202-8bf0-720180d77897\",\"topicId\":\"23314be2-7350-44f5-8989-575c3b40898f\",\"variantIds\":[]},\"currentValidation\":null,\"latestPreview\":null,\"currentPublication\":null,\"dependencyRevision\":\"ab3d88aca71f3146110a1adb06b8d2e6\",\"nextAction\":\"VALIDATE\"},\"892b6087-8961-486c-b4d9-d3dd46bc2605\":{\"document\":{\"kind\":\"CONCEPT\",\"title\":\"/dev/vda 뒤에 있는 것 — qcow2 · RAW · Host block device\",\"slug\":\"qemu-block-backend-forms\",\"summary\":\"게스트 안에서 /dev/vda 를 보는 것만으로는 호스트에 무엇이 있는지 알 수 없다. 가상 머신 경계를 넘으면 QEMU 의 virtio-blk 장치 모델과 블록 백엔드가 그 요청을 받는데, 그 백엔드는 qcow2 파일일 수도, RAW 파일일 수도, 호스트의 실제 블록 장치일 수도 있다. 갈래마다 바뀌는 것이 다르다. 파일 백엔드면 게스트 스토리지 계층 아래에 호스트 파일시스템과 블록 계층이 한 겹 더 놓이고, qcow2 면 게스트가 보는 크기와 호스트가 실제로 쓰는 크기가 갈린다. 무엇에 붙어 있는지는 게스트가 아니라 호스트에서 virsh domblklist 로 확인한다. 가상 머신에 디스크를 붙여 본 사람을 독자로 둔다.\",\"relations\":[{\"id\":\"d068f14a-a93c-4736-a72c-a9a94a6befa8\",\"targetId\":\"3b05c1f0-13e9-414a-97a6-2078fb4bab26\",\"reason\":\"게스트 안에서 virtqueue 까지 내려온 요청을 이 글이 이어받는다.\",\"order\":0},{\"id\":\"b921a94e-5b36-4b81-80bb-612896459913\",\"targetId\":\"8fafaeea-8746-4537-87a4-679a3a91aa4c\",\"reason\":\"파일 백엔드면 호스트 페이지 캐시가 한 겹 더 생기고, 그 위에서 완료가 무엇을 보장하는지가 갈린다.\",\"order\":1},{\"id\":\"75c1254d-d4bb-4624-ab2d-9a55ac2a631a\",\"targetId\":\"ecaaa37d-7b33-4cbf-9bc5-4c6caad14ee5\",\"reason\":\"이 글은 백엔드 형태까지만 본다. 그 아래에서 요청이 어떻게 줄을 서는지는 그 글이 본다.\",\"order\":2},{\"id\":\"440fe9cd-6f00-4799-9dd4-e7f786c4768f\",\"targetId\":\"274c5636-33f6-4c19-a54b-d2a96af31289\",\"reason\":\"이 글이 적은 세 갈래가 그 규칙의 근거다.\",\"order\":3},{\"id\":\"1e76bb17-3216-4062-a4ab-d23f87a53d0a\",\"targetId\":\"1f76d54b-bb17-4174-8128-0c2d71d4256f\",\"reason\":\"세 갈래 가운데 어느 것인지 이 장비에서 확인하지 않았다.\",\"order\":4},{\"id\":\"584e60a7-bd43-46e3-b8f6-2d79203ace3b\",\"targetId\":\"ae69e2e1-1a3b-4ae3-ae2a-572af07b95d7\",\"reason\":\"이 글이 든 크기 차이는 SSOT 의 예시 값이고, 이 장비의 값은 그 질문이 잰다.\",\"order\":5},{\"id\":\"d4640ca0-53f9-4a6b-84bd-f373f79e2b0d\",\"targetId\":\"eead2379-94fe-463a-9e5e-06a01bf2105d\",\"reason\":\"파일 백엔드라면 그 파일이 놓인 호스트 장치까지 따라가야 경로가 끝난다.\",\"order\":6}],\"id\":\"892b6087-8961-486c-b4d9-d3dd46bc2605\",\"version\":4,\"updatedAt\":\"2026-09-09T07:07:00.914735Z\",\"bodyMarkdown\":\"## 가상 머신 경계를 넘으면 QEMU 가 받는다\\n\\n게스트의 `virtio-blk` 가 `virtqueue` 에 게시한 요청은 가상 머신 경계를 넘어 호스트 사용자 공간의 QEMU 로 간다. QEMU 안에서는 `virtio-blk` 장치 모델과 블록 백엔드가 그 요청을 받는다. QEMU 는 게스트에게 가상 블록 장치를 노출하고, 게스트의 가상 I/O 를 호스트 백엔드에 연결한다.\\n\\nSSOT 는 제4부의 전체 구조를 그린 뒤 이 갈림을 핵심 문장 하나로 따로 뽑아 두었다.\\n\\n> Guest는 `/dev/vda`를 실제 block device처럼 보지만, Host에서는 그 disk가 qcow2 파일, RAW 파일, 또는 실제 block device에 연결되어 있을 수 있다.\\n\\n이 글이 다루는 것이 그 문장이 남긴 세 갈래다.\\n\\n## QEMU 가 물리 SSD 를 직접 제어하지는 않는다\\n\\n백엔드가 `qcow2` 파일이면 QEMU 는 결국 호스트 Linux 에 파일 I/O 를 요청한다. `pread` 나 `pwrite` 같은 호출이 호스트 커널로 들어가고, 그 뒤로 호스트 파일시스템과 호스트 블록 계층, NVMe 드라이버를 지나 물리 NVMe 에 닿는다. 그 경로가 호스트 커널을 한 번 더 지나기 때문에, 게스트 스토리지 계층 아래에 호스트 스토리지 계층이 한 번 더 존재할 수 있다.\\n\\n## 같은 대상을 호스트는 파일로, 게스트는 디스크로 본다\\n\\n호스트에 `/var/lib/libvirt/images/keycloak-node1.qcow2` 라는 파일이 있다고 하자. 호스트 관점에서 그것은 파일 하나인데, 같은 대상을 게스트는 `/dev/vda` 라는 디스크로 보고 그 안을 `/dev/vda1` 과 `/dev/vda2` 로 나눠 쓴다. 두 관점 모두 맞다.\\n\\n## qcow2 는 게스트가 보는 크기와 호스트가 쓰는 크기가 다르다\\n\\n`qcow2` 는 가상 디스크 크기와 실제 호스트 할당량이 다를 수 있다. SSOT 가 든 예시에서는 게스트가 `/dev/vda` 를 100 GB 로 보는 동안 호스트의 `vm1.qcow2` 가 3GB 를 쓴다. 게스트가 데이터를 기록하면서 가상 크기 100GB 는 그대로인 채 실제 할당량이 1GB 에서 10GB 로, 다시 40GB 로 늘 수 있다. 이 숫자들은 SSOT 가 설명하려고 든 값이고 이 호스트에서 잰 값이 아니다.\\n\\n```bash label=\\\"qcow2 의 virtual size 와 실제 할당량\\\"\\nqemu-img info vm1.qcow2\\n```\\n\\n출력의 `virtual size` 와 실제 할당량은 서로 다른 값이다.\\n\\n## RAW 와 qcow2 를 성능으로 먼저 가르지 않는다\\n\\nRAW 는 `qcow2` 보다 구조가 단순하다. 게스트의 블록 요청이 `qcow2` 에서는 QEMU 의 매핑과 메타데이터 처리를 지나 `qcow2` 파일 I/O 가 되고, RAW 에서는 상대적으로 직접적인 오프셋 대응을 지나 RAW 파일 I/O 가 된다. `qcow2` 는 Copy-on-Write 와 sparse allocation, 스냅샷에 유리하지만 그 대신 매핑과 메타데이터 처리가 붙는다.\\n\\n그렇다고 RAW 가 무조건 빠르고 `qcow2` 가 무조건 느리다고 일반화하면 안 된다. SSOT 는 실제 성능이 캐시 모드와 스토리지 백엔드, 작업 부하 패턴, 큐 깊이, 스냅샷 체인, 그 아래 파일시스템, 물리 장치에 영향을 받는다고 적었다. 형식 이름만으로는 어느 쪽이 빠른지 정해지지 않는다.\\n\\n## 백엔드가 파일이 아니라 호스트 블록 장치일 수도 있다\\n\\n백엔드는 파일이 아니어도 된다. 게스트의 `/dev/vda` 가 `virtio-blk` 와 QEMU 를 지나 호스트의 `/dev/nvme0n1p3` 같은 블록 장치에 바로 연결될 수 있고, SSOT 가 그린 이 경로에는 호스트 파일시스템이 없다. 같은 이름 아래에서 경로가 세 갈래로 갈리기 때문에, 게스트에 `/dev/vda` 가 있다는 정보만으로는 백엔드 구조를 알 수 없다.\\n\\n## 세 갈래가 각각 바꾸는 것\\n\\n| 호스트에서는 무엇인가 | 그래서 무엇이 달라지나 |\\n|---|---|\\n| `qcow2` 파일 | 게스트 파일시스템 아래에 호스트 파일시스템과 블록 계층이 한 겹 더 놓인다. Copy-on-Write 와 sparse allocation, 스냅샷을 쓸 수 있고 매핑과 메타데이터 처리가 붙는다. 게스트가 보는 가상 크기와 호스트 실제 할당량이 갈린다 |\\n| RAW 파일 | 게스트 파일시스템 아래에 호스트 파일시스템과 블록 계층이 한 겹 더 놓인다. 블록 위치가 파일 오프셋에 상대적으로 직접 대응한다 |\\n| 호스트 블록 장치 | SSOT 가 그린 경로에 호스트 파일시스템이 없다 |\\n\\n## 무엇에 붙어 있는지는 호스트에서 확인한다\\n\\n게스트에서 `lsblk` 로 장치를 보고, 호스트에서 `virsh domblklist` 로 그 장치의 `Source` 를 본다.\\n\\n```bash label=\\\"게스트가 보는 block device\\\"\\nlsblk\\n```\\n\\n```bash label=\\\"호스트에서 본 VM 의 disk 연결\\\"\\nvirsh domblklist <VM_NAME>\\n```\\n\\n```text label=\\\"SSOT 가 든 출력 예시. 이 호스트에서 실행한 결과가 아니다\\\"\\nTarget Source\\n-----------------------------------------------\\nvda /var/lib/libvirt/images/vm1.qcow2\\n```\\n\\n이 출력이 나오면 게스트의 `/dev/vda` 가 `virtio-blk` 와 QEMU 를 지나 `/var/lib/libvirt/images/vm1.qcow2` 에 닿는 연결이 확인된다.\\n\\n## 확인하지 못한 것\\n\\n이 호스트의 백엔드가 셋 중 무엇인지는 SSOT 에 없다. SSOT 가 열린 질문 넷을 적어 두었는데, 그 확인을 아직 돌리지 않았다. `/dev/vda` 가 어떤 백엔드에 붙어 있는지, 백엔드가 `qcow2` 인지 RAW 인지, `qcow2` 의 가상 크기와 실제 호스트 사용량이 얼마나 다른지, 그 이미지가 최종적으로 어느 호스트 블록 장치 위에 있는지 넷이다. 앞에서 든 100 GB 와 3GB, 1GB 에서 40GB 까지의 증가는 SSOT 가 설명하려고 든 예시 값이다.\\n\\n`qcow2` 와 RAW 의 성능 비교도 이 저장소에 없다. SSOT 는 실제 성능을 가르는 조건만 열거했고 어느 쪽이 이 환경에서 빠른지는 재지 않았다.\",\"basisVersion\":\"QEMU block backend 세 형태 — qcow2 파일 · RAW 파일 · Host block device. libvirt 로 정의한 VM 기준이고 SSOT 가 QEMU 와 libvirt 버전을 적지 않았다\",\"projectId\":\"8a638411-2b21-4202-8bf0-720180d77897\",\"topicId\":\"23314be2-7350-44f5-8989-575c3b40898f\",\"variantIds\":[]},\"currentValidation\":null,\"latestPreview\":null,\"currentPublication\":null,\"dependencyRevision\":\"1d4e8a4aaff8482ad40f1f71e533b212\",\"nextAction\":\"VALIDATE\"},\"8fafaeea-8746-4537-87a4-679a3a91aa4c\":{\"document\":{\"kind\":\"CONCEPT\",\"title\":\"완료라는 말의 네 가지 뜻 — write · writeback · flush · 전원 장애 생존\",\"slug\":\"write-completion-is-not-durability\",\"summary\":\"buffered I/O 에서 write() 는 페이지 캐시까지만 내려간다. 가상 머신에서는 그 페이지 캐시가 게스트와 호스트 양쪽에 두 번 나타날 수 있다. 게스트가 성공을 돌려받은 데이터가 호스트 메모리에 머무는 동안 호스트 전원이 나가면 물리 SSD 에는 이전 상태가 남는다. 그래서 완료라는 말이 네 단계로 갈린다 — write() 완료, writeback 완료, fsync/flush 완료, 전원 장애에도 안전한 영속성. Direct I/O 와 QEMU 캐시 모드는 이 가운데 어디까지 갔는지를 바꾼다. 게스트에게 FLUSH 완료라고 응답했는데 데이터가 실제로는 호스트 메모리에만 있으면, 그것은 느린 구성이 아니라 영속성 계약이 깨진 상태다. 페이지 캐시와 fsync 를 아는 사람을 독자로 둔다. Keycloak 과 PostgreSQL 을 가상 머신 위에서 돌리는 실험에서 이 구분을 먼저 세워 두면, 지연을 재는 것과 데이터가 남는지를 재는 것을 한 실험에 섞지 않게 된다.\",\"relations\":[{\"id\":\"f687e87e-d4ff-4bb1-890f-12e1c0ca3dfd\",\"targetId\":\"3b05c1f0-13e9-414a-97a6-2078fb4bab26\",\"reason\":\"게스트 쪽 경로를 VFS 부터 virtqueue 까지 이어 그린 글이다. 이 글은 그 경로 위에서 완료 응답이 무엇을 보장하는지만 본다.\",\"order\":0},{\"id\":\"12ca5784-929c-4fcf-b4b6-4a3498f5d4c0\",\"targetId\":\"892b6087-8961-486c-b4d9-d3dd46bc2605\",\"reason\":\"백엔드가 파일이면 게스트 파일시스템 아래에 호스트 파일시스템과 호스트 페이지 캐시가 한 겹 더 놓인다. 두 번째 페이지 캐시가 왜 생기는지를 그 글이 설명한다.\",\"order\":1},{\"id\":\"23de76ca-8f2c-4e36-8de0-f51dbb556704\",\"targetId\":\"ecaaa37d-7b33-4cbf-9bc5-4c6caad14ee5\",\"reason\":\"fsync 요구가 호스트로 내려간 뒤 어느 계층을 지나는지는 그 글이 맡는다.\",\"order\":2},{\"id\":\"d7f3a322-33e8-4f24-ad7c-12cb0d073a67\",\"targetId\":\"decbab8d-9c98-45c6-b5b8-e6162e407299\",\"reason\":\"이 개념이 세운 네 경계를 설정 선택과 성능 개선의 판정에 적용하는 규칙이다.\",\"order\":3},{\"id\":\"d6828a9b-76ff-4e5d-b0e2-6e25f6e2ddab\",\"targetId\":\"480f86ac-86fb-4290-9bf5-584763aa1363\",\"reason\":\"이 호스트가 cache=none 구성인지 cache=writeback 구성인지 SSOT 에 적혀 있지 않다.\",\"order\":4}],\"id\":\"8fafaeea-8746-4537-87a4-679a3a91aa4c\",\"version\":4,\"updatedAt\":\"2026-09-09T07:07:09.10675Z\",\"bodyMarkdown\":\"## 게스트와 호스트에 페이지 캐시가 두 번 생긴다\\n\\n일반적인 buffered I/O 에서는 `write()` 가 호출될 때마다 물리 SSD 까지 즉시 내려갈 필요가 없다. 커널은 그 내용을 메모리의 페이지 캐시에 받아 두고, 나중에 writeback 으로 파일시스템과 블록 계층을 지나 저장장치 쪽에 내려보낸다. 저장장치보다 최신인 페이지를 dirty page 라고 한다. 저장장치에 `ABC` 가 있는 상태에서 애플리케이션이 `DEF` 를 추가하면 페이지 캐시는 `ABCDEF` 를 dirty 상태로 들고 있고 SSD 에는 아직 `ABC` 만 있다.\\n\\n가상 머신에서는 이 페이지 캐시가 한 번 더 나타날 수 있다. 게스트가 buffered I/O 를 쓰고 디스크 백엔드가 호스트의 파일이며 호스트도 페이지 캐시를 쓰는 구성이라고 하자. 게스트 PostgreSQL 의 쓰기는 게스트 `ext4` 와 게스트 페이지 캐시를 지나 게스트 블록 계층과 `virtio-blk` 를 거쳐 `virtqueue` 로 나간다. 가상 머신 경계를 넘은 다음에는 QEMU 와 디스크 이미지 파일을 지나 호스트 페이지 캐시에 다시 담긴다. 같은 데이터가 게스트 메모리와 호스트 메모리 양쪽에 캐시될 수 있다.\\n\\n## write() 성공이 어디까지를 뜻하는가\\n\\n`write()` 가 성공을 돌려준 시점에 데이터는 게스트 페이지 캐시에 있고, virtio 를 지나 호스트 페이지 캐시까지 갔을 수도 있다. 그 상태에서 호스트 전원이 나가면 물리 SSD 에는 그 데이터가 없기 때문에, 완료라는 말이 네 단계로 갈린다.\\n\\n```text label=\\\"완료의 네 단계\\\"\\nwrite() 완료\\n ≠\\nwriteback 완료\\n ≠\\nfsync/flush 완료\\n ≠\\n전원 장애에도 안전한 durability\\n```\\n\\n## fsync() 는 요구를 계층 전체로 내려보낸다\\n\\n애플리케이션이 부르는 쓰기 호출은 이렇게 생겼다.\\n\\n```c label=\\\"이 호출이 성공해도 정전 이후 생존은 보장되지 않는다\\\"\\nwrite(fd, data, size);\\n```\\n\\n필요한 시점에 `fsync()` 를 불러 변경 내용을 필요한 영속성 경계까지 반영하도록 요청한다.\\n\\n```c label=\\\"영속성 경계까지 반영을 요청한다\\\"\\nfsync(fd);\\n```\\n\\n가상 머신에서는 이 요청이 한 계층에서 끝나지 않는다. 게스트 PostgreSQL 의 `fsync()` 가 게스트 파일시스템으로 가고, 게스트 블록 계층에서 `FLUSH` 등으로 바뀐다. 그다음 `virtio-blk` 를 통해 QEMU 와 백엔드로 내려가고, 호스트 스토리지 계층을 지나 물리 저장장치까지 그 의미가 전달되어야 한다.\\n\\n:::evidence key=\\\"write-completion-boundaries-87bdee72\\\" alt=\\\"PostgreSQL 이 부른 fsync() 가 Guest Filesystem 으로 내려가고, Guest Block Layer 에서 FLUSH 등으로 바뀌어 virtio-blk 와 QEMU / Backend, Host Storage Stack 을 지나 맨 아래 Physical Storage 까지 닿는 흐름도. 여섯 번 손이 바뀌는 동안 한 곳이라도 그 의미를 지키지 않으면 게스트가 받은 완료 응답이 거짓이 된다.\\\" caption=\\\" \\\" zoom=\\\"true\\\"\\n:::\\n\\n`WRITE` 와 `FLUSH` 가 요구하는 것은 서로 다르다.\\n\\n```text label=\\\"WRITE 와 FLUSH 가 요구하는 것\\\"\\nWRITE\\n ↓\\n\\\"이 데이터를 써라\\\"\\n\\nFLUSH\\n ↓\\n\\\"앞서 쓴 데이터를 필요한 영속성 경계까지\\n반영하고 완료 상태를 보장해라\\\"\\n```\\n\\n실제 순서 보장과 영속성의 의미는 이 두 줄보다 복잡하다. SSOT 도 그렇게 밝혀 두었고, 이 글은 두 요청의 구분까지만 말한다.\\n\\n## Direct I/O 가 우회하는 대상은 페이지 캐시다\\n\\nbuffered I/O 에서는 QEMU 의 쓰기가 호스트 페이지 캐시를 거쳐 호스트 파일시스템과 블록 계층을 지나 SSD 로 간다. Direct I/O 에서는 호스트 페이지 캐시를 건너뛰고 호스트 파일시스템과 블록 I/O 경로로 바로 들어간다. Linux 의 `O_DIRECT` 가 여기에 쓰인다. 우회하는 대상은 페이지 캐시이고, Direct I/O 로 썼다고 영속성이 자동으로 보장되지는 않는다.\\n\\n## cache=none 과 cache=writeback 이 정하는 것\\n\\nQEMU 와 libvirt 로 정의한 디스크에서 대표적으로 볼 수 있는 설정은 `cache=none` 과 `cache=writeback` 이다. 두 값이 정하는 것은 QEMU 가 호스트 페이지 캐시와 쓰기 완료·플러시의 의미를 어떤 방식으로 쓰는가다. 이름만으로는 캐시가 아예 없는지, 무조건 위험한지가 정해지지 않는다.\\n\\n| 설정 | 호스트 페이지 캐시를 어떻게 쓰나 |\\n|---|---|\\n| `cache=none` | 우회하는 방향으로 구성한다. 이중 캐시를 줄일 수 있다 |\\n| `cache=writeback` | 쓸 수 있게 구성한다. 일반 쓰기가 호스트 메모리에서 빠르게 완료될 수 있다 |\\n\\n호스트 페이지 캐시를 우회한다고 해서 쓰기가 곧바로 영속 매체에 반영되지는 않는다. `cache=writeback` 으로 두어도 게스트의 `fsync()` 와 `FLUSH` 가 무시되지 않는다. 정상적인 계층이라면 게스트의 `fsync` 와 `FLUSH` 가 virtio `FLUSH` 로 이어진다. QEMU 와 백엔드에서 호스트 동기화와 플러시로 이어지고, 저장장치에서 필요한 완료를 확인한 뒤 게스트 완료로 돌아온다.\\n\\n`cache=writeback` 을 위험하다는 한 낱말로 줄이지 않으려고 SSOT 는 정확한 표현을 따로 적어 두었다.\\n\\n> writeback caching에서는 volatile cache가 존재할 수 있으므로, Guest의 flush/fsync semantics가 전체 backend/storage stack에서 올바르게 보존되는지가 중요하다.\\n\\n그래서 보는 것은 게스트가 요구한 영속성이 게스트 파일시스템에서 게스트 블록 계층과 virtio, QEMU 와 백엔드, 호스트 스토리지를 지나 장치까지 가는 동안 그 의미가 깨지지 않는지다.\\n\\n두 설정 가운데 어느 쪽을 쓸지 이 프로젝트는 정하지 않았다. SSOT 는 두 설정의 뜻과 각각의 오해를 갈라 놓기만 했고, 어느 쪽을 쓰기로 했다고도 그렇게 해서 무엇을 감수했다고도 적지 않았다. 이 호스트의 지금 값이 무엇인지부터 확인되지 않아서, 관계로 걸어 둔 질문이 그것을 먼저 답한다.\\n\\n## 호스트 페이지 캐시를 우회해도 장치 안에 캐시가 있다\\n\\nDirect I/O 로 호스트 페이지 캐시를 건너뛰어도 요청은 호스트 블록 계층과 NVMe 드라이버, NVMe 컨트롤러를 지나 장치 쪽 캐시를 거쳐 플래시에 닿는다. 스토리지 컨트롤러나 장치가 휘발성 쓰기 캐시를 가질 수 있다.\\n\\n```text label=\\\"RAM 을 떠난 뒤에도 세 상태가 다르다\\\"\\nRAM에서 나갔다\\n ≠\\nDevice에 command가 전달됐다\\n ≠\\n전원이 끊겨도 살아남는 상태가 됐다\\n```\\n\\n운영에서는 장치의 플러시와 FUA(Force Unit Access) 의미, 전원 손실 보호(power-loss protection)를 갖췄는지도 확인 대상이 된다. SSOT 는 그것이 중요할 수 있다고만 적었고, 이 호스트의 장치가 무엇을 갖췄는지는 적지 않았다.\\n\\n## 거짓 완료는 성능 문제가 아니다\\n\\n게스트가 `WRITE` 에 이어 `FLUSH` 를 요청했는데 데이터는 호스트 메모리에만 있고 물리 저장장치에는 이전 데이터가 들어 있다고 하자. 이때 게스트에게 `FLUSH 완료` 라고 응답하면 PostgreSQL 은 영속성이 확보됐다고 판단한다. 직후에 호스트 전원이 나가면 메모리의 데이터가 사라진다. 이것은 영속성 계약이 깨지는 정확성(correctness) 문제다.\\n\\nPostgreSQL 로 보면 무엇이 어긋나는지 드러난다. 잔액을 바꾸고 커밋하는 트랜잭션을 생각해 본다.\\n\\n```sql label=\\\"durability 가 걸리는 트랜잭션\\\"\\nBEGIN;\\n\\nUPDATE users\\nSET balance = 1000\\nWHERE id = 1;\\n\\nCOMMIT;\\n```\\n\\nPostgreSQL 은 WAL(Write-Ahead Log) 등의 영속성 프로토콜을 쓰며 필요한 시점에 스토리지 동기화를 수행한다. WAL 쓰기는 게스트 페이지 캐시와 `fsync` 를 지나 게스트 파일시스템과 게스트 블록 계층, `virtio-blk`, QEMU, 호스트 스토리지를 거쳐 물리 저장장치까지 간다. 그 완료가 되돌아와야 필요한 영속성 조건이 충족됐다고 보고 `COMMIT` 을 성공으로 처리한다. 가상 머신 스토리지 계층이 플러시와 `fsync` 의 의미를 제대로 보존하지 않으면, PostgreSQL 의 영속성 가정과 실제 저장장치 동작이 어긋날 수 있다.\\n\\n## 이 호스트에서 확인하지 않은 것\\n\\n이 글에 이 테스트 호스트에서 잰 값은 하나도 없다. 두 가상 머신의 디스크 캐시 모드가 무엇으로 설정돼 있는지 SSOT 에 없어서, 이 호스트가 `cache=none` 구성인지 `cache=writeback` 구성인지 여기서 말하지 않는다. 거짓 완료가 이 환경에서 실제로 일어나는지도 재현하지 않았다. 장치의 플러시와 FUA 의미가 어떻게 동작하는지, 전원 손실 보호를 갖췄는지도 확인하지 않았다. `fsync()` 지연 값은 관계로 걸어 둔 질문이 게스트 지연과 호스트 스토리지 지연을 같은 시간축으로 잰 뒤에 이 글에 들어온다. 이 글은 이 구조에서 완료 응답이 무엇을 보장하는지까지 설명하고, 이 서버가 그 보장을 지키는지는 재지 않았다.\",\"basisVersion\":\"Linux O_DIRECT 와 QEMU/libvirt disk 의 cache=none · cache=writeback 두 설정 기준\",\"projectId\":\"8a638411-2b21-4202-8bf0-720180d77897\",\"topicId\":\"23314be2-7350-44f5-8989-575c3b40898f\",\"variantIds\":[]},\"currentValidation\":null,\"latestPreview\":null,\"currentPublication\":null,\"dependencyRevision\":\"2916288f9b6cd496a26cf24ed8b9cc58\",\"nextAction\":\"VALIDATE\"},\"ecaaa37d-7b33-4cbf-9bc5-4c6caad14ee5\":{\"document\":{\"kind\":\"CONCEPT\",\"title\":\"VM 아래에 한 번 더 있는 Host block stack — blk-mq · I/O Scheduler · NVMe\",\"slug\":\"host-block-stack-under-the-vm\",\"summary\":\"게스트가 디스크라고 부르는 것이 호스트에서는 qcow2 나 RAW 파일일 수 있고, 그러면 QEMU 의 쓰기는 호스트 파일시스템을 거쳐 호스트 블록 I/O 가 된다. 호스트 블록 계층은 그 요청이 가상 머신 안의 PostgreSQL 에서 시작했는지 호스트 프로세스에서 시작했는지를 본질적으로 구분해 처리하는 계층이 아니다. 들어온 것은 모두 호스트 블록 요청이기 때문에 가상 머신 두 대와 호스트의 Nginx 와 나머지 프로세스가 같은 큐로 들어온다. blk-mq 가 CPU 마다 큐를 두어 병렬로 처리하고, I/O 스케줄러가 들어온 순서 그대로 장치에 전달하지 않을 수 있다. 여기서 생기는 경쟁이 스토리지 경쟁(Storage Contention)이고, 호스트 논리 CPU 실행 시간을 두고 벌어지는 CPU 경쟁과는 다투는 자원이 다르다. 블록 계층과 스케줄러 이름을 아는 사람을 독자로 둔다. 가상 머신 위에서 Keycloak 을 돌리며 지연을 볼 때 이 계층을 CPU 계층과 갈라 두면 어느 쪽을 재야 하는지가 먼저 정해진다.\",\"relations\":[{\"id\":\"91c4d509-baa7-41ea-84a6-aeac590e2d4e\",\"targetId\":\"3b05c1f0-13e9-414a-97a6-2078fb4bab26\",\"reason\":\"virtqueue 까지가 게스트 쪽 경로이고, 이 글은 그 요청이 가상 머신 경계를 넘어온 뒤를 잇는다.\",\"order\":0},{\"id\":\"befaa946-e039-4108-a292-c035d25b7d82\",\"targetId\":\"892b6087-8961-486c-b4d9-d3dd46bc2605\",\"reason\":\"백엔드가 파일인지 블록 장치인지에 따라 이 글이 설명하는 계층 위에 호스트 파일시스템이 한 겹 더 놓인다.\",\"order\":1},{\"id\":\"300e4a2c-b88b-4b50-8c81-d2e10d6aa849\",\"targetId\":\"8fafaeea-8746-4537-87a4-679a3a91aa4c\",\"reason\":\"같은 계층을 영속성 쪽에서 본 글이다. 이 글은 요청이 어떻게 줄을 서고 누구와 섞이는지를 본다.\",\"order\":2},{\"id\":\"1221c412-9b32-4124-8906-fd5724a29a4c\",\"targetId\":\"b1cda8c4-d31d-4ae4-a4d9-4103a0ab3109\",\"reason\":\"이 글이 가른 두 경쟁을 진단 순서로 편 규칙이다.\",\"order\":3},{\"id\":\"5cab72f8-3092-48bd-a036-768b633db00b\",\"targetId\":\"eead2379-94fe-463a-9e5e-06a01bf2105d\",\"reason\":\"이 호스트의 디스크 이미지가 어느 블록 장치 위에 있는지 SSOT 에 적혀 있지 않다.\",\"order\":4}],\"id\":\"ecaaa37d-7b33-4cbf-9bc5-4c6caad14ee5\",\"version\":3,\"updatedAt\":\"2026-09-09T07:07:17.106537Z\",\"bodyMarkdown\":\"## 게스트 디스크 파일이 호스트 블록 요청이 되는 곳\\n\\n백엔드가 `qcow2` 파일이면 QEMU 가 물리 SSD 를 직접 제어하지 않는다. QEMU 는 `pread` 나 `pwrite` 같은 호출로 호스트 커널에 파일 I/O 를 요청하고, 그 요청이 호스트 파일시스템과 호스트 블록 계층, NVMe 드라이버를 지나 물리 NVMe 에 닿는다. 게스트 스토리지 계층 아래에 호스트 스토리지 계층이 한 번 더 존재할 수 있다.\\n\\n호스트 블록 계층은 그 I/O 가 가상 머신 안의 PostgreSQL 에서 시작했는지 호스트 프로세스에서 시작했는지를 본질적으로 구분해서 처리하는 계층이 아니다. 내려온 것은 모두 호스트 블록 요청이다.\\n\\n| 호스트 쪽 어느 계층인가 | 여기서 무엇이 일어나나 |\\n|---|---|\\n| 호스트 파일시스템 | `qcow2` 나 RAW 파일을 읽고 쓰는 일이 파일 I/O 로 처리된다 |\\n| 호스트 블록 계층 | 어디서 왔는지 구분하지 않고 모두 블록 요청으로 다룬다 |\\n| `blk-mq` | CPU 마다 큐를 두어 여러 CPU 가 병렬로 블록 I/O 를 처리한다 |\\n| I/O 스케줄러 | 들어온 순서 그대로가 아니라 정책에 따라 장치로 내보낸다 |\\n| NVMe 드라이버 | 호스트 커널의 장치 드라이버로서 NVMe 컨트롤러에 요청을 보낸다 |\\n| NVMe 컨트롤러 | PCIe 를 통해 요청을 받아 플래시에 반영한다 |\\n\\n## 한 NVMe 로 여러 곳의 요청이 들어온다\\n\\n가상 머신 둘의 QEMU 프로세스와 호스트의 Nginx, 그리고 호스트의 다른 프로세스에서 동시에 I/O 가 들어올 수 있다. VM1 이 X 를 쓰고 Y 를 읽고 Z 를 쓰는 동안 VM2 는 A 를 읽고 B 를 쓰고, 호스트 프로세스는 C 를 읽는 식이다. 이 요청들은 호스트 블록 계층의 큐에서 관리되다가 장치로 나간다.\\n\\n:::evidence key=\\\"vms-sharing-one-nvme-53ce295a\\\" alt=\\\"VM1 QEMU 와 VM2 QEMU, Nginx, Host 기타 네 곳에서 나온 화살표가 하나의 Host Block Layer 로 모이고 거기서 NVMe 로 dispatch 되는 수렴 흐름도. VM1 QEMU 아래에는 WRITE X 와 READ Y, WRITE Z 가, VM2 QEMU 아래에는 READ A 와 WRITE B 가 적혀 있고, Host Block Layer 는 그것들을 모두 Host block request 로 다룬다.\\\" caption=\\\" \\\" zoom=\\\"true\\\"\\n:::\\n\\n## `blk-mq` 는 CPU 마다 큐를 둔다\\n\\n현대 Linux 의 블록 계층은 `blk-mq` 로 되어 있다. CPU0 부터 CPU3 까지 각 CPU 가 자기 큐를 가지고 그 큐들이 NVMe 로 이어진다. NVMe 가 높은 병렬성과 큐 깊이를 지원하기 때문에 여러 CPU 가 병렬로 블록 I/O 를 처리할 수 있는 구조가 중요해진다. 그래서 스토리지 처리 성능은 CPU 스케줄링과 완전히 분리되지 않는다.\\n\\n## I/O 스케줄러는 들어온 순서 그대로 내보내지 않는다\\n\\n여러 I/O 요청이 있어도 항상 들어온 순서 그대로 장치에 전달되지는 않는다. 요청은 I/O 스케줄러를 지나 장치 드라이버로 나간다. 스케줄러마다 목적과 내보내는 정책이 다르다. 대표적으로 볼 수 있는 값은 `none` 과 `mq-deadline` 과 `bfq` 다.\\n\\n`none` 을 고르면 복잡한 스케줄링 정책을 최소화해서 비교적 직접 장치 쪽으로 내보낸다. NVMe 처럼 장치 자체가 강한 병렬성과 큐 기능을 가진 경우에는 이런 단순한 정책이 적합할 수 있다. 그렇게 골랐다고 블록 계층이 아무 일도 하지 않지는 않는다.\\n\\n지금 선택된 스케줄러는 `sysfs` 에서 읽는다.\\n\\n```bash label=\\\"호스트의 현재 I/O Scheduler\\\"\\ncat /sys/block/nvme0n1/queue/scheduler\\n```\\n\\n출력이 `[none] mq-deadline` 이면 대괄호 안의 `none` 이 지금 선택된 스케줄러다. SATA 나 SCSI 장치라면 `cat /sys/block/sda/queue/scheduler` 처럼 장치 이름을 바꿔 읽는다.\\n\\n여기 든 `[none] mq-deadline` 은 SSOT 가 읽는 법을 보이려고 든 출력이다. 이 호스트에서 그 명령을 아직 돌리지 않아서 이 장비의 스케줄러가 무엇인지는 이 글이 말하지 않는다.\\n\\n## NVMe 드라이버는 호스트 커널의 장치 드라이버다\\n\\n호스트 블록 계층에서 I/O 스케줄러를 지난 요청은 NVMe 드라이버로 가고, NVMe 컨트롤러를 거쳐 물리 저장장치에 닿는다. NVMe 드라이버는 호스트 Linux 커널의 장치 드라이버다. 네트워크에서 물리 NIC 드라이버가 하드웨어를 제어하는 것과 같은 계층에 놓인다.\\n\\n이름이 비슷해 섞이는 두 낱말도 여기서 갈린다. SSD 는 저장장치의 넓은 종류를 가리키고, NVMe 는 PCIe 기반 비휘발성 저장장치를 위한 프로토콜이자 인터페이스다. SSD 아래에서 SATA SSD 는 SATA 와 AHCI 를 쓴다. NVMe SSD 는 PCIe 와 NVMe 를 쓴다. NVMe SSD 에서는 Linux NVMe 드라이버가 PCIe 를 통해 NVMe 컨트롤러에 요청을 보낸다. 컨트롤러는 플래시를 다룬다.\\n\\n## 스토리지 경쟁과 CPU 경쟁은 다투는 자원이 다르다\\n\\n여러 가상 머신이 같은 물리 NVMe 를 쓰면 스토리지 자원 경쟁이 생길 수 있다. VM1 의 PostgreSQL 과 VM2 의 Keycloak 이 요청한 I/O 가 같은 호스트 블록 계층과 같은 I/O 큐를 지나 하나의 NVMe 로 가기 때문에, VM1 에서 대량 I/O 가 발생하면 VM2 의 스토리지 지연이 올라갈 수 있다. 이렇게 벌어지는 경쟁을 스토리지 경쟁(Storage Contention)이라고 한다.\\n\\n```text label=\\\"두 경쟁이 다투는 자원\\\"\\nCPU Contention\\n→ Host logical CPU 실행 시간 경쟁\\n\\nStorage Contention\\n→ IOPS / bandwidth / queue / device 처리시간 경쟁\\n```\\n\\nCPU 경쟁은 호스트 논리 CPU 의 실행 시간을 두고 벌어지고, 스토리지 경쟁은 IOPS 와 대역폭, 큐, 장치 처리시간을 두고 벌어진다. 애플리케이션에서는 둘 다 「느리다」로 관찰되지만 다투는 자원이 달라서 따로 잰다.\\n\\n## 이 호스트에서 확인하지 않은 것\\n\\n이 글에도 이 테스트 호스트에서 잰 값은 없다. 이 호스트의 I/O 스케줄러가 무엇으로 설정돼 있는지 SSOT 에 없고, 가상 머신들의 디스크 이미지가 최종적으로 어느 호스트 블록 장치 위에 있는지도 없다. 가상 머신 두 대와 호스트 프로세스의 I/O 가 이 환경에서 실제로 같은 시간에 겹치는지도 재지 않았다. VM1 의 대량 I/O 가 VM2 의 스토리지 지연을 올린다는 것은 이 계층 구성에서 나오는 가능성이고 이 서버에서 관측한 값이 아니다. 이 글은 요청이 호스트에서 어떤 순서로 어느 계층을 지나는지까지 설명하고, 이 서버가 그 계층에서 실제로 막히는지는 재지 않았다.\",\"basisVersion\":\"현대 Linux 의 blk-mq 기반 block layer 와 NVMe driver · scheduler 는 none · mq-deadline · bfq 기준\",\"projectId\":\"8a638411-2b21-4202-8bf0-720180d77897\",\"topicId\":\"23314be2-7350-44f5-8989-575c3b40898f\",\"variantIds\":[]},\"currentValidation\":null,\"latestPreview\":null,\"currentPublication\":null,\"dependencyRevision\":\"ed30c3be7b7390d91844ced8b39e0694\",\"nextAction\":\"VALIDATE\"}}"