物理サーバーの NVMe 性能をどう検証するか:fio のファイルテスト
新しいサーバーでデータベースやファイルサービスを運用する場合、大きなファイルを 1 つコピーして得られた速度だけでは、ストレージの検証として不十分です。ファイルがキャッシュにヒットしている可能性もあれば、書き込みがまだ永続化されていない可能性もあり、バックグラウンド処理によって結果が変わることもあります。より信頼できる方法は、テストファイル、ブロックサイズ、同時実行条件を固定してスループットとレイテンシをそれぞれ確認し、再測定に使える生の記録を残すことです。
CloudUDP の物理サーバーには 1TB NVMe が 2 台標準搭載されており、それぞれが直接接続されています。ハードウェア RAID はありません。以下では Debian 13 を例に、I/O 負荷を設定できるテストツール fio を使い、指定したファイルシステムを測定します。ローデバイスへの書き込みは行わず、テスト結果を実際のサービス性能の保証として扱うこともしません。
1. テストの範囲と実際の保存先を確認する
ファイル単位のテストで測定するのは「ファイルシステムからストレージまで」の経路であり、ドライブ単体の限界性能ではありません。暗号化、ボリューム管理、ミラーリング、マウントオプションはいずれも結果に影響する可能性があります。2 台のドライブがそれぞれ直接接続されていても、特定のディレクトリが両方のドライブを同時に使うとは限りません。
まずツールをインストールし、対象ディレクトリを確認します。以下では /srv をテスト場所の候補としています。対象のファイルシステム上にない場合は、実際のマウント先ディレクトリに変更してください。
sudo apt-get install fio
findmnt -T /srv -o TARGET,SOURCE,FSTYPE,OPTIONS
lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINTS
df -h /srv
df -i /srv
fio --version
空き容量、マウント状態、ディレクトリの権限を確認します。ドライブの公称容量だけで判断してはいけません。ファイルシステムの管理領域や既存データも容量を消費します。テストファイルを最後まで書き込んだ後にも、サービスのデータ増加に備えた余裕を残す必要があります。
書き込みテストに
/dev/nvme…のようなデバイスパスを指定しないでください。専用ファイルを使えば誤って既存データを上書きするリスクは減らせますが、リソース競合は防げません。できるだけサービスの運用開始前に実施し、サービスのレイテンシに異常が出た場合、空き容量が事前に定めた下限に近づいた場合、または I/O エラーが発生した場合は、直ちに中止してください。
2. 専用ディレクトリを作成し、テストファイル全体を書き込む
すべてのコマンドを同じ Bash セッションで実行します。管理者権限で、現在の作業アカウントだけがアクセスできる一時ディレクトリを作成します。テストファイルと結果はすべてその中に置き、既存のサービス用ファイルに触れないようにします。
TESTDIR=$(sudo mktemp -d /srv/fio-acceptance.XXXXXX)
sudo chown "$(id -u):$(id -g)" "$TESTDIR"
chmod 700 "$TESTDIR"
export TESTDIR
printf '%s\n' "$TESTDIR"
findmnt -T "$TESTDIR"
この例では 1GiB のファイルを使います。これは手順を確認するための出発点であり、ストレージ全体の容量や長時間の定常状態での性能を代表するものではありません。fio の size=1G は二進単位で解釈されるため、デバイスの公称容量と同じ単位として計算してはいけません。空き容量が十分であることを確認したら、まずシーケンシャル書き込みでファイル全体を埋めます。
fio --name=prepare \
--filename="$TESTDIR/test.bin" \
--size=1G --rw=write --bs=1M \
--ioengine=libaio --direct=1 \
--iodepth=1 --numjobs=1 --end_fsync=1 \
--group_reporting --output-format=json \
--output="$TESTDIR/prepare.json"
終了ステータスと JSON 内のエラーフィールドは必ず確認してください。ダイレクト I/O がサポートされていない場合は、まずファイルシステムとマウント方法を確認します。黙ってキャッシュ経由の I/O に変更し、そのまま比較を続けてはいけません。準備段階でエラーがあれば、その後の読み取りテストには信頼できる前提がありません。
direct=1 は通常、ファイルデータのページキャッシュを迂回しますが、すべてのデバイスキャッシュを迂回するわけではありません。end_fsync=1 はジョブ終了時に同期を要求する設定であり、書き込みのたびにデータベースのコミットを再現するものではありません。
3. パラメータを固定し、ランダム読み取りとランダム書き込みを別々に測定する
まず単一ジョブでランダム読み取りを実行し、その後にランダム書き込みを実行します。混合負荷によって問題が見えにくくなるのを避けるためです。テスト中は、データのコピーや別のストレージテストを同時に実行しないでください。
fio --name=randread \
--filename="$TESTDIR/test.bin" \
--size=1G --rw=randread --bs=4k \
--ioengine=libaio --direct=1 \
--iodepth=8 --numjobs=1 \
--time_based --runtime=30 --ramp_time=5 \
--group_reporting --output-format=json \
--output="$TESTDIR/randread-q8.json"
fio --name=randwrite \
--filename="$TESTDIR/test.bin" \
--size=1G --rw=randwrite --bs=4k \
--ioengine=libaio --direct=1 \
--iodepth=8 --numjobs=1 \
--time_based --runtime=30 --ramp_time=5 \
--end_fsync=1 --group_reporting \
--output-format=json \
--output="$TESTDIR/randwrite-q8.json"
ここで指定する実行時間とウォームアップ時間は、あくまでテスト用のパラメータです。短時間のテストはデバイスキャッシュ、温度、バックグラウンドの整理処理の影響を受ける可能性があるため、この結果から長時間の書き込み性能を推定することはできません。
低いキュー深度で比較テストを追加する
読み取りテストの iodepth を 1 に変更し、出力ファイルを randread-q1.json に変更します。それ以外のパラメータは同じにします。キュー深度は、同時に未完了の状態で存在できる I/O リクエスト数を表します。CPU コア数とは異なり、サービスの同時接続数でもありません。
キュー深度を上げるとスループットが増える一方でテールレイテンシが大きく悪化する場合、その負荷ではスループットと待ち時間にトレードオフがあることを示しているにすぎません。データベースのコネクションプールにその値をそのまま適用しないでください。
4. スループット、IOPS、テールレイテンシを合わせて読む
IOPS は、1 秒間に完了した I/O の回数です。ブロックサイズが固定されていればスループットと対応関係がありますが、ブロックサイズが異なる結果を IOPS だけで順位付けしてはいけません。fio の JSON にある bw は通常 KiB/s で表されるため、読む際にはバージョンと各フィールドの単位を確認してください。
重点的に確認するのは、read または write 内の iops、帯域幅のフィールド、および clat_ns の平均値とパーセンタイル値です。完了レイテンシは、アプリケーション側の応答時間と完全に同じではありません。アプリケーション内の待ち行列や処理は、この測定経路に含まれていません。
P99 は、およそ 99% のリクエストのレイテンシがその値以下であることを示し、最大レイテンシを意味するものではありません。平均値が近いのに P99 が上昇している場合、一部のリクエストで待ち時間が長くなっている可能性があります。実際のキュー深度と繰り返し測定の結果を合わせて判断する必要があります。
検証記録には、少なくとも次の項目を含めます。
- fio のバージョン、完全なコマンド、ファイルサイズ、対象のマウント情報。
- 読み書きの種類、ブロックサイズ、ジョブ数、リクエストのキュー深度。
- スループット、IOPS、平均完了レイテンシ、P99。
- エラーフィールド、テスト中のサービス負荷、異常の有無と内容。
fio の実際の I/O 深度分布も確認してください。8 に設定していても、実行中に常にその深度に達しているとは限りません。エンジンやファイルシステムの動作が異なれば、設定上のパラメータが同じでも比較できるとは限りません。
5. 再測定、保存、後片付けまで行い、検証を完了する
各負荷を繰り返し実行し、その都度異なる出力ファイル名を使って前回の結果を上書きしないようにします。最も高い数値だけを選ぶのではなく、変動幅を比較してください。テストファイルを大きくしたり実行時間を延ばしたりする前には、空き容量を再確認し、追加の書き込み量とリソース競合を明確に把握する必要があります。
2 台のドライブを個別に検証する場合は、それぞれのファイルシステム上のディレクトリを選び、同じパラメータで順番にテストします。ディレクトリが実際には同じボリューム上にある場合、ディレクトリを変えただけでは 2 台目のドライブを独立して検証したことにはなりません。
読み書きテストでエラーが発生した場合は、まず生の出力と関連するカーネルの記録を保存し、その後でファイルシステムとデバイスの接続経路を調べます。エラーがなくても結果の変動が大きい場合は、同じサーバー上の別の処理やテスト条件の変化を先に切り分け、すぐにハードウェアの異常と判断しないでください。
完了したら、JSON、マウント情報、コマンドの記録を管理された保存先にコピーします。保存したファイルが読めることを確認してから、テストディレクトリを再確認し、今回作成したファイルだけを削除します。
printf '%s\n' "$TESTDIR"
ls -lh -- "$TESTDIR"
rm -- "$TESTDIR/test.bin"
残った結果ファイルも保存済みであることを確認してから 1 つずつ削除し、ディレクトリが空になったら rmdir で削除します。デバイスのマウントポイントを再帰的に削除してはいけません。最終的な結論は「これらのパラメータと負荷条件で、この結果が得られた」と記述し、「このドライブは常に一定の速度を出せる」とは記述しないでください。ファイル単位の基準値は明らかな差異を見つけるためのものであり、アプリケーションの運用開始前には、実際の読み書きパターンによる検証が必要です。
よくある質問
ファイル単位の fio テストなら稼働中のサービスに影響しませんか?
影響はあります。専用ファイルで既存ファイルの直接上書きは避けられますが、ディスク、CPU、ファイルシステムの競合は残ります。サービス開始前に実行するか、影響の許容範囲と停止条件を先に決めます。
direct=1 はすべてのキャッシュを回避し、データベースのコミットを再現しますか?
いいえ。直接 I/O は通常、ファイルデータのページキャッシュを回避しますが、装置内のすべてのキャッシュを回避するわけではありません。永続化を伴うコミットはアプリケーションと同期書き込みの別テストで確認します。
EPYC 7543P 専用物理サーバー、月額 $454〜