Gobble up pudding

プログラミングの記事がメインのブログです。

MENU

KVM qcow2 スナップショットと性能劣化の話

スポンサードリンク

Ubuntu + KVM 環境でスナップショットを取った後、削除しても性能が戻らない問題と、その仕組み・対処法のまとめ。


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-deleteqemu-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. 参考リンク

理論・メカニズム

実用的影響

公式回答(QEMU 開発者)

実例・症例

手順


TL;DR

  • Cockpit で取るスナップショットは internal(qcow2 ファイル内埋め込み)
  • 削除してもクラスタは「未使用」マークされるだけで物理的には残る
  • ファイルが肥大化すると L2 メタデータがキャッシュに収まらず性能が落ちる
  • 完全に元に戻すには スナップショット削除 + qemu-img convertセットでの実施が必要
  • 構造的には DB の dead tuple / インデックス肥大化と同じ問題