「なぜかクエリが遅い」を解決する第一歩:PostgreSQLのヒストグラムと仲良くなろう
現場でパフォーマンスチューニングをしていると、必ずぶち当たる壁があるよね。
「実行計画を見ると、行数見積もりが現実と全然違う……」
「本来ならインデックスを使ってサクッと終わるはずが、なぜかフルスキャンしてる……」
そんな時、真っ先に疑うべきなのが、PostgreSQLのオプティマイザが持っている「統計情報」、特に「ヒストグラム」なんだ。
今日は、教科書にはあまり書いていない、現場レベルでのヒストグラムとの付き合い方を少し深掘りして解説するよ。
—
ヒストグラムって、結局なんなの?
PostgreSQLはテーブルの全データを毎回スキャンするわけにはいかないから、`ANALYZE`コマンドで統計情報を取って、「だいたいこのくらいの行数かな?」という見積もりを立てている。
その中で、`BETWEEN`や`>` `<`といった範囲検索をする時に威力を発揮するのが「ヒストグラム」だ。 簡単に言うと、カラムの値を「100個のバケツ」に分けて、「どのバケツにどれくらいのデータが入っているか」を記録したものだと思ってほしい。
- 頻出値(MCV): 「東京都」「大阪府」みたいによく出る値は正確にカウント。
- ヒストグラム: それ以外の「ばらつきのある値」は、範囲ごとに区切って分布を把握。
オプティマイザはこのヒストグラムを見て、「検索範囲の幅なら、全体の何パーセントくらい該当しそうだな」と計算しているんだ。
—
なぜ見積もりがズレるのか?
ここからが実務の話。ヒストグラムの最大の弱点は「値が急激に偏った時」だ。
例えば、`created_at`(作成日時)みたいなカラムを考えてみて。通常は均等にデータが増えるけど、キャンペーンで特定の時間にアクセスが集中したり、バッチ処理で一気にデータが入ったりすると、ヒストグラムの「バケツ」の解像度が足りなくなって、見積もりが大きく狂うことがある。
実際に見てみよう
試しに、今のカラムがどういうヒストグラムを持っているか確認するコードだ。
SELECT
attname AS column_name,
n_distinct,
histogram_bounds
FROM pg_stats
WHERE tablename = ‘orders’
AND attname = ‘total_amount’;
この`histogram_bounds`の中身を見てみてほしい。これがまさに、PostgreSQLが認識している「範囲の境界線」だ。もしここがスカスカだったり、実際の分布と乖離しているようなら、それがクエリ遅延の戦犯だ。
—
現場で役立つチューニングのコツ
もし見積もりがズレてクエリが遅いなら、以下の手順を試してみてほしい。
1. 統計情報の解像度を上げる
デフォルトの`default_statistics_target`は「100」だけど、複雑な分布をしているカラムに対しては、これを上げてあげるのが手っ取り早い。
— 特定のカラムだけ解像度を1000まで引き上げる
ALTER TABLE orders ALTER COLUMN total_amount SET STATISTICS 1000;
— 統計情報の再取得
ANALYZE orders;
これでヒストグラムのバケツが10倍細かくなる。これだけで見積もりが劇的に改善することは本当によくある。
2. 「不適切」な値の偏りがないか確認する
あまりに値が偏っているなら、そもそも統計情報だけに頼るのが危険な場合もある。そんな時は、素直にクエリにヒント句(pg_hint_planなど)を検討するか、あるいは`WHERE`句の条件を工夫して、オプティマイザが迷わないように誘導してあげる必要があるね。
—
最後に:完璧を求めすぎないこと
データベースエンジニアとして一つアドバイスしておくと、「統計情報を完璧に追いかけすぎるな」ということ。
データは生き物だ。今完璧に見積もれても、明日のデータ分布はまた変わる。ヒストグラムはあくまで「地図」であって、実際の地形とは微妙にズレているのが当たり前なんだ。
まずは、`EXPLAIN ANALYZE`を叩いて、見積もりと実測の乖離(`rows=10000`に対して`actual rows=1`みたいな大きな差)を見つけること。そこから、「このカラムのヒストグラムは信頼できるか?」と問いかけるだけで、君のチューニングスキルは一段上のレベルに行けるはずだよ。
何か具体的なクエリで困っていたら、いつでも相談してくれ。一緒に実行計画の迷宮を紐解いていこう!
コメント