手当たり次第に書くんだ

飽きっぽいのは本能

io_uring を有効化すると KVM はどれだけ速くなるのか

概要

本記事では、KVM 仮想マシンにおけるディスク I/O パフォーマンスをテーマに、io_uring を有効化した場合と無効化した場合で、どれほど実際の数値に差が出るのかを検証しています。

テストでは、ゲスト OS 側のアプリケーションは従来どおり libaio を利用しつつ、ホスト側の QEMU に対してのみ io_uring を有効化するという構成で比較を実施しました。

io_uring についてはこちらでも説明しています。

io_uring とは

Linux カーネルが提供する 次世代の高速 I/O インターフェースです。

従来の AIO(async I/O)では実現できなかった効率的なノンブロッキング I/O を、ユーザー空間とカーネル空間の間に「リングバッファ(queue)」を置く仕組みで実現します。

主に以下の改善により、I/O のレイテンシとスループットが全体的に改善されます。

  • システムコールの回数を減らす
  • コピー処理などの無駄を減らす
  • I/O の完了通知が高速になる

QEMU における io_uring の有効化と無効化

今回、KVM 上の VM に対して io_uring を有効にします。まず、KVM 側で io_uring を利用可能か確認します。

grep IO_URING /boot/config-$(uname -r)

以下の出力であれば問題ありません。

CONFIG_IO_URING=y

次に、XML を編集して io_uring を有効化します。KVM 上の既存の VM: io-uring-test をテストに使用しました。

  <devices>
    <emulator>/usr/bin/qemu-system-x86_64</emulator>
    <disk type='file' device='disk'>
      <driver name='qemu' type='qcow2'/>
      <source file='/var/lib/libvirt/images/io-uring-test.qcow2'/>
      <target dev='vda' bus='virtio'/>
      <address type='pci' domain='0x0000' bus='0x03' slot='0x00' function='0x0'/>
    </disk>

devices.disk type.driver を変更することで、io_uring の有効化と無効化を切り替えることができます。

無効化時:

<driver name='qemu' type='qcow2'/>

有効化時:

これにより、QEMU プロセス(ホスト側)の I/O パスに io_uring が使用されます。

<driver name='qemu' type='qcow2' io='io_uring'/>

今回、virsh edit コマンドを使用して書き換えました。

sudo virsh edit io-uring-test

変更後、以下のように VM を再起動します。

virsh reboot io-uring-test

テストコマンド

テストコマンドは fio コマンドを使用しました。これはゲスト OS 上で実行します。

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
オプション意味 / 役割ポイント / 解説
--name=uringtestジョブの名前ログや出力の見分けに使用される識別名
--filename=/tmp/test.imgテスト対象のファイルここに指定したファイルへ I/O を実行。存在しない場合は自動作成
--size=4Gテスト対象ファイルのサイズ4GB の test.img を対象にランダム READ を行う。容量は I/O パターンに影響
--rw=randread読み書きのパターンランダムリード。ランダム I/O の性能を見るために一般的
--bs=4kブロックサイズ4KB 単位で READ を発行。実運用に近い設定
--iodepth=32発行するキュー深度一度に 32 個の I/O を飛ばす。ディスクや virtio の並列性能が見える
--ioengine=libaio使う I/O エンジンここは aio ベース(ゲスト側)。io_uring の比較ではここを固定
--direct=1OS バッファを bypass するページキャッシュを使わず、純粋なディスク性能を測定
--numjobs=1並列ジョブ数1 ジョブのみ。CPU コアやスレッドを増やすと IOPS も変わる
--time_based=1時間で制御するモード何バイト読むかではなく、一定時間読み続ける方式
--runtime=60テスト実行時間60 秒間連続で I/O を生成し続ける
--group_reportingグループ単位で結果をまとめるnumjobs を増やした時でも読みやすい一括出力にする

テスト結果

テスト結果は以下のようになりました。

io_uring 無効時

uringtest: (g=0): rw=randread, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=libaio, iodepth=32
fio-3.28
Starting 1 process
uringtest: Laying out IO file (1 file / 4096MiB)
Jobs: 1 (f=1): [r(1)][100.0%][r=23.8MiB/s][r=6086 IOPS][eta 00m:00s]
uringtest: (groupid=0, jobs=1): err= 0: pid=1646: Sat Nov 22 14:03:41 2025
  read: IOPS=8213, BW=32.1MiB/s (33.6MB/s)(1925MiB/60008msec)
    slat (usec): min=3, max=7963, avg=16.49, stdev=26.93
    clat (nsec): min=1581, max=60802k, avg=3877259.54, stdev=3523016.01
     lat (usec): min=17, max=60811, avg=3894.00, stdev=3525.64
    clat percentiles (usec):
     |  1.00th=[   28],  5.00th=[   46], 10.00th=[   66], 20.00th=[  135],
     | 30.00th=[  603], 40.00th=[ 1795], 50.00th=[ 3490], 60.00th=[ 4948],
     | 70.00th=[ 6194], 80.00th=[ 7242], 90.00th=[ 8455], 95.00th=[ 9503],
     | 99.00th=[12125], 99.50th=[13566], 99.90th=[18220], 99.95th=[21365],
     | 99.99th=[43254]
   bw (  KiB/s): min=20456, max=174104, per=100.00%, avg=32954.39, stdev=25474.00, samples=119
   iops        : min= 5114, max=43526, avg=8238.56, stdev=6368.49, samples=119
  lat (usec)   : 2=0.01%, 4=0.01%, 10=0.01%, 20=0.11%, 50=5.99%
  lat (usec)   : 100=10.32%, 250=8.15%, 500=3.89%, 750=3.60%, 1000=2.72%
  lat (msec)   : 2=6.32%, 4=12.10%, 10=43.18%, 20=3.55%, 50=0.06%
  lat (msec)   : 100=0.01%
  cpu          : usr=5.85%, sys=18.29%, ctx=210097, majf=0, minf=44
  IO depths    : 1=0.1%, 2=0.1%, 4=0.1%, 8=0.1%, 16=0.1%, 32=100.0%, >=64=0.0%
     submit    : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0%
     complete  : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.1%, 64=0.0%, >=64=0.0%
     issued rwts: total=492859,0,0,0 short=0,0,0,0 dropped=0,0,0,0
     latency   : target=0, window=0, percentile=100.00%, depth=32

Run status group 0 (all jobs):
   READ: bw=32.1MiB/s (33.6MB/s), 32.1MiB/s-32.1MiB/s (33.6MB/s-33.6MB/s), io=1925MiB (2019MB), run=60008-60008msec

Disk stats (read/write):
    dm-0: ios=492420/169, merge=0/0, ticks=1881168/1288, in_queue=1882456, util=99.96%, aggrios=493367/164, aggrmerge=0/5, aggrticks=1891895/1155, aggrin_queue=1893179, aggrutil=99.88%
  vda: ios=493367/164, merge=0/5, ticks=1891895/1155, in_queue=1893179, util=99.88%

io_uring 有効時

uringtest: (g=0): rw=randread, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=libaio, iodepth=32
fio-3.28
Starting 1 process
uringtest: Laying out IO file (1 file / 4096MiB)
Jobs: 1 (f=1): [r(1)][100.0%][r=28.3MiB/s][r=7236 IOPS][eta 00m:00s]
uringtest: (groupid=0, jobs=1): err= 0: pid=1642: Sat Nov 22 14:14:26 2025
  read: IOPS=9425, BW=36.8MiB/s (38.6MB/s)(2209MiB/60008msec)
    slat (usec): min=3, max=12701, avg=23.28, stdev=44.30
    clat (nsec): min=1376, max=31411k, avg=3367318.26, stdev=3263867.26
     lat (usec): min=17, max=31425, avg=3390.92, stdev=3266.57
    clat percentiles (usec):
     |  1.00th=[   24],  5.00th=[   44], 10.00th=[   70], 20.00th=[  123],
     | 30.00th=[  221], 40.00th=[  603], 50.00th=[ 2933], 60.00th=[ 4359],
     | 70.00th=[ 5538], 80.00th=[ 6587], 90.00th=[ 7767], 95.00th=[ 8717],
     | 99.00th=[11207], 99.50th=[12518], 99.90th=[16188], 99.95th=[18220],
     | 99.99th=[23462]
   bw (  KiB/s): min=25624, max=94802, per=100.00%, avg=37792.75, stdev=15803.08, samples=119
   iops        : min= 6406, max=23700, avg=9448.03, stdev=3950.80, samples=119
  lat (usec)   : 2=0.01%, 4=0.01%, 10=0.03%, 20=0.40%, 50=5.70%
  lat (usec)   : 100=10.16%, 250=15.64%, 500=7.12%, 750=1.80%, 1000=0.79%
  lat (msec)   : 2=3.14%, 4=12.51%, 10=40.59%, 20=2.07%, 50=0.03%
  cpu          : usr=9.51%, sys=28.70%, ctx=201485, majf=1, minf=45
  IO depths    : 1=0.1%, 2=0.1%, 4=0.1%, 8=0.1%, 16=0.1%, 32=100.0%, >=64=0.0%
     submit    : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0%
     complete  : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.1%, 64=0.0%, >=64=0.0%
     issued rwts: total=565626,0,0,0 short=0,0,0,0 dropped=0,0,0,0
     latency   : target=0, window=0, percentile=100.00%, depth=32

Run status group 0 (all jobs):
   READ: bw=36.8MiB/s (38.6MB/s), 36.8MiB/s-36.8MiB/s (38.6MB/s-38.6MB/s), io=2209MiB (2317MB), run=60008-60008msec

Disk stats (read/write):
    dm-0: ios=565174/95, merge=0/0, ticks=1848428/72, in_queue=1848500, util=99.98%, aggrios=566004/90, aggrmerge=0/7, aggrticks=1862109/115, aggrin_queue=1862296, aggrutil=99.90%
  vda: ios=566004/90, merge=0/7, ticks=1862109/115, in_queue=1862296, util=99.90%

テスト結果の比較

KVM ホスト側の io_uring を有効化しただけで、ゲスト OS の IOPS とスループットが約 15% 程度向上しました。レイテンシも一貫して低下しており、特に p50〜p95 の改善が非常に明確です。これは「単なるピーク性能」ではなく、VM の日常的な I/O 応答そのものが速くなることを意味します。

指標io_uring 無効io_uring 有効差分
IOPS8,2139,425+14.7%
BW32.1 MiB/s36.8 MiB/s+14.6%
平均レイテンシ3.89 ms3.39 ms-0.5 ms
p503,490 µs2,933 µs-557 µs
p706,194 µs5,538 µs-656 µs
p908,455 µs7,767 µs-688 µs
p959,503 µs8,717 µs-786 µs
sys CPU18.29%28.70%+10.4%
disk util99.88%99.90%±

今回の fio の比較において、特に注目すべきなのは p50〜p95 のレイテンシが大幅に改善している点です。これらは平均値とは異なり、システムの “実際の応答性” を最もよく反映する指標です。

p50(中央値):
普段の I/O のレイテンシ。ここが下がるほど、OS の基本動作が軽くなります。

p95:
遅い側 5% を除いた領域。“時々重くなる” という現象やジッターを示すため、ワークロード安定性に強く関わります。

特に以下のような、細かい I/O が大量に発生する環境では p95 の低下が大きな体感改善につながります。

  • Kubernetes ノード
  • Filebeat / Loki / journald などのログ処理
  • CI/CD エージェント
  • 軽量 DB(SQLite / Redis などの混在環境)

今回の比較結果では、以下の変化が確認でき、I/O パスの最適化によって応答遅延が滑らかに抑えられています。

  • io_uring 無効: p50〜p95 のレイテンシが全体的に高め
  • io_uring 有効: 同区間が明確に低下し、レイテンシの「山」が削れる

IOPS やスループットの向上に加え、「I/O のばらつきが減る=体感が軽くなる」という効果が得られている点が非常に重要です。

また、sys CPU の使用率が 18.29% → 28.70% と増加していますが、これは性能悪化ではありません。io_uring は I/O をより積極的にカーネル側で処理するため、sys CPU が増えるのは設計上自然な挙動であり、その結果として I/O の待ち時間が削減され、レイテンシ(特に p50〜p95)が大きく改善しています。

総合評価

今回の検証により、io_uring を有効化すると qcow2 を使用している場合でも確実に性能が向上することが明確になりました。

特に注目すべき点は、ゲスト側のアプリケーションが io_uring に対応していなくても効果が表れるという事実です。fio では ioengine=libaio を指定していましたが、ホストの QEMU が io_uring を用いてストレージにアクセスすることで I/O パス上の無駄が削減され、そのまま性能改善に繋がっています。
つまり、アプリケーション側の対応状況に依存せず、ホスト側で io_uring を有効化するだけでメリットが得られるということになります。

特に以下のような用途では、有効化による効果が顕著に現れる傾向が確認できました。

  • 小さな I/O が高頻度で発生する VM(Kubernetes ノード、ログ集約、CI/CD、軽量 DB など)
  • qcow2 バックエンド(vda)を利用している環境
  • Ceph RBD や ZFS をバックエンドに持ち、ホスト側の I/O オーバーヘッドが大きくなりがちな構成

fio の結果では、IOPS とスループットが概ね 15% 程度向上し、レイテンシについても p50〜p95 を中心に全域で低下しました。特に tail latency(遅延が最も大きい I/O)が改善されており、I/O の応答性の安定化にも寄与しています。

総合すると、現在の KVM 基盤において io_uring の有効化は標準設定にしても問題ないレベルの信頼性と性能向上を示すと言えます。

io_uring を有効化すると KVM はどれだけ速くなるのか

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)

トップへ戻る