Ubuntu + KVM 環境でスナップショットを取った後、削除しても性能が戻らない問題と、その仕組み・対処法のまとめ。
- 1. スナップショットの種類
- 2. なぜスナップショット削除後も遅いままなのか
- 3. DB の dead tuple 問題とのアナロジー
- 4. 解消方法
- 5. 運用のベストプラクティス
- 6. 参考リンク
- TL;DR
1. スナップショットの種類
KVM/libvirt のスナップショットには大きく2種類ある。
| 種類 | 仕組み | Cockpit からの作成 |
|---|---|---|
| Internal | .qcow2 ファイル内部にスナップショットデータを埋め込む |
✅ デフォルト(種類を選ぶ UI なし) |
| External | 元ファイルを read-only にし、差分を別の qcow2 オーバーレイに書き込む | ❌ Cockpit 不可(virsh が必要) |
Cockpit 経由で取られたスナップショットは 基本的に internal と考えてよい。裏で virsh snapshot-create-as のデフォルト動作(internal)が走っている。
2. なぜスナップショット削除後も遅いままなのか
これが本題。直感的には「使っていない領域が残っているだけ」に思えるが、実際にはちゃんと性能に影響する理由がある。
2.1 ファイルサイズが縮まない
internal スナップショットを virsh snapshot-delete や qemu-img snapshot -d で消しても、qcow2 はクラスタを「未使用」とマークするだけで、ファイルシステムには領域を返さない。
QEMU 開発者の公式回答(libvirt-users ML): "QEMU will just mark the clusters as unused, it won't return them to the underlying filesystem."
つまり メタデータ上は消えていても、物理的なファイルサイズは肥大化したまま。
2.2 L2 キャッシュがカバーしきれなくなる
qcow2 は「仮想ディスク上のオフセット → ファイル内の物理オフセット」を引くために L1/L2 テーブルというインデックス構造を持つ。
QEMU はこの L2 テーブルを RAM 上にキャッシュしているが、デフォルトの L2 キャッシュサイズは 1MB で、これは仮想ディスク 8GB ぶんしかカバーできない(Alberto García / Igalia による解説)。
ファイルが肥大化
↓
必要な L2 メタデータが増える
↓
キャッシュに収まらない部分が出る
↓
ディスクから L2 を毎回読み直し
↓
I/O レイテンシが増える
2.3 refcount-block のチェックコスト
qcow2 は破損検出のため書き込み時に複数のサニティチェックを行う。中でも refcount-block チェックは qcow2 ファイルの実サイズに比例してコストが増える(小さなイメージでも比較的高コスト)。
これも「ファイルが大きいほど遅くなる」要因のひとつ。
2.4 ホスト側のファイルシステム断片化
qcow2 ファイル自体が大きくなれば、ホストのファイルシステム上で断片化が進みやすくなる。シーケンシャルに見える読み取りが、実際は物理的にバラバラの場所を辿っていることになる。
2.5 ページキャッシュの圧迫
ホストの RAM 上のページキャッシュに qcow2 全体が乗らなくなる → キャッシュミスが増える → 実ディスクアクセスが増える。
3. DB の dead tuple 問題とのアナロジー
この現象は PostgreSQL などの DB で起きる「論理削除と物理解放のズレ」と構造的に同じ。
| DB(PostgreSQL等) | KVM/qcow2 |
|---|---|
| テーブルの dead tuple | qcow2 の未解放クラスタ |
| インデックスの肥大化 | L1/L2 テーブルの肥大化 |
| VACUUM | virt-sparsify |
| REINDEX / pg_dump+restore | qemu-img convert |
| autovacuum | TRIM/UNMAP の自動解放 |
共通する本質:
「論理的に空き」と「物理的に解放済み」が
自動的には一致しない
↓
管理メタデータが肥大化したまま残る
↓
検索・参照のたびにそのコストを払い続ける
4. 解消方法
4.1 推奨手順(セットで実施)
# 1. VM を停止 virsh shutdown <domain> # 2. 念のためバックアップ cp /var/lib/libvirt/images/vm.qcow2 /backup/vm.qcow2.bak # 3. 残っているスナップショットを確認 qemu-img snapshot -l /var/lib/libvirt/images/vm.qcow2 # 4. スナップショットを削除(メタデータレベル) qemu-img snapshot -d <snapshot_name> /var/lib/libvirt/images/vm.qcow2 # 5. qemu-img convert で新クリーンイメージに変換(ここで物理的に縮む) qemu-img convert -O qcow2 vm.qcow2 vm-new.qcow2 # 6. 差し替え mv vm-new.qcow2 vm.qcow2 # 7. オーナー・パーミッションを戻す(必要に応じて) chown libvirt-qemu:kvm vm.qcow2 chmod 600 vm.qcow2
4.2 なぜ「セット」なのか
qemu-img snapshot -dだけ → メタデータは消えるが物理ブロックは残るqemu-img convertだけ → スナップショットも含めて全部コピーされるので残ってしまう
両方やって初めて完全にクリーンになる。
4.3 補助的な手段
virt-sparsify── ゲスト内の空き領域(ゼロブロック)をホストに返す。VMware のゼロフィル+shrink と本質的に同じ。- TRIM/UNMAP 対応 ── libvirt XML で
discard='unmap'を設定し、ゲストから発行された TRIM をホスト側で qcow2 のクラスタ解放に反映させる(最近の環境向け)。
5. 運用のベストプラクティス
① 作業前にスナップショット取得
↓
② 検証・作業・必要ならロールバック
↓
③ 「これで確定」となったらスナップショット削除
↓
④ qemu-img convert で新クリーンイメージに変換
↓
⑤ 差し替えて運用再開
DB で言えば「作業ブランチでやって、確定したら pg_dump & restore でクリーンにする」のと同じ感覚。
注意点
qemu-img convert中は VM を停止する必要がある- イメージサイズによっては時間がかかる(数十 GB で数十分)
- 変換後しばらくは元ファイルをバックアップとして残しておくと安全
- スナップショットチェーンを 2〜3 個以上残さないのが定石
6. 参考リンク
理論・メカニズム
Alberto García (Igalia) — Improving disk I/O performance in QEMU 2.5 with the qcow2 L2 cache https://blogs.igalia.com/berto/2015/12/17/improving-disk-io-performance-in-qemu-2-5-with-the-qcow2-l2-cache/ QEMU の qcow2 メンテナ本人による解説。L2 キャッシュと性能の関係。
Alberto García (Igalia) — QEMU and the qcow2 metadata checks https://blogs.igalia.com/berto/2017/02/08/qemu-and-the-qcow2-metadata-checks/ refcount-block など、qcow2 のサニティチェックがどう性能に効くか。
実用的影響
Blockbridge — Inside Proxmox VE 9 SAN Snapshot Support https://kb.blockbridge.com/technote/proxmox-qcow-snapshots-on-lvm/ スナップショットチェーンの長さによる劣化、削除しても領域が解放されない問題を明記。
ProxmoxR Blog — Snapshot Management: Best Practices https://proxmoxr.com/blog/proxmox-snapshot-management チェーン深度ごとの読み取りレイテンシ増加(5-15%)を定量化。
公式回答(QEMU 開発者)
- libvirt-users ML — after snapshot-delete, the qcow2 image file size doesn't decrease https://libvirt-users.redhat.narkive.com/aYDuKBDK/after-snapshot-delete-the-qcow2-image-file-size-doesn-t-decrease 「削除してもクラスタは未使用マークされるだけで返却されない」「in-place では解決不可、qemu-img convert しかない」と明言。
実例・症例
Proxmox Forum — QCOW2 Snapshot Deletion Causes VM To Temporarily Lose Ethernet Connection https://forum.proxmox.com/threads/qcow2-snapshot-deletion-causes-vm-to-temporarily-lose-ethernet-connection.77385/ 4TB qcow2 のスナップショット削除で VM が 15 分以上応答停止した実例。
Proxmox Forum — qcow2 snapshot creation takes too long https://forum.proxmox.com/threads/qcow2-snapshot-creation-takes-too-long.99184/ 「削除後も裏で重い I/O が数分続いた」という生々しい報告。
手順
- Slack Notebook — How to shrink qcow2 on Linux (日本語) https://slacknotebook.com/how-to-shrink-qcow2-on-linux/ qemu-img convert によるシュリンクとスナップショット削除をセットで解説。
TL;DR
- Cockpit で取るスナップショットは internal(qcow2 ファイル内埋め込み)
- 削除してもクラスタは「未使用」マークされるだけで物理的には残る
- ファイルが肥大化すると L2 メタデータがキャッシュに収まらず性能が落ちる
- 完全に元に戻すには スナップショット削除 +
qemu-img convertの セットでの実施が必要 - 構造的には DB の dead tuple / インデックス肥大化と同じ問題