アプリケーションのパフォーマンスを低下させるデータベースクエリを見つける方法
ローカル環境ではダッシュボードが200msで読み込まれるのに、本番環境では8秒かかる。ユーザーから苦情が来る。PMから何が問題なのか聞かれる。データベースが原因だと疑っているが、証明できない。
この記事では、ユーザーに見える症状から遅いデータベースクエリまで追跡する方法、実際のデータベースパフォーマンスのボトルネックを特定する方法、そして無闇にインデックスを追加し始める前に、異なる根本原因を区別する方法を説明します。
重要なポイント
- 診断はデータベース内部ではなく、ユーザーに見える症状から始める—クエリを調査する前に、実際にどこで時間がかかっているかを確認する。
EXPLAIN ANALYZEを使用してクエリプランを読み、シーケンシャルスキャン、ネストループ、行数の不一致を特定する。- 平均値ではなくp95とp99のレイテンシを追跡し、実際のユーザーに影響を与える外れ値を捉える。
- CPU負荷、I/O負荷、ロック競合の問題を区別する—それぞれ異なる最適化アプローチが必要。
ユーザーに見える症状から始める
ページが遅いからといって、必ずしもクエリが遅いとは限りません。データベース内部に入り込む前に、実際にどこで時間がかかっているかを確認しましょう。
次のようなパターンに注目してください:
- 一貫した遅延:
/api/ordersへのすべてのリクエストが3秒以上かかる。おそらく単一の高コストなクエリが原因。 - ウォーターフォールリクエスト: ページが47回の連続したAPIコールを行う。典型的なN+1クエリ問題。
- 不安定なレイテンシ: 同じエンドポイントが50msから5秒まで変動する。ロック競合またはキャッシュミスの可能性。
- タイムアウトのスパイク: リクエストが時々完全に失敗する。テーブルロックまたはコネクションプールの枯渇を確認。
ブラウザのDevToolsとAPIログが最初の手がかりを与えてくれます。ネットワーク時間が最小限でレスポンス時間が高い場合、バックエンドがボトルネックです。バックエンドログがデータベース呼び出しに大半の時間を費やしていることを示している場合、ターゲットを見つけたことになります。
クエリパフォーマンス分析のコア検出テクニック
クエリプランを読む
すべての主要なデータベースは、クエリをどのように実行するかを説明します。これが主要な診断ツールです。
PostgreSQL:
EXPLAIN ANALYZE SELECT * FROM orders WHERE user_id = 123;
MySQL (8.0.18+):
EXPLAIN ANALYZE SELECT * FROM orders WHERE user_id = 123;
SQLite:
EXPLAIN QUERY PLAN SELECT * FROM orders WHERE user_id = 123;
大きなテーブルでのシーケンシャルスキャン、高い行数のネストループ、推定行数と実際の行数の不一致に注目してください。インデックス検索を期待していたのに、100万行のテーブルで「Seq Scan」を示すプランは、すぐに問題を明らかにします。
注意: クエリプランナーはデータベースのバージョン間で変わります。PostgreSQL 14で機能したプランがPostgreSQL 16では異なる可能性があります。アップグレードが自動的に悪いクエリを修正すると思い込まないでください。
スロークエリログを有効にする
しきい値を超えるクエリをログに記録するようにデータベースを設定します。
PostgreSQLは集約された実行時統計にpg_stat_statementsを使用します。クエリごとのログにはlog_min_duration_statementを使用します。
MySQLのPerformance Schemaとsysスキーマは、非推奨のクエリキャッシュなしでスロークエリを表示します。SQLiteは組み込みのスロークエリログがないため、アプリケーションレベルのタイミングが必要です。
本番環境では保守的にしきい値を設定してください。100msから始めて、明らかな問題を修正したら厳しくしていきます。
平均値ではなくパーセンタイルを測定する
平均クエリ時間は外れ値を隠します。平均50msのクエリがp99で2秒に達する可能性があり、これは1%のユーザーに一貫して影響を与えます。
p95とp99のレイテンシを追跡してください。p99が中央値の10倍である場合、調査する価値のあるテールレイテンシの問題があります。
Discover how at OpenReplay.com.
CPU vs. I/O vs. ロック競合を区別する
高いクエリ時間は異なる意味を持ちます:
- CPU負荷: 複雑な計算、大規模なソート、または正規表現操作。クエリプランは計算負荷の高いノードで高い「コスト」を示します。
- I/O負荷: ディスクから大量のデータを読み取る。シーケンシャルスキャンと欠落したインデックスに注目。
- ロック競合: 他のトランザクションを待っているクエリ。
pg_stat_activity(PostgreSQL)またはSHOW PROCESSLIST(MySQL)でブロックされたクエリを確認。
それぞれ異なるSQLクエリ最適化アプローチが必要です。インデックスを追加してもCPU負荷の高い集約には役立ちません。
分散トレーシングによる観測性優先の検出
OpenTelemetryを使用すると、遅いリクエストを特定のデータベーススパンと関連付けることができます。/api/dashboardエンドポイントを示すトレースは、どのクエリが時間を消費したかを正確に明らかにします。
ORMまたはデータベースドライバーを設定して、クエリタイミングを含むスパンを出力します。N+1問題が明確に見えます:1つであるべきところに50個の同一のSELECTスパンがあります。
セキュリティ警告: 本番環境のトレースでパラメータを含む完全なSQLをログに記録しないでください。ユーザーID、メール、その他のPIIが観測性プラットフォームに残ります。代わりにクエリフィンガープリント(パラメータ化されたテンプレート)をログに記録してください。
避けるべき一般的な罠
- 無闇にインデックスを追加する: インデックスは読み取りを高速化しますが、書き込みを遅くします。新しいインデックスをクエリプランが実際に使用することを確認してください。
- 平均値に依存する: その50msの平均は、実際のユーザーに影響を与える5秒の外れ値を隠しています。
- 万能なチューニング: 「
work_memを増やす」は普遍的なアドバイスではありません。まず測定してください。 - 生のSQLをログに記録する: セキュリティとコンプライアンスのリスク。パラメータ化されたフィンガープリントを使用してください。
まとめ
データベースパフォーマンスのボトルネックを見つけることは、推測ではなく症状から始まります。遅いページから遅いエンドポイントへ、遅いエンドポイントから遅いクエリへ、遅いクエリからその実行プランへと追跡します。パーセンタイルを測定し、CPU/IO/ロックの問題を区別し、分散トレーシングを使用してフロントエンドの症状とバックエンドの原因を結びつけます。
その後で初めて最適化すべきです。
よくある質問
APIログでレスポンス時間の内訳を確認してください。ネットワークレイテンシが低くバックエンドのレスポンス時間が高い場合、サーバーがボトルネックです。バックエンド内では、データベース呼び出しとアプリケーションロジックに費やされた時間を測定します。ほとんどのフレームワークとAPMツールはこの内訳を表示でき、クエリが実際の原因かどうかを確認するのに役立ちます。
EXPLAINは、クエリを実行せずにデータベースが使用する予定のクエリプランを表示します。EXPLAIN ANALYZEは実際にクエリを実行し、実際のタイミングと行数を報告します。クイックチェックにはEXPLAINを使用し、正確なパフォーマンスデータが必要な場合はEXPLAIN ANALYZEを使用します。EXPLAIN ANALYZEは変更を実行するため、書き込み操作では注意してください。
インデックスはストレージを消費し、データベースがそれらを維持する必要があるため、INSERT、UPDATE、DELETE操作を遅くします。さらに、クエリプランナーがシーケンシャルスキャンの方が速いと推定した場合、インデックスを使用しない可能性があります。常にEXPLAIN ANALYZEで、新しいインデックスがターゲットとしている特定のクエリを実際に改善することを確認してください。
クエリログを有効にするか、分散トレーシングを使用して、リクエストごとに実行されるすべてのクエリを確認します。同じクエリテンプレートが異なるパラメータで何度も実行されるパターンを探します。ORMは関連レコードを一度に1つずつロードする際にこれを引き起こすことがよくあります。修正は通常、イーガーローディングまたはIN句を使用してクエリを単一のリクエストにバッチ処理することです。
Understand every bug
Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.