KVM / QEMU のストレージ I/O 性能を見る時、ゲスト OS 内のアプリケーションだけを見ていると、重要な層を見落とします。仮想ディスクの I/O は、ゲスト OS、virtio、QEMU、ホスト kernel、ファイルシステム、物理ストレージを通って処理されます。
この記事で見ている io_uring は、ゲスト OS のアプリケーションが使う I/O API ではなく、ホスト側 QEMU が仮想ディスク I/O を処理する時の backend としての io_uring です。fio の ioengine はゲスト側では libaio のまま固定し、libvirt の disk driver に io='io_uring' を指定した場合と、指定しない場合を比較しています。
結論から言えば、この検証環境では 4KB randread / iodepth 32 の条件で IOPS と帯域が約 15% 改善し、p50 から p95 の latency も低下しました。ただし、これは io_uring を入れれば必ず速くなるという意味ではありません。QEMU、kernel、libvirt、ディスク形式、ストレージ backend、I/O パターンによって結果は変わります。
書籍
作って理解する仮想化技術 – ハイパーバイザを実装しながら仕組みを学ぶ
ハイパーバイザー、CPU 仮想化支援、メモリ仮想化、割り込み、仮想デバイスなど、VM の性能設計を低レイヤーから理解したい場合の参考書籍です。価格や在庫はリンク先で確認してください。
Amazon で見るこのリンクは Amazon アソシエイトリンクです。
どの層の io_uring を見ているのか
io_uring は Linux kernel が提供する非同期 I/O の仕組みです。ユーザー空間と kernel 空間の間で submission queue と completion queue を使い、I/O 要求と完了通知を効率よく扱うことを狙います。
ただし、KVM の文脈では、io_uring という言葉が複数の層に出てきます。ゲスト OS のアプリケーションが io_uring を使う場合と、ホスト側 QEMU が仮想ディスク I/O の backend として io_uring を使う場合は、同じ話ではありません。
| 層 | 今回の扱い | 意味 |
|---|---|---|
| ゲスト OS 内のアプリケーション | fio --ioengine=libaio のまま固定 | ゲスト側の I/O API は変えない |
| 仮想ディスク | qcow2 / virtio を固定 | ディスク形式や virtio 経路は同じにする |
| ホスト側 QEMU | io='io_uring' の有無を比較 | QEMU が使う I/O backend を変える |
| 評価対象 | ゲスト内 fio の結果 | IOPS、帯域、latency、CPU 使用率を見る |
今回の検証は、VM 内のアプリケーションを io_uring 化する話ではありません。QEMU からホスト kernel へ出ていく I/O 経路を変えることで、ゲストから見えるストレージ性能がどう変わるかを見るものです。
libvirt で io_uring を指定する
libvirt の domain XML では、disk の driver 要素に io 属性を指定できます。libvirt のドキュメントでは、QEMU guest の disk driver で threads、native、io_uring が扱われることが説明されています。
まず、ホスト kernel で io_uring が有効になっているかを確認します。
grep IO_URING /boot/config-$(uname -r)有効な kernel であれば、次のような出力になります。
CONFIG_IO_URING=yio_uring を指定しない場合
比較対象として、disk driver に io 属性を付けない構成を用意します。
<driver name='qemu' type='qcow2'/>io_uring を指定する場合
io_uring を使う場合は、disk driver に io='io_uring' を指定します。
<driver name='qemu' type='qcow2' io='io_uring'/>実際に使う時は、対象 VM の XML を変更したあと、VM を停止して起動し直します。ディスク設定は稼働中に安全に差し替えられるとは限らないため、検証 VM で手順を確認してから本番 VM に適用します。
fio のテスト条件
テストはゲスト OS 上で fio を実行し、4KB のランダムリードを 60 秒間行いました。ゲスト側の ioengine は libaio のまま固定しています。
sudo fio --name=uringtest \
--filename=/tmp/test.img \
--size=4G \
--rw=randread \
--bs=4k \
--iodepth=32 \
--ioengine=libaio \
--direct=1 \
--numjobs=1 \
--time_based=1 \
--runtime=60 \
--group_reporting| オプション | 意味 |
|---|---|
--rw=randread | 4KB ランダムリードを実行する |
--bs=4k | 小さい I/O を高頻度に発行する |
--iodepth=32 | 同時に 32 個の I/O を発行する |
--ioengine=libaio | ゲスト側 I/O engine は libaio のまま固定する |
--direct=1 | ゲスト OS のページキャッシュの影響を減らす |
--runtime=60 | 60 秒間の time based テストにする |
テスト結果
この環境では、io_uring を有効化した場合に IOPS と帯域が約 15% 改善しました。平均 latency だけでなく、p50、p70、p90、p95 も低下しています。
| 指標 | io_uring 無効 | io_uring 有効 | 差分 |
|---|---|---|---|
| IOPS | 8,213 | 9,425 | +14.7% |
| 帯域 | 32.1 MiB/s | 36.8 MiB/s | +14.6% |
| 平均 latency | 3.89 ms | 3.39 ms | -0.50 ms |
| p50 | 3,490 us | 2,933 us | -557 us |
| p70 | 6,194 us | 5,538 us | -656 us |
| p90 | 8,455 us | 7,767 us | -688 us |
| p95 | 9,503 us | 8,717 us | -786 us |
| sys CPU | 18.29% | 28.70% | +10.41% |
| disk util | 99.88% | 99.90% | ほぼ同等 |
結果の読み方
IOPS と帯域は改善した
今回の条件では、IOPS と帯域はどちらも約 15% 改善しました。4KB randread / iodepth 32 のように小さい I/O を多く発行する条件では、QEMU の I/O backend を io_uring にすることで、ホスト側 I/O 経路の効率が改善した可能性があります。
latency の分布も下がった
平均値だけではなく、p50 から p95 までの latency が下がっている点も重要です。これは一部の極端な値だけではなく、通常時からやや遅い側までの応答性が改善していることを示します。
sys CPU は増えている
一方で、sys CPU は 18.29% から 28.70% に増えています。これは必ずしも悪い結果ではありません。I/O をより多く処理できるようになった結果として kernel 側の CPU 使用率が増え、その代わりに IOPS と latency が改善している可能性があります。
ただし、ホスト CPU に余裕がない場合は、この sys CPU 増加が別の制約になります。io_uring を標準設定にする前に、CPU 使用率、steal、iowait、VM 数、同居ワークロードを合わせて見る必要があります。
io_uring が効きやすい条件
この検証だけで一般化はできませんが、io_uring は小さい I/O が多く、QEMU 側の I/O 経路が効いている VM で効果を期待しやすいと考えられます。
- 小さいランダム I/O が多い VM
- qcow2 など QEMU 側の処理を通る構成
- ホスト CPU に余裕があり、I/O 処理を増やせる構成
- ストレージ backend が完全には詰まっていない構成
- p95 / p99 latency を下げたいワークロード
逆に、物理ストレージやネットワークストレージが先に詰まっている場合、大きな sequential I/O が中心の場合、CPU が先に飽和している場合は、同じ効果が出るとは限りません。
標準設定にする前に確認すること
io_uring は有力な選択肢ですが、KVM 環境で無条件に標準化するものではありません。少なくとも次の観点を確認してから適用するべきです。
- QEMU、libvirt、kernel が io_uring を安定して扱えるバージョンか
- raw、qcow2、block device、RBD、ZFS など backend ごとの差を測ったか
- IOPS だけでなく p95 / p99 latency を見たか
- sys CPU の増加をホストが許容できるか
- 本番 VM と同じ I/O パターンで測定したか
- トラブル時に元の disk driver 設定へ戻せる手順があるか
VM パフォーマンス設計の中での位置づけ
io_uring は、KVM のストレージ性能を決める唯一の要素ではありません。virtio、キャッシュ設定、ディスク形式、IOThread、ホスト filesystem、物理ストレージ、NUMA、CPU pinning など、複数の要素が重なって VM の I/O 性能が決まります。
その中で io_uring は、QEMU がホスト側で I/O をどう発行するかに関わる設定です。ゲスト内のアプリケーションを変える技術ではなく、VM の外側にある QEMU の I/O backend を変える設定として扱うと、評価範囲を間違えにくくなります。
まとめ
この検証環境では、QEMU の disk driver に io='io_uring' を指定することで、4KB randread の IOPS と帯域が約 15% 改善し、p50 から p95 の latency も低下しました。
ただし、同時に sys CPU は増えています。io_uring は単純な高速化スイッチではなく、ホスト側 QEMU の I/O 処理方式を変える設計項目です。効果は QEMU、kernel、libvirt、ストレージ backend、ディスク形式、I/O パターンに依存します。
KVM のストレージ I/O を見る時は、ゲスト側の fio 結果だけではなく、QEMU がどの I/O backend を使い、ホスト側でどの資源を消費しているかまで含めて判断する必要があります。
参考情報
関連する記事
あわせて読みたい:

