【テクニカル・上級編】 プランキャッシュ – PostgreSQL

PostgreSQLのプランキャッシュ:その「便利さ」の裏に潜む落とし穴と向き合う

PostgreSQLのパフォーマンスチューニングにおいて、避けては通れないのが「プランキャッシュ(Plan Cache)」です。

我々のようなエンジニアがプリペアドステートメント(`PREPARE` / `EXECUTE`)や、ORM経由で発行されるパラメータ付きクエリを扱うとき、PostgreSQLは賢くも「一度作った実行計画を使い回す」という最適化を行います。

一見すると、毎回パースやプランニングのコストを払わなくて済む、素晴らしい機能のように思えます。しかし、大規模なデータベースを運用していると、この「再利用」が時に牙を剥くことがある。今日は、このプランキャッシュの深淵を少し覗いてみましょう。

—

なぜ「万能なプラン」は存在しないのか

PostgreSQLのプランキャッシュは、基本的に「汎用的なプラン」を作ろうとします。しかし、SQLの世界において「汎用的」であることは、必ずしも「最適」であることを意味しません。

例えば、あるテーブルに「ステータス」というカラムがあり、その分布が極端に偏っているとします。

  • `status = ‘COMPLETED’` が全体の99%を占める
  • `status = ‘PENDING’` が全体の1%以下

ここで、`status` を引数にしたクエリをキャッシュさせたとしましょう。
最初に `’PENDING’` で実行計画が生成されると、PostgreSQLは「このクエリは結果が少ないな、よし、インデックススキャンを選択しよう」と判断します。しかし、次に同じ計画で `’COMPLETED’` が投げられるとどうなるか。当然、インデックススキャンは悲鳴を上げ、本来であればシーケンシャルスキャンで全件なめるべき場面で、無駄なI/Oを垂れ流すことになります。

これが、プランキャッシュが引き起こす「悲劇」の典型例です。

内部的な生存戦略:Generic vs Custom Plans

PostgreSQLのクエリ最適化エンジンは、非常にしたたかです。`PREPARE` されたステートメントに対して、内部的に以下の2つの戦略を使い分けています。

1. Custom Plan: パラメータ値を具体的に見て、その都度最適化された計画を作る。
2. Generic Plan: パラメータ値を無視し、統計情報に基づいて汎用的な計画を作る。

面白いのは、PostgreSQLのオプティマイザが最初は Custom Plan を5回試し、そのコストを平均化し、それ以降は「Generic Planの方が安上がりだ」と判断した瞬間に、Generic Planへ切り替えるという挙動です(`plan_cache_mode`の設定にも依存します)。

この挙動を理解していないと、「昨日までは速かったクエリが、今日急に遅くなった」という現象に頭を抱えることになります。5回の試行を経て最適化されたつもりが、実は汎用プランに切り替わった瞬間に、特定のデータ分布に対して最悪の選択肢を選ばされていた……なんてことは、運用現場では日常茶飯事です。

トラブルシューティング:どう立ち向かうべきか

もし、特定のクエリが「時々」遅くなるのであれば、まずは `pg_stat_statements` を見てください。`mean_exec_time` や `stddev_exec_time` が異常に高いクエリがあれば、それがプランキャッシュの犠牲者である可能性が高いです。

対策として、以下の3つを検討してください。

  • plan_cache_mode の調整: 特定のセッションやクエリに対して `force_custom_plan` を設定することで、キャッシュの再利用を強制的に無効化できます。どうしても実行計画がブレる場合は、これが最も確実な「荒療治」です。
  • 統計情報の鮮度: 言うまでもないことですが、`ANALYZE` はプランナーの唯一の羅針盤です。データ分布が変わったのに統計情報が古い場合、プランナーは霧の中を歩くような判断を下します。
  • クエリの書き換え: 「パラメータ化してキャッシュさせること」が正義とは限りません。極端な分布を持つカラムに対するクエリは、あえて動的SQLにしてプランナーに毎回適切な判断をさせる、という選択もプロフェッショナルとしては十分ありです。

最後に:データベースと対話するということ

プランキャッシュは、魔法ではありません。あくまで、パフォーマンスを最大化するための「トレードオフ」です。

便利だからといってすべてをプリペアドステートメントに詰め込むのではなく、データがどのように分布し、プランナーがどう判断を下そうとしているのか。そのプロセスを想像できるようになると、データベースエンジニアとしての景色は一変します。

PostgreSQLは、非常に正直なデータベースです。あなたが設定した設定値や、投げたクエリに対して、常に論理的な回答を返してくれます。もし遅いクエリがいるなら、それはPostgreSQLが「君の指示通りに実行した結果だよ」と教えてくれているサインなのかもしれません。

さあ、今日は `EXPLAIN (ANALYZE, BUFFERS)` を片手に、実行計画の奥深さを探求してみてはいかがでしょうか。

コメント

タイトルとURLをコピーしました