なぜクエリは突然遅くなるのか?「pg_stats」を味方につけて統計情報の裏側を覗こう
現場で働いていると、たまに「昨日まで爆速だったクエリが、今日になって突然フルスキャンを始めた……」なんていう冷や汗モノのトラブルに遭遇しますよね。
そんな時、真っ先に疑うべきは「統計情報の鮮度」です。PostgreSQLのオプティマイザ(プランナ)は、いわば「地図」を頼りに目的地(実行計画)を決める旅人です。その地図が古かったり、荒かったりしたら……そりゃあ迷子にもなりますよね。
今日は、その地図の「生データ」を覗き見る、PostgreSQLの心臓部の一つ『pg_statistic』と、その扱い方について少し深い話をしようと思います。
なぜ「pg_statistic」を直接触ってはいけないのか
まず、大前提の話をさせてください。PostgreSQLには `pg_statistic` というシステムカタログがありますが、これに直接 `SELECT` を投げるのはやめましょう。
理由は単純で、データが人間にとって絶望的に読みにくい形式(配列形式で、かつ型ごとにカラムが分かれている)で格納されているからです。
僕たちが普段使うべきは、そのラッパーである `pg_stats` ビュー です。これは `pg_statistic` の内容を人間が読みやすい形に整形してくれています。
— 自分のテーブルの統計情報をざっと確認する
SELECT
attname AS column_name,
n_distinct,
most_common_vals,
most_common_freqs
FROM pg_stats
WHERE tablename = ‘orders’;
これだけで、オプティマイザがそのカラムについて「どんな値がどれくらい頻繁に出現するか」をどう認識しているかが見えてきます。
現場で役立つ「統計情報の活用術」
僕がパフォーマンスチューニングをするとき、必ず見るポイントを3つ紹介します。
1. n_distinct(カーディナリティの把握)
`n_distinct` は「そのカラムにユニークな値がどれくらいあるか」を示しています。
- 値が `-1` に近いなら、ほぼユニーク(主キーに近い)。
- 値が小さいなら、重複が多いデータ。
もし、実際には100万種類の値があるカラムなのに、ここが極端に小さい数字になっている場合、オプティマイザは「この条件で絞り込めば少ししかヒットしないはずだ!」と誤解して、インデックスを使わずにフルスキャンを選ぶ……なんてミスを犯します。
2. most_common_vals (MCV)
「特定のフラグ値が異常に偏っている」ことはよくあります。例えば `status = ‘processed’` が全体の99%を占めているようなケースです。
`pg_stats` で `most_common_vals` を見れば、PostgreSQLが「どの値が頻出するか」をちゃんと把握しているか確認できます。もしここが空っぽなら、`ANALYZE` が足りていない証拠です。
3. histogram_bounds
範囲検索(`WHERE price > 1000` など)をする際、オプティマイザはヒストグラムを見て「何件くらいヒットするか」を推測します。ここが古いままだと、実際の件数と見積もりに大きな乖離(見積もり誤差)が生まれ、実行計画が崩壊します。
「統計情報がズレている」と気づいた時のアクション
もし、クエリの実行計画を見て「見積もり件数(rows)と実際の件数(actual rows)が全然違う!」となったら、迷わず手動で叩きましょう。
— 特定のテーブルの統計情報を強制更新
ANALYZE VERBOSE orders;
もし、それでも改善しない場合は、統計情報の精度(サンプリングサイズ) を疑ってください。PostgreSQLはデフォルトで「まあ、このくらいの量を見れば全体像はわかるだろう」とサンプリングして統計を作ります。データ分布が極端な場合は、これを上げてあげる必要があります。
— 統計情報の精度を上げる(デフォルトは0=自動)
ALTER TABLE orders ALTER COLUMN user_id SET STATISTICS 1000;
ANALYZE orders;
最後に:魔法の杖ではないけれど
統計情報のチューニングは、いわば「エンジンの調律」です。急激なデータ増加や、偏ったデータ投入が行われた時、PostgreSQLの自動 `ANALYZE` だけでは追いつかないことがあります。
`pg_stats` を眺める癖をつけておくと、「なぜこのクエリが遅いのか」を勘ではなく、根拠を持って説明できるようになります。これができると、一気にエンジニアとしてのレベルが一つ上がりますよ。
もし次にクエリが遅くなったら、まずは `pg_stats` を覗いてみてください。PostgreSQLが何を考えているのか、きっと教えてくれるはずです。
それでは、また現場で!
コメント