本番でI/O遅延を調査するときbpftraceが有効な理由
iostatやiotopはI/Oの平均スループットやプロセス単位の使用量を把握するには十分ですが、「どのリクエストが遅いのか」「遅延がストレージ側で起きているのかファイルシステム側で起きているのか」という問いには答えられません。平均値を見ていると、ごく一部のリクエストが数百ミリ秒かかっていても全体の数字に埋もれてしまいます。
bpftraceはLinuxカーネルのeBPFインフラを利用し、カーネル内部のイベントにリアルタイムでプローブを差し込める動的トレーシングツールです。オーバーヘッドは計測対象のイベント頻度によりますが、ブロックI/O系のプローブであれば本番トラフィック下でも通常数パーセント以下に収まるケースが多く、短時間の計測であれば実用的に使える水準です。再起動もカーネルパッチも不要で、稼働中のシステムに後から計測を差し込めるのが最大の特長です。
ペタバイト級のストレージを扱うクラウドネイティブ環境でも、データベースを抱えるオンプレミスのベアメタルサーバーでも、遅延原因の仮説検証ツールとして現場での採用が広がっています。
プローブ選択の考え方──block系とvfs系をどう使い分けるか
I/O遅延を調査するうえで最初に決めるべきことは、スタックのどの層を観測するかです。bpftraceのプローブはカーネルの各レイヤーに対応しており、層を誤ると「遅延はあるが原因が分からない」という状態に陥ります。
ブロック層(block:)プローブ
block:block_rq_issueはブロックデバイスにリクエストが発行された瞬間、block:block_rq_completeはそのリクエストが完了した瞬間をとらえます。2つの差分がデバイス側の待ち時間、つまりディスクやSSDのレイテンシそのものを示します。アプリケーションの体感遅延がブロック層より大きい場合、ファイルシステムやVFS層にボトルネックがある可能性が高まります。
VFS層(kprobe:/kfunc:)プローブ
ファイルシステムよりも高い抽象度で観測したい場合はkprobe:vfs_readやkprobe:vfs_writeを使います。ここでの遅延にはページキャッシュの有無、ファイルシステムのジャーナリング処理、dentry/inode のロック待ちが混在します。block層との比較によって、遅延がキャッシュミスに起因するのかデバイス自体に起因するのかを切り分けられます。
調査の基本的な順序は「block層で全体のレイテンシ分布を確認→外れ値があればVFS層や特定プロセスに絞って深掘り」です。最初からVFS層だけを見てもデバイス側の遅延と混同しやすいため、block層から着手することを推奨します。
計測スクリプトの実例と実行手順
以下にブロックI/Oのレイテンシを分布(ヒストグラム)で表示する典型的なbpftraceスクリプトを示します。
#!/usr/bin/env bpftrace
BEGIN {
printf("Tracing block I/O latency... Hit Ctrl-C to end.\n");
}
tracepoint:block:block_rq_issue {
@start[args->dev, args->sector] = nsecs;
}
tracepoint:block:block_rq_complete {
$key = (args->dev, args->sector);
if (@start[$key]) {
$lat = (nsecs - @start[$key]) / 1000; /* usec */
@usecs = hist($lat);
delete(@start[$key]);
}
}
END {
clear(@start);
}
実行はsudo bpftrace latency.btで行います。数十秒から数分間計測してCtrl-Cで終了すると、レイテンシのヒストグラムが標準出力に表示されます。特定のデバイスに絞りたい場合はargs->devを条件でフィルタリングします。
ワンライナーでの簡易確認であれば次の形でも同様の情報が得られます。
sudo bpftrace -e '
tracepoint:block:block_rq_issue { @s[args->dev, args->sector] = nsecs; }
tracepoint:block:block_rq_complete / @s[args->dev, args->sector] / {
@us = hist((nsecs - @s[args->dev, args->sector]) / 1000);
delete(@s[args->dev, args->sector]);
}'
実行権限にはroot、またはCAP_BPFとCAP_PERFMON(カーネル5.8以降)が必要です。非rootでの実行が求められる環境では、コンテナセキュリティポリシーの見直しが先決になります。
計測結果の読み方と遅延原因の切り分け
ヒストグラムの出力は対数スケールのバケットで表示されます。たとえば以下のような出力が得られたとします。
@us:
[0] 12 | |
[1] 834 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@ |
[2, 4) 1204 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ |
[4, 8) 987 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ |
[8, 16) 423 |@@@@@@@@@@@@@@@@ |
[16, 32) 89 |@@@ |
[32, 64) 12 | |
[64, 128) 3 | |
[128, 256) 1 | |
大半のリクエストが1〜8マイクロ秒台に収まっており、これはNVMe SSDの典型的な応答時間です。問題なのは128〜256マイクロ秒のバケットにわずかながら外れ値があるケースです。この外れ値がアプリケーションのタイムアウトやスロークエリと時間的に一致していれば、ストレージ側の問題と判断できます。
外れ値がblock層には現れず、VFS層のkprobeでは見える場合は、ページキャッシュの退避(writeback)やファイルシステムのロック競合を疑います。ext4のジャーナリングやXFSのAGロック、またはinode再利用のタイミングで遅延スパイクが出ることがあります。
さらに絞り込みたいときはcomm変数でプロセス名をフィルタし、特定のデーモン(mysqldやpostgresなど)のI/Oだけを抽出する方法が有効です。プロセスのPIDをキーにして@start[tid, args->sector]のように書き換えるだけで対応できます。
対処判断と切り戻しの基準
計測結果から原因を絞り込んだ後の対処は、遅延の発生箇所によって方針が変わります。
デバイス側に遅延がある場合
block層のヒストグラムに外れ値が集中しているときは、まずストレージのキュー深度とスケジューラーを確認します。cat /sys/block/sda/queue/schedulerでmqd-deadlineやkyberが設定されているか確認し、データベースワークロードであればnoneかmqd-deadlineが一般的な推奨です。I/Oスケジューラーの変更はシステム再起動なしにecho mqd-deadline > /sys/block/sda/queue/schedulerで即時反映でき、効果がなければ同様に切り戻せます。
ファイルシステム・VFS層に遅延がある場合
writebackストームが疑われる場合は/proc/sys/vm/dirty_ratioとdirty_background_ratioの値を下げてwritebackをより頻繁かつ小規模に実行させる調整が有効です。ただし変更前の値を記録し、問題が解決しない場合は元の値に戻せるよう手順をメモしておくことが重要です。本番環境では変更をsysctlの設定ファイルに反映せず、まず動的な変更で効果を確認してから永続化するのが安全な順序です。
bpftrace自体の切り戻しはCtrl-Cでプログラムを停止するだけで完了します。プローブはプログラム終了時に自動でデタッチされ、カーネルの動作に影響を残しません。計測によって問題が拡大するリスクは原則としてありませんが、プローブ数が多すぎると計測自体のオーバーヘッドが無視できなくなるため、本番では計測対象を絞った小規模なスクリプトを使うことを推奨します。
2026年現行環境での注意点
2026年時点で主要ディストリビューションのデフォルトカーネルはLinux 6.8〜6.12系に移行しています。この世代ではeBPFのverifierが強化されており、以前は動いていたスクリプトが「プログラムが複雑すぎる」と拒否されるケースが報告されています。特にループを含むスクリプトや、マップ操作が多いスクリプトで出やすい問題です。エラーが出た場合はbpftrace 0.21以降への更新で対応できるケースが多く、パッケージマネージャー経由のインストールよりもGitHubのリリースから最新バイナリを取得する方が現行カーネルへの追従が早い傾向があります。
Ubuntu 24.04 LTSとDebian 13(Trixie)ではUnprivileged BPFがデフォルトで無効化されており、rootまたはCAP_BPF権限が必須です。RHELおよびAlmaLinux 9系ではkernel.perf_event_paranoidの値によってはkprobeの利用に追加の設定が必要になる場合があります。コンテナ環境でbpftraceを動かす際はホストのカーネルと同一バージョンのヘッダーが参照できるかどうかを事前に確認してください。DaemonSetとして展開する構成ではsecurityContextのprivileged: trueまたはCapabilitiesの明示的な付与が必要になります。
また、tracepointインターフェイスはkprobeよりも安定しており、カーネルバージョンをまたいでも引数名が変わりにくい特長があります。長期運用を見越したスクリプトを書くのであれば、可能な限りtracepointベースで実装し、kprobeは補完的に使う構成が保守しやすいです。本記事のサンプルスクリプトもtracepointを主体にしているのはこの理由からです。
「コマンドは打てる。次は”現場で通用する型”を最短で身につけたい」——そんなあなたへ。
ネットの断片的なTipsをコピペするだけでは、体系的な運用スキルはなかなか積み上がりません。リナックスマスター.JPの無料メルマガ「リナマガ」では、現場で実際に使うLinuxサーバー運用の考え方を、順を追って実務目線でお届けしています。いま登録された方には、安全なサーバー構築の「型」をまとめた『Linuxサーバー構築入門マニュアル(図解60P)』を完全無料でプレゼント中です。
>> 図解60P『Linux入門マニュアル』を無料で受け取る
(メルマガ登録は10秒・いつでも解除OK)
※ 「独学の時間がもったいない」「プロから現場の技術を最短で学びたい」という本気の方には、2日で実務レベルが身につく初心者向けハンズオンセミナーもご用意しています。
PR・広告
コマンド操作から基本運用までを手を動かして学べる定番書。記事で触れた技術の土台固めに。
※ Amazonのアソシエイトとして、当サイトは適格販売により収入を得ています。
