深夜2時15分のパフォーマンス・アラート
火曜日の深夜2時15分、監視アラートが鳴り響きました。本番APIのレイテンシが、安定していた50msから一気に4秒まで跳ね上がったのです。ダッシュボードを見ると、PostgreSQLのCPU使用率は99%に張り付いていました。私の最初の直感は、インスタンスをアップグレードしてvCPUを増やすという「クラウド的な解決策」でした。しかし、ハードウェアの増強は通常、根本的な設定ミスやクエリのボトルネックに対する一時的な絆創膏に過ぎません。
問題を解決するには、単にサーバーを大きくするのではなく、ベースラインが必要でした。現在の構成が限界に達する前に、具体的に秒間何トランザクション(TPS)を処理できるかを知る必要があったのです。ここで、pgbenchが主要な診断ツールとなります。64GBのRAMを積んでいるにもかかわらずデータベース의動作が重いのであれば、推測をやめてベンチマークを開始すべきです。
本当に重要なメトリクス
PostgreSQLのパフォーマンスを測定する際、黄金律となる2つのメトリクスがあります。それが**TPS**と**レイテンシ(Latency)**です。これらは常にセットで見る必要があります。
- TPS(接続を含む): これは、データベースが1秒間に完了したトランザクション数をカウントします。これには、リクエストごとに新しい接続を確立するために費やされた時間も含まれます。
- TPS(接続を除く): これはいわゆる「純粋な」スループットです。接続がすでにアクティブになった後、エンジンがどれだけ速くデータを処理できるかを測定します。
- レイテンシ: 1つのトランザクションの往復時間です。平均レイテンシが5秒に達してしまえば、ユーザーはすでに離脱しているため、高いTPSも無意味になります。
PostgreSQLにはデフォルトでpgbenchが含まれています。これは、1トランザクションあたり5つのSELECT、UPDATE、INSERTコマンドを使用して、TPC-Bに近いベンチマークを実行します。しかし、その真の力は、特定のアプリケーションのワークロードを模倣したカスタムスクリプトを実行できる点にあります。
テスト環境の準備
稼働中の本番データベースでベンチマークを実行するのは避けましょう。私は常に、本番ハードウェアを正確に反映したステージング環境を構築します。まず、特定の「スケールファクタ(Scale Factor)」を指定してテスト用データベースを初期化します。
# テスト用データベースを作成
createdb benchmark_test
# スケールファクタ50でpgbenchを初期化
# これにより約750MBのデータ(accountsテーブルに500万行)が生成されます
pgbench -i -s 50 benchmark_test
スケールファクタ(-s)は極めて重要です。スケール1では現代のシステムには小さすぎます。現実的なテストのために、私はデータセットがshared_buffersよりも大きくなるように設定します。これにより、単にRAMにヒットする性能ではなく、ディスクI/Oのパフォーマンスを確実にテストできます。
ベースラインの確立
標準的なテストを実行してみましょう。10個の同時クライアント(-c)、2つのワーカースレッド(-j)を使用し、60秒間(-T)シミュレートします。
pgbench -c 10 -j 2 -T 60 benchmark_test
結果が出たら、最終行を確認してください。たとえばtps = 450.23のような数値が表示されます。これがあなたの「底(フロア)」です。もしpostgresql.confの設定を調整してその数値が300に落ちたなら、その変更が失敗だったという即時の証拠になります。
実環境のクエリに合わせたカスタムスクリプト
標準のTPC-Bテストも役立ちますが、実際のアプリケーションは単にpgbench_accountsテーブルを更新しているだけではないはずです。実環境のアプリは通常、重いSELECT結合や大量のINSERTスパイクで苦労します。私はこれらの特定のボトルネックを切り出すために.sqlファイルを作成します。
レポート用クエリが遅い場合は、read_heavy.sqlという名前のファイルに保存します。
-- read_heavy.sql
BEGIN;
SELECT abalance FROM pgbench_accounts WHERE aid = (random() * 100000 * :scale)::int;
SELECT count(*) FROM pgbench_branches;
END;
-fフラグを使用して、その特定のファイルを指定してpgbenchを実行します。
pgbench -c 20 -j 4 -T 120 -f read_heavy.sql benchmark_test
これらのテストスクリプトを準備する際、乱雑なレガシーデータのエクスポートを扱うことがよくあります。クイックなデータインポートのためにCSVをJSONに変換する必要があるときは、toolcraft.app/ja/tools/data/csv-to-json を利用しています。これは完全にブラウザ上で動作するため、一度限りのテストのためにPythonパーサーを書く手間が省けます。
最適化の検証
最近、RAMが16GBあるにもかかわらずshared_buffersがデフォルトの128MBのままになっているサーバーを扱いました。これは典型的なパフォーマンスキラーです。私はshared_buffersを4GBに増やし、effective_cache_sizeを12GBに設定しました。
サービスを再起動した後、同じpgbenchコマンドを再実行しました。改善は一目瞭然でした。
- チューニング前: 450 TPS、平均レイテンシ 22ms
- チューニング後: 1,200 TPS、平均レイテンシ 8ms
また、-M preparedフラグを使用して**プリペアドステートメント(Prepared Statements)**をテストすることもできます。これにより、通常10〜20%のパフォーマンス向上が得られます。実行のたびにデータベースがSQLを再解析する必要がなくなるためです。これらの数値があれば、自信を持って深夜3時に設定変更を本番環境へ反映させることができます。
ジッターを無視しない
TPSだけがすべてではありません。レイテンシの**標準偏差(Standard Deviation)**を注意深く見てください。平均レイテンシが10msであっても、標準偏差が50msであれば、ユーザーは「ジッター(揺らぎ)」を経験しています。一部のリクエストは高速ですが、他のリクエストは大幅に遅延している状態です。これは多くの場合、ディスクI/Oの競合や、autovacuumプロセスがクエリに干渉していることを示唆しています。
ベンチマークを習慣にする
テストは一度きりのイベントではありません。PostgreSQLのアップグレード、クラウドインスタンスのタイプの変更、あるいは大規模な新機能のデプロイのたびにpgbenchを実行すべきです。これにより、会話が「データベースが遅い気がする」から「このインスタンスは、レイテンシが100msに達する前に1,500ユーザーまで処理できることが分かっている」へと変わります。
真夜中にフリーズしたダッシュボードを見つめることになったとき、推測はやめましょう。テストデータを初期化し、ベースラインを取得し、カスタムスクリプトを使って問題点を見つけ出すのです。データこそが、あなたを安眠へと導いてくれる唯一の手段です。

