기록 84편을 계약 에이전트로 다시 썼다. 기존 71편(kss 25 · virt 46)과, 계약에만 있고 안 쓰여 있던 새 글감 13편이다. 원장 84개를 열어 단계마다 스킬 영수증과 관문 종료 코드를 적었고 verify-pipeline-run.py 가 error 0 으로 닫는다. SSOT 결함 둘을 고쳤다. - kss 의 `약 58일` 이 반입 중 `약 59일` 로 바뀌어 있었다. 원 증거 파일이 「남은 일수: 88일 … 실제 갱신까지 약 58일」로 산수를 직접 적는다. D-4a 쪽 `약 59일` 은 강제 갱신 뒤(`VALID: 89 days`)라 맞는 값이라 그대로 뒀다. - virt §198 의 `11.6GB` 는 §178 의 원 측정 `Mem: 11648`(MiB)과 어긋나는데 원 가이드의 표기 그대로라 고치지 않고 쓰이는 자리에 대조를 적었다. 기록의 수치 오류 셋을 고쳤다 — CASE 요약의 「게스트 셋에 8240MB」(5120+3120 은 둘이다), k3s 편이 같은 것을 여섯·일곱·여덟로 세던 것, no-docker 편의 「셋을 더 든다」(§281 의 표는 네 행이고 디스크 행이 빠져 있었다). 계약을 셋 고쳤다. - kss 의 sourceRepository 리비전이 cdac9b8 이었는데 그 커밋에는 docs/guides/** 28개가 아예 없다. 9465582b 로 바꾸고, 반입한 바이트가 어느 커밋과도 같지 않다는 것을 측정값과 함께 적었다 — 반입은 커밋이 아니라 그 시점의 작업 트리에서 떠 온 것이다(kss 297/306 · virt 12/14 가 작업 트리와 같고, 200 커밋을 거슬러 전수 대조했을 때 가장 가까운 커밋도 28개가 어긋났다). - virt 계약이 「2026-09-11 재배분」이라고 적는데 SSOT 는 재배분 날짜를 적지 않고 재배분 뒤 값은 이미 2026-09-10 측정에 찍혀 있다. - kss 후보 대장이 지나친 절 아홉에 처분을 적었다(warn 9 → 0). 새 글감은 0건이고 넷은 앵커가 h3 슬러그의 접두가 아니라 중간 토막이라 검사기가 못 본 것이었다. style_profile.mjs 의 결함 둘을 고쳤다 — frontmatter 가 문장으로 세어져 (실측 398자짜리 「문장」 하나) 평균 길이를 기준 안으로 밀어 올리고 있었고, engPerSent 의 분자는 목록을 포함한 글에서, 분모는 목록을 걷어낸 글에서 세고 있었다(Question 기록에서 11.94 → 3.86). verify-pipeline.py 전 항목 PASS · error 0 · unittest 334건 OK. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
11 KiB
id, kind, slug, title, topic, topicName, project, status, basisVersion, studio, sourceRevision, source
| id | kind | slug | title | topic | topicName | project | status | basisVersion | studio | sourceRevision | source | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 892b6087-8961-486c-b4d9-d3dd46bc2605 | CONCEPT | qemu-block-backend-forms | /dev/vda 뒤에 있는 것 — qcow2 · RAW · Host block device | storage-virtualization | 스토리지 가상화 | virtualization | 초안 | QEMU block backend 세 형태 — qcow2 파일 · RAW 파일 · Host block device. libvirt 로 정의한 VM 기준이고 제4부가 QEMU 와 libvirt 판을 고정하지 않았다 | https://hyeonworks.com/studio/documents/892b6087-8961-486c-b4d9-d3dd46bc2605/edit | no-commit · 밖에서 반입한 문서 한 편. 고정할 저장소 리비전이 없다 |
|
/dev/vda 뒤에 있는 것 — qcow2 · RAW · Host block device
게스트 안에서 /dev/vda 를 보는 것만으로는 호스트에 무엇이 있는지 알 수 없다. 가상 머신 경계를 넘으면 QEMU 의 virtio-blk 장치 모델과 블록 백엔드가 그 요청을 받는데, 그 백엔드는 qcow2 파일일 수도, RAW 파일일 수도, 호스트의 실제 블록 장치일 수도 있다. 파일 백엔드면 게스트 스토리지 계층 아래에 호스트 파일시스템과 블록 계층이 한 겹 더 놓이고, qcow2 면 게스트가 보는 크기와 호스트가 실제로 쓰는 크기가 갈린다. 무엇에 붙어 있는지는 게스트가 아니라 호스트에서 virsh domblklist 로 확인한다. 가상 머신에 디스크를 붙여 본 사람을 독자로 둔다.
관계
- Guest 의 write() 가 virtqueue 에 실리기까지 — VFS · Filesystem · Page Cache · Block Layer · virtio-blk 게스트 안에서 virtqueue 까지 내려온 요청을 이 글이 이어받는다.
- 완료라는 말의 네 가지 뜻 — write · writeback · flush · 전원 장애 생존 파일 백엔드면 호스트 페이지 캐시가 한 겹 더 생기고, 그 위에서 완료가 무엇을 보장하는지가 갈린다.
- VM 아래에 한 번 더 있는 Host block stack — blk-mq · I/O Scheduler · NVMe 이 글은 백엔드 형태까지만 본다. 그 아래에서 요청이 어떻게 줄을 서는지는 그 글이 본다.
- Guest 안에서 본 disk 로 backend 를 단정하지 않는다 이 글이 적은 세 갈래가 그 규칙의 근거다.
- 이 호스트의 /dev/vda 는 어떤 backend 에 붙어 있는가 세 갈래 가운데 어느 것인지를 그 물음이 이 장비에서 출력으로 확정한다.
- 이 disk image 는 qcow2 인가 RAW 인가, 그리고 Guest 가 보는 크기와 Host 가 실제로 쓰는 크기는 얼마나 다른가 이 글이 든 크기 차이는 SSOT 의 예시 값이고, 이 장비의 값은 그 질문이 잰다.
- disk image 는 최종적으로 어느 Host block device 위에 있는가 파일 백엔드라면 그 파일이 놓인 호스트 장치까지 따라가야 경로가 끝난다.
본문
가상 머신 경계를 넘으면 QEMU 가 받는다
게스트의 virtio-blk 가 virtqueue 에 게시한 요청은 가상 머신 경계를 넘어 호스트 사용자 공간의 QEMU 로 간다. QEMU 안에서는 virtio-blk 장치 모델과 블록 백엔드가 그 요청을 받는다. QEMU 는 게스트에게 가상 블록 장치를 노출하고, 게스트의 가상 I/O 를 호스트 백엔드에 연결한다.
SSOT 는 제4부의 전체 구조를 그린 뒤 이 갈림을 핵심 문장 하나로 따로 뽑아 두었다.
Guest는
/dev/vda를 실제 block device처럼 보지만, Host에서는 그 disk가 qcow2 파일, RAW 파일, 또는 실제 block device에 연결되어 있을 수 있다.
이 글이 다루는 것이 그 문장이 남긴 세 갈래다.
QEMU 가 물리 SSD 를 직접 제어하지는 않는다
백엔드가 qcow2 파일이면 QEMU 는 결국 호스트 Linux 에 파일 I/O 를 요청한다. pread 나 pwrite 같은 호출이 호스트 커널로 들어가고, 그 뒤로 호스트 파일시스템과 호스트 블록 계층, NVMe 드라이버를 지나 물리 NVMe 에 닿는다. 그 경로가 호스트 커널을 한 번 더 지나기 때문에, 게스트 스토리지 계층 아래에 호스트 스토리지 계층이 한 번 더 존재할 수 있다.
같은 대상을 호스트는 파일로, 게스트는 디스크로 본다
호스트에 /var/lib/libvirt/images/keycloak-node1.qcow2 라는 파일이 있다고 하자. 호스트 관점에서 그것은 파일 하나인데, 같은 대상을 게스트는 /dev/vda 라는 디스크로 보고 그 안을 /dev/vda1 과 /dev/vda2 로 나눠 쓴다. 두 관점 모두 맞다.
qcow2 는 게스트가 보는 크기와 호스트가 쓰는 크기가 다르다
qcow2 는 가상 디스크 크기와 실제 호스트 할당량이 다를 수 있다. SSOT 가 든 예시에서는 게스트가 /dev/vda 를 100 GB 로 보는 동안 호스트의 vm1.qcow2 가 3GB 를 쓴다. 게스트가 데이터를 기록하면서 가상 크기 100GB 는 그대로인 채 실제 할당량이 1GB 에서 10GB 로, 다시 40GB 로 늘 수 있다. 이 숫자들은 SSOT 가 설명하려고 든 값이고 이 호스트에서 잰 값이 아니다.
qemu-img info vm1.qcow2
출력의 virtual size 와 실제 할당량은 서로 다른 값이다.
SSOT 의 실측 절이 2026-09-10 에 이 호스트의 바닥 이미지에 그 명령을 돌린 출력을 남겼다. file format: qcow2 에 virtual size: 3 GiB (3221225472 bytes) 이고 disk size: 335 MiB 다. 같은 절의 목록에서 20GB 로 선언한 게스트 오버레이 둘은 1521025024 와 697499648 바이트, 곧 1.4 GiB 와 665 MiB 였다.
바닥 쪽 차이가 안 쓴 공간만으로 생기지는 않는다. SSOT 의 qcow2 내부 절이 같은 파일에 qemu-img map --output=json 을 돌려 compressed: True 인 구간을 606개, 전체를 1236개로 셌다. 3 GiB 가운데 2.01 GiB 가 구멍이고 남은 1010 MiB 를 zlib 로 압축해 324 MiB 가 된다. 배포본 이미지가 배포 시점에 이미 압축된 채로 오기 때문이고, 게스트가 그 클러스터에 쓰면 압축하지 않은 형태로 오버레이에 새로 할당된다. 바닥은 작은데 오버레이가 상대적으로 커 보이는 이유 가운데 하나가 그것이라고 SSOT 는 적었다.
RAW 와 qcow2 를 성능으로 먼저 가르지 않는다
RAW 는 qcow2 보다 구조가 단순하다. 게스트의 블록 요청이 qcow2 에서는 QEMU 의 매핑과 메타데이터 처리를 지나 qcow2 파일 I/O 가 되고, RAW 에서는 상대적으로 직접적인 오프셋 대응을 지나 RAW 파일 I/O 가 된다. qcow2 는 Copy-on-Write 와 sparse allocation, 스냅샷에 유리하지만 그 대신 매핑과 메타데이터 처리가 붙는다.
그렇다고 RAW 가 무조건 빠르고 qcow2 가 무조건 느리다고 일반화하면 안 된다. SSOT 는 실제 성능이 캐시 모드와 스토리지 백엔드, 작업 부하 패턴, 큐 깊이, 스냅샷 체인, 그 아래 파일시스템, 물리 장치에 영향을 받는다고 적었다. 형식 이름만으로는 어느 쪽이 빠른지 정해지지 않는다.
그 목록 가운데 스냅샷 체인이 왜 걸리는지는 SSOT 의 qcow2 내부 절이 따로 풀었다. 매핑 항목이 0 인 클러스터를 만날 때마다 한 층 아래로 내려가야 해서 체인이 깊으면 읽기가 느려지고, 그래서 이 실험대는 층을 두세 겹 넘게 쌓지 않는다. 굳힐 때는 qemu-img commit 으로 아래층에 병합하거나 qemu-img convert 로 단일 파일로 평탄화한다.
백엔드가 파일이 아니라 호스트 블록 장치일 수도 있다
백엔드는 파일이 아니어도 된다. 게스트의 /dev/vda 가 virtio-blk 와 QEMU 를 지나 호스트의 /dev/nvme0n1p3 같은 블록 장치에 바로 연결될 수 있고, SSOT 가 그린 이 경로에는 호스트 파일시스템이 없다. 같은 이름 아래에서 경로가 세 갈래로 갈리기 때문에, 게스트에 /dev/vda 가 있다는 정보만으로는 백엔드 구조를 알 수 없다.
세 갈래가 각각 바꾸는 것
| 호스트에서는 무엇인가 | 그래서 무엇이 달라지나 |
|---|---|
qcow2 파일 |
게스트 파일시스템 아래에 호스트 파일시스템과 블록 계층이 한 겹 더 놓인다. Copy-on-Write 와 sparse allocation, 스냅샷을 쓸 수 있고 매핑과 메타데이터 처리가 붙는다. 게스트가 보는 가상 크기와 호스트 실제 할당량이 갈린다 |
| RAW 파일 | 게스트 파일시스템 아래에 호스트 파일시스템과 블록 계층이 한 겹 더 놓인다. 블록 위치가 파일 오프셋에 상대적으로 직접 대응한다 |
| 호스트 블록 장치 | SSOT 가 그린 경로에 호스트 파일시스템이 없다 |
무엇에 붙어 있는지는 호스트에서 확인한다
게스트에서 lsblk 로 장치를 보고, 호스트에서 virsh domblklist 로 그 장치의 Source 를 본다.
lsblk
virsh domblklist <VM_NAME>
Target Source
-----------------------------------------------
vda /var/lib/libvirt/images/vm1.qcow2
이 출력이 나오면 게스트의 /dev/vda 가 virtio-blk 와 QEMU 를 지나 /var/lib/libvirt/images/vm1.qcow2 에 닿는 연결이 확인된다.
확인하지 못한 것
이 글은 백엔드가 셋으로 갈린다는 데까지 말하고, 이 호스트가 그중 무엇인지는 여기서 정하지 않는다. SSOT 의 실험대 구축 절과 실측 절이 게스트마다 base.qcow2 위의 오버레이 파일을 붙였다고 적었으므로 파일 쪽 갈래이지만, 그 대응을 virsh domblklist 로 읽은 출력은 아직 없다. 형식이 무엇인지, 가상 크기와 실제 사용량이 얼마나 다른지, 그 이미지가 어느 호스트 블록 장치 위에 있는지는 관계로 걸어 둔 질문 셋이 출력으로 확정한다.
앞에서 든 100 GB 와 3GB, 1GB 에서 40GB 까지의 증가는 SSOT 가 설명하려고 든 예시 값이고 이 호스트에서 잰 값이 아니다. 이 호스트에서 qemu-img info 가 돌아간 것은 바닥 base.qcow2 하나여서, 오버레이 둘은 ls -l 의 파일 크기로만 남아 있고 그 안의 virtual size 는 읽히지 않았다.
qcow2 와 RAW 의 성능 비교도 이 저장소에 없다. SSOT 는 실제 성능을 가르는 조건만 열거했고 어느 쪽이 이 환경에서 빠른지는 재지 않았다.