【実務・中級編】 プランキャッシュ – PostgreSQL

「毎回コンパイルしてたら身が持たないでしょ?」PostgreSQLのプランキャッシュと付き合う極意

「クエリの実行速度が、さっきまで速かったのに急に遅くなった……」

実務でそんな現象に遭遇したことはないかな? もしPostgreSQLを使っているなら、それは「プランキャッシュ」という仕組みが、少しばかり余計なお世話を焼いてくれているせいかもしれない。

今日は、PostgreSQLがSQLをどうやって「料理」しているのか、そしてその過程でプランキャッシュがどう関わっているのかを、現場の視点から紐解いていこうと思う。

—

プランキャッシュって結局なんなの?

データベースにとって、SQLを受け取ってから結果を返すまでの工程は、大きく分けると2つある。

1. 解析と最適化(プランニング): 「どのインデックスを使うのが一番効率的かな?」と考えて実行計画を作る。
2. 実行(エグゼキューション): 実際にデータを読みに行って結果を出す。

ここでのポイントは、「1」のプランニングには意外とコストがかかるということ。特に複雑なJOINが絡むクエリだと、プランを練るだけでCPUをガリガリ削ることもある。

そこでPostgreSQLは、一度作った実行計画をメモリに保存して、次回以降は使い回そうとする。これが「プランキャッシュ」だ。特に`PREPARE`文や、アプリケーション側のプリペアドステートメント(`SELECT FROM users WHERE id = $1` のような形)を使うと、この恩恵を強く受けることになる。

「Generic Plan」と「Custom Plan」の深い関係

ここからが現場でハマりやすいポイントだ。PostgreSQLのプリペアドステートメントには、大きく分けて2つの戦略がある。

  • Generic Plan(汎用計画): パラメータの値に関係なく使える、万能な計画。
  • Custom Plan(個別計画): 渡されたパラメータの値を考慮して、その時最適化された計画。

実は、PostgreSQLは賢い。最初は「本当にこのクエリが何度も使われるか?」を判断するために、数回(デフォルトで5回)は「Custom Plan」を生成して様子を見るんだ。そして「お、これは何度も呼ばれるな」と判断したら、より効率的な「Generic Plan」へ切り替える仕組みになっている。

なぜ「急に遅くなる」現象が起きるのか?

一番よくあるのは、「値によって最適なインデックスが変わるケース」だ。

例えば、あるテーブルに「ステータス」というカラムがあって、99%が「完了」、1%が「処理中」というデータ分布だったとする。

— 処理中のデータを探すとき
SELECT FROM orders WHERE status = ‘processing’;

この時、プランナーは「1%しか該当しないから、インデックスをフル活用して高速に引こう(Index Scan)」と判断する。ところが、あとから「完了」のデータで同じプランを使い回されるとどうなるか。「大量のレコードをインデックス経由で引くのは非効率だ! 全件スキャン(Sequential Scan)したほうが速いじゃないか!」と、プランが破綻してパフォーマンスが急落するわけだ。

実務で守るべき「3つの心得」

じゃあ、どう付き合えばいいのか。現場で意識していることをまとめておくよ。

1. 実行計画を疑う癖をつける

遅いクエリがあったら、まず `EXPLAIN ANALYZE` を叩く。もし、直感的に「いや、この検索条件ならインデックスを使うべきでしょ」と思うのに `Seq Scan` が走っていたら、キャッシュ済みの古いプランを疑おう。`DISCARD ALL` でセッションをリセットすれば、その場は改善するかもしれない。

2. 統計情報は常に最新に

プランナーは「テーブルに何件データがあるか」という統計情報を見て判断している。`ANALYZE` をサボっていると、古い統計情報を元に誤ったプランがキャッシュされ続ける。「なぜか急に遅い」の犯人は、大抵この統計情報の鮮度だ。

3. クエリを簡潔に保つ

あまりに複雑すぎるクエリは、プランナーを迷わせる。複雑な処理はビューや関数に切り出し、PostgreSQLがプランを生成しやすい環境を作ってやることが大切だ。

—

最後に

プランキャッシュは、うまく付き合えば最強の味方だけど、甘やかしすぎると足元をすくわれる存在だ。

「とりあえずプリペアドステートメントを使えば速くなる」なんて幻想は捨てて、データベースが今、どんな計画を立てて、どのデータにアクセスしようとしているのか――。その「思考プロセス」を想像できるようになると、君も一人前のDBエンジニアだ。

もしまた、奇妙なクエリの挙動に悩まされたら、いつでも相談してくれ。現場からは以上だ。

コメント

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