「なぜかクエリが遅い」を解決する第一歩。PostgreSQLの `pg_stats` を使いこなそう
現場で開発をしていると、どうしても避けられないのが「突然のクエリ遅延」ですよね。
「昨日までは爆速だったのに、今日はなぜか全表スキャンしてる……?」
「インデックスは貼ってあるはずなのに、プランナがそれを使ってくれない」
そんなとき、多くのエンジニアは `EXPLAIN ANALYZE` を叩いてプランを見ると思います。でも、そのプランがなぜ「そう判断されたのか」まで深掘りできていますか?
実は、PostgreSQLのクエリオプティマイザは、魔法を使っているわけではありません。彼らが見ているのは、私たちが育てた「統計情報」という名の地図だけなんです。今日は、その地図の正体である `pg_stats` ビューについて、実戦的な話をしようと思います。
—
`pg_stats` って結局なんなの?
一言で言うと、「PostgreSQLの脳内にある、データ分布のメモ帳」です。
`ANALYZE` コマンドを実行したとき、PostgreSQLはテーブル全体を舐めるのではなく、一部のデータをサンプリングして「このカラムにはどんな値が、どのくらいの比率で入っているか」を推定します。その結果が格納されているのが `pg_stats` です。
もしプランナが間違った選択をしたら、まずは `pg_stats` を覗いてみてください。「あ、こいつ、ここが極端に偏ったデータだって認識できてないな」と気づくことが、チューニングへの近道です。
—
現場でよく見る「確認すべきカラム」3選
`pg_stats` は情報量が多いので、全部を見る必要はありません。まずは以下の3つだけ追ってみてください。
- `null_frac`: NULLがどのくらい含まれているか。
- `n_distinct`: ユニークな値がいくつあるか。(負の値は「テーブル全行数に対する割合」を意味します)
- `most_common_vals` / `most_common_freqs`: よく出てくる値とその頻度。
具体的なSQL例
例えば、「ユーザーのステータス」カラムで検索が遅いなら、こんなふうに確認します。
SELECT
attname AS column_name,
null_frac,
n_distinct,
most_common_vals,
most_common_freqs
FROM pg_stats
WHERE tablename = ‘users’
AND attname = ‘status’;
もし、`most_common_vals` に検索対象のステータスが入っていなかったら?
それはプランナが「その値がどのくらい含まれているか」を正確に予測できていないことを意味します。これが、インデックスが使われない原因のトップランナーだったりするわけです。
—
統計情報がズレる「あるある」シーン
実務でよくあるのが、「大量のデータを一括更新(バッチ処理)した直後」のトラブルです。
`UPDATE` や `DELETE` を大量に行うと、実際のデータ分布と統計情報に乖離が生まれます。PostgreSQLの自動バキューム(autovacuum)が追いついていないと、オプティマイザは「古い地図」を頼りに旅をすることになります。結果、本来ならインデックスを使うべきところでシーケンシャルスキャンを選択してしまう……という悲劇が起こるわけです。
そんなときは、迷わず手動で叩いてください。
ANALYZE users;
これだけで解決することも多いです。まずはここから。「統計を最新にする」という発想、常に持っておいて損はないですよ。
—
先輩からのアドバイス:深追いしすぎに注意
とはいえ、`pg_stats` を見て「統計情報を手動でガチガチに調整しよう」とするのは禁物です。
例えば `ALTER TABLE … SET STATISTICS` で精度を上げることもできますが、これはあくまで最終手段。まずはインデックスの設計や、クエリ自体の書き方を見直すのが先です。統計情報はあくまで「プランナの勘を助けるためのもの」であって、ここをいじりすぎるのはメンテナンスコストを上げるだけになりがちですから。
まずは「プランナが今のデータをどう見ているか」を知るための「レンズ」として `pg_stats` を使ってみてください。これが見えるようになると、クエリの挙動が今までとは全く違った景色で見えてくるはずです。
何か困ったことがあったら、またいつでも聞いてくださいね。一緒に泥臭いチューニングを楽しみましょう!
コメント