午前2時のディスク・メルトダウン
時刻は午前2時。稼働中のPostgreSQLデータベースが突然限界を迎えました。定期的なバックアップスクリプトが実行された途端、クエリのレイテンシが15msから450msへと急上昇したのです。ユーザーの画面では読み込み中を示すホイールが回り続け、監視ダッシュボードは高いI/Oウェイト(I/O wait)を示す赤いアラートで埋め尽くされています。
バックアップスクリプトはCPUやRAMをそれほど消費しているわけではありません。単にディスクを占有しているだけです。これは典型的な「ディスク競合(disk contention)」の事例です。デフォルトの設定では、Linuxはすべてのプロセスを公平に扱おうとしますが、本番サーバーにおける「公平さ」は、しばしばすべてのアプリケーションが等しくパフォーマンス低下に苦しむことを意味します。バックアップを後回しにし、データベースに絶対的な優先権を与える必要があります。ここで、I/Oスケジューリングとioniceが真価を発揮します。
Linuxがデータトラフィックを管理する仕組み
ターミナルを操作する前に、カーネルがデータの移動をどのように処理しているかを理解する必要があります。複数のアプリケーションが同時に読み取りや書き込みを要求した際、I/Oスケジューラーが交通整理の役割を果たし、どのリクエストを優先的にディスクに送るかを決定します。
最新のマルチキュー(Multi-Queue)アーキテクチャ
古いカーネルはCFQ(Completely Fair Queuing)のようなスケジューラーに依存していました。最新のカーネル(4.12以降)では、SSD向けに最適化され、毎秒数百万ものオペレーションを処理できるマルチキュー・ブロックレイヤー(blk-mq)が採用されています。主に以下の4つのスケジューラーを目にすることになるでしょう。
- mq-deadline: 多くのサーバーにおける安全なデフォルト設定。書き込みよりも読み取りを優先し、データの待機によるアプリケーションのフリーズを防ぎます。
- bfq (Budget Fair Queuing): 高機能なスケジューラー。CPUをわずかに消費しますが、大規模なバックグラウンドコピー中でもシステムの応答性を維持する能力に非常に長けています。
- kyber: FacebookがハイエンドのNVMeストレージ向けに開発。読み取りレイテンシを2ms以下などの目標値に厳密に抑えることに重点を置いています。
- none: カーネルのオーバーヘッドがメリットを上回ってしまうような、超高速なNVMeドライブで使用されます。
ioniceは、ディスクのための「nice」コマンド
スケジューラーがディスク全体を管理するのに対し、ioniceは特定のプロセスの優先度を設定します。これはCPUにおけるniceコマンドのディスク版です。主に3つのクラスがあります。
- Idle(クラス3): 他にディスクを必要とするプロセスがない場合のみ動作します。バックアップやログローテーションに最適です。
- Best-effort(クラス2): 標準のデフォルト。優先レベルは0〜7で、0が最高優先度です。
- Real-time(クラス1): 即座にディスクアクセス権が与えられます。暴走したプロセスがOS全体のディスク時間を奪う可能性があるため、慎重に使用してください。
実践編:本番サーバーのチューニング
これらの概念を適用するには、現在のハードウェアのボトルネックを特定する必要があります。理論から、稼働中のシステムにおけるアクティブな最適化へと進みましょう。
ステップ1:アクティブなスケジューラーの確認
ディスクごとに異なるスケジューラーを使用できます。sysfsファイルシステムに問い合わせて、どれが有効かを確認します。sdaの部分を、nvme0n1やvdaなどのドライブ名に置き換えてください。
cat /sys/block/sda/queue/scheduler
出力結果には、[mq-deadline] kyber bfq noneのように表示されます。角括弧 [] で囲まれている名前が、現在制御しているスケジューラーです。
ステップ2:再起動なしでのスケジューラーの切り替え
スケジューラーは即座に切り替え可能です。大量の小さな書き込みが並列で発生しているファイルサーバーを管理している場合、bfqに切り替えることで即座に状況が改善されることがよくあります。
# BFQを最初のSATAドライブに適用する
echo bfq | sudo tee /sys/block/sda/queue/scheduler
ステップ3:ioniceによるバックグラウンドタスクの優先度下げ
これはシステム管理者にとって最も効果的なテクニックです。500GBのrsyncバックアップを実行する場合、Webサーバーの動作を妨げないように、Idleクラスで実行しましょう。
# '-c 3' フラグにより、rsyncをアイドルプールに配置する
ionice -c 3 rsync -av /var/www/ /backup/www/
プロセスがすでに実行中でシステムを遅延させている場合は、そのPIDを見つけて、その場でスロットリング(制限)をかけることができます:
# プロセス 1234 を最高優先度 (0) の 'Best-effort' に強制設定する
sudo ionice -c 2 -n 0 -p 1234
ステップ4:変更を永続化する
/sys/block/への直接の変更は、再起動すると消失します。設定を固定するには、/etc/udev/rules.d/60-scheduler.rulesにudevルールを作成します:
# HDD(回転型ディスク)にはbfqを使用する
ACTION=="add|change", KERNEL=="sd[a-z]*", ATTR{queue/rotational}=="1", ATTR{queue/scheduler}="bfq"
# 標準的なSSDにはmq-deadlineを使用する
ACTION=="add|change", KERNEL=="sd[a-z]*", ATTR{queue/rotational}=="0", ATTR{queue/scheduler}="mq-deadline"
# 高速なNVMeにはnoneを使用する
ACTION=="add|change", KERNEL=="nvme[0-n]*", ATTR{queue/scheduler}="none"
現実的なチェック:セットアップのテスト
速そうだからという理由だけでスケジューラーを選ばないでください。ハードウェアのパフォーマンスは予測不可能です。50以上のベアメタルノードを管理してきた私の経験では、SamsungのエンタープライズSSDで優れた結果を出した設定が、安価なクラウドプロバイダーのボリュームでは制限(スロットル)の原因になることもあります。
必ずベンチマークを行ってください。fioを使用して、特定のワークロードをシミュレートします。データベースの場合は、IOPSと99パーセンタイル・レイテンシを追跡してください。メディアサーバーの場合は、MB/s単位のスループットを重視します。
バックアップスクリプトの実行中に模擬負荷をかけてみてください。バックアップがionice -c 3でバックグラウンドで動いている間、データベースのレイテンシが20ms以下に保たれていれば、システムのチューニングは成功です。
まとめ
ディスクI/Oの最適化とは、魔法の「ターボボタン」を見つけることではありません。リソースの割り当てが重要です。データベースには低レイテンシの読み取りが、ファイルサーバーには高スループットの書き込みが必要です。そしてバックグラウンドタスクは、邪魔にならないようにする必要があります。まずは現在のスケジューラーを確認することから始め、次回の大きなファイル転送にはioniceを使い、udevルールで最適化を定着させましょう。ユーザーはシステムの応答性の違いにすぐ気づくはずです。

