NVMe サーバで I/O スケジューラが問題になる場面
HDD 時代に設計された CFQ(Completely Fair Queuing)は、ディスク回転数やシーク時間を前提とした調停ロジックを持っていました。NVMe デバイスはハードウェア内部にキューを持ち、並列 I/O に対して本質的に強い構造です。にもかかわらず、ソフトウェア側でスケジューラが余計な並べ替えや待機を挟むと、レイテンシが増加したり CPU サイクルが無駄に消費されたりします。
実際に現場で問題が表面化しやすいのは、データベース(PostgreSQL・MySQL)の IOPS が伸び悩む場面や、コンテナオーケストレータが emptyDir を NVMe に置いたときのレイテンシ変動です。OS のデフォルト設定のまま本番投入し、後から「なぜかレイテンシが不安定」と調査が始まるケースは珍しくありません。
本記事では、none(カーネル内部のパススルー)と mq-deadline(マルチキュー対応の期限スケジューラ)を中心に、ワークロード別の選択指針とベンチマーク測定の進め方、そして万一のロールバック設計までを一本で整理します。
Linux I/O スケジューラの現在地:mq-deadline・none・bfq
カーネル 5.0 以降、旧来のシングルキュー(sq)スケジューラは削除され、現在はマルチキュー(blk-mq)アーキテクチャのみが残ります。2026 年時点で主要なディストリビューションが提供するスケジューラは次の 3 種です。
- none:スケジューリングをほぼ行わず、デバイスキューへ直接投入する。NVMe のように内部コントローラが高度な並列処理を持つ場合に最大スループットを狙える。
- mq-deadline:各 I/O にソフトウェア期限を設け、長時間待たせないよう優先度を操作する。読み書き混在ワークロードや、レイテンシの上限保証が必要な DB 向きとされる。
- bfq(Budget Fair Queuing):プロセス単位の帯域公平制御。デスクトップ・マルチテナント環境向けで、純粋スループット重視の本番サーバでは選ぶ場面が少ない。
RHEL 9 / Rocky Linux 9 は NVMe に対してデフォルトで none を適用します。Ubuntu 22.04 LTS 以降も同様に none がデフォルトです。一方、古い RHEL 8 系では mq-deadline がデフォルトに残っているケースがあり、アップグレード後に設定が変化していないか確認が必要です。
現在の設定確認と一時切り替え手順
まず対象デバイスのスケジューラを確認します。デバイス名は nvme0n1 など NVMe 固有の命名になります。
# 現在のスケジューラ確認([] が現在選択中)
cat /sys/block/nvme0n1/queue/scheduler
# 一時切り替え(再起動で元に戻る)
echo mq-deadline | sudo tee /sys/block/nvme0n1/queue/scheduler
echo none | sudo tee /sys/block/nvme0n1/queue/scheduler
出力例として none [mq-deadline] kyber のように表示された場合、現在は mq-deadline が有効です。kyber は高並列 NVMe 向けのスケジューラですが、多くのディストリビューションでは標準ビルドに含まれていないため、表示されないことも多いです。
一時切り替えはベンチマーク比較の際に便利ですが、再起動で元に戻るため本番反映には使いません。本番への適用は後述の永続化手順で行います。
fio によるベンチマーク測定と結果の読み方
スケジューラの効果はワークロードによって逆転することがあります。「none のほうが常に速い」は誤りで、ランダムリード混在時に mq-deadline のほうがテールレイテンシ(p99)が安定するケースも報告されています。そのため、自分の環境・ワークロードで計測することが不可欠です。
代表的なテストパターンを示します。/dev/nvme0n1 を直接対象にする場合はデータが破壊されるため、必ず専用のテスト領域またはファイルを用意してください。
# シーケンシャルリード(スループット確認)
fio --name=seq-read \
--filename=/mnt/nvme_test/testfile \
--rw=read --bs=1M --size=4G \
--numjobs=4 --iodepth=32 \
--ioengine=libaio --direct=1 \
--runtime=30 --time_based \
--output-format=json \
--output=seq_read_none.json
# ランダム混在(DB ワークロード模倣)
fio --name=randrw \
--filename=/mnt/nvme_test/testfile \
--rw=randrw --rwmixread=70 --bs=4k --size=4G \
--numjobs=8 --iodepth=64 \
--ioengine=libaio --direct=1 \
--runtime=60 --time_based \
--output-format=json \
--output=randrw_none.json
スケジューラを切り替えながら同じジョブを実行し、JSON 出力を比較します。注目すべき指標は、bw(帯域幅)よりも lat_ns の p99・p99.9(テールレイテンシ)です。スループットが近い場合でも、p99.9 レイテンシが 10 倍以上異なるケースがあり、DB のスロークエリ率に直結します。
簡易集計には jq が便利です。
jq '.jobs[0].read.lat_ns | {p99: .percentile."99.000000", p999: .percentile."99.900000"}' randrw_none.json
計測は 3 回以上実施して中央値を採用します。NVMe のサーマルスロットリングや OS のページキャッシュ効果が結果を歪めることがあるため、計測前に echo 3 | sudo tee /proc/sys/vm/drop_caches でキャッシュをクリアしておくことも検討してください。
永続化と udev ルールによるロールバック設計
本番環境への適用は udev ルール による永続化が現在の標準的な手法です。systemd の elevator カーネルパラメータを使う方法もありますが、デバイス個別に制御できる udev のほうが柔軟性があります。
# /etc/udev/rules.d/60-io-scheduler.rules
# NVMe デバイスに mq-deadline を適用する場合
ACTION=="add|change", KERNEL=="nvme[0-9]n[0-9]", ATTR{queue/rotational}=="0", \
ATTR{queue/scheduler}="mq-deadline"
# none に変更したい場合は上記の mq-deadline を none に書き換える
ルール追加後は sudo udevadm control --reload-rules && sudo udevadm trigger を実行して即時反映させます。再起動後も有効です。
ロールバック手順は次の 3 ステップです。
- udev ルールファイルを削除またはコメントアウトする。
sudo udevadm control --reload-rules && sudo udevadm triggerを実行し、OS デフォルト値に戻す。cat /sys/block/nvme0n1/queue/schedulerで選択が外れていることを確認する。
変更前のルールファイルを /etc/udev/rules.d/60-io-scheduler.rules.bak として退避しておくと、再適用が容易です。Ansible や Chef など構成管理ツールを使っている環境では、ルールファイルをコード管理し、変更時に pull request 経由でレビューするフローが安全です。
なお、/sys/block/nvme0n1/queue/nr_requests(キュー深さ)や read_ahead_kb(先読みサイズ)も同一の udev ルール内でチューニング可能です。スケジューラ変更と合わせて評価することで、単体変更よりも大きな効果が得られる場合があります。
2026 年時点の環境差分と注意点
カーネル 6.x 系では io_uring を使ったアプリケーションが増えており、スケジューラの影響を受けにくい経路が広がっています。PostgreSQL 16 以降で実験的に io_uring パスが使われ始めており、将来的にはスケジューラチューニングの効果が薄れる方向に向かう可能性があります。ただし 2026 年 10 月現在、io_uring 経由の I/O が主流になっているわけではなく、ブロックデバイス層のスケジューラ選定は依然として有効な最適化手段です。
RHEL 9 / Rocky Linux 9 では tuned プロファイルがデバイスのスケジューラを上書きすることがあります。tuned-adm active で現在のプロファイルを確認し、throughput-performance や latency-performance が有効な場合は、udev ルールとの競合を防ぐため tuned 側の設定も確認してください。
Ubuntu 24.04 LTS(カーネル 6.8)では、NVMe デバイスに対して none がデフォルトになっており、積極的に変更する場面は限られます。一方でクラウド環境(AWS Nitro の NVMe・GCP Persistent Disk NVMe)では、ホスト側ハイパーバイザがすでに I/O 調停を行っているため、ゲスト OS 側のスケジューラ変更効果が軽微になることも多く、計測による確認が特に重要です。
コンテナ環境では、ホスト OS のスケジューラ設定がコンテナ内のデバイスアクセスにも適用されます。Kubernetes ノードのチューニングは DaemonSet 経由で行うことが一般的ですが、hostPath や特権コンテナを使ったスケジューラ変更は権限管理の観点からリスクを伴うため、ノード初期化スクリプト(userdata・cloud-init)に組み込む方法が安全です。
「コマンドは打てる。次は”現場で通用する型”を最短で身につけたい」——そんなあなたへ。
ネットの断片的なTipsをコピペするだけでは、体系的な運用スキルはなかなか積み上がりません。リナックスマスター.JPの無料メルマガ「リナマガ」では、現場で実際に使うLinuxサーバー運用の考え方を、順を追って実務目線でお届けしています。いま登録された方には、安全なサーバー構築の「型」をまとめた『Linuxサーバー構築入門マニュアル(図解60P)』を完全無料でプレゼント中です。
>> 図解60P『Linux入門マニュアル』を無料で受け取る
(メルマガ登録は10秒・いつでも解除OK)
※ 「独学の時間がもったいない」「プロから現場の技術を最短で学びたい」という本気の方には、2日で実務レベルが身につく初心者向けハンズオンセミナーもご用意しています。
PR・広告
[試して理解]Linuxのしくみ 増補改訂版(Amazon)
プロセス・権限・ファイルシステムなどLinux内部を図解で理解できる一冊。運用判断の勘所に。
※ Amazonのアソシエイトとして、当サイトは適格販売により収入を得ています。
