`ANALYZE`を制する者は、PostgreSQLの「脳」を制する
データベースの世界に長くいると、「クエリが急に遅くなった」という相談を山ほど受けます。そのほとんどが、インデックスの欠如や非効率な結合ではなく、「プランナが現実を見失っている」ことに起因しています。
PostgreSQLのオプティマイザは、統計情報という名の「地図」を頼りに実行計画を立てます。もしその地図が古ければ、どれほど精鋭のエンジンを積んでいても、迷路の中で立ち往生するだけです。今日は、その地図を最新に保つための生命線、`ANALYZE`の深淵を覗いてみましょう。
—
なぜ統計情報が「ズレる」のか
PostgreSQLの自動バキューム(`autovacuum`)は優秀です。しかし、大規模なバッチ処理や、特定のカラムにデータが偏るような更新が頻発する環境では、デフォルトの閾値だけでは追いつかないことがあります。
`pg_statistic`(システムビューの`pg_stats`から参照可能)に格納されている「頻出値(MCV)」や「ヒストグラム」が実態と乖離した瞬間、プランナは悲劇的な選択をします。
例えば、あるフラグカラムで「特定の状態」が全体の99%を占めるようになったとします。しかし、統計情報が古いせいで、プランナが「その状態はレアケースだ」と誤認すれば、効率的なインデックススキャンではなく、フルテーブルスキャンを選択してしまう。経験豊富な皆さんなら、一度は目にしたことがある光景でしょう。
`ANALYZE`の内部で起きていること
`ANALYZE`を実行すると、PostgreSQLはテーブル全体をフルスキャンするわけではありません。そんなことをすれば、本番環境のIO負荷でシステムが停止してしまいます。
実は、`ANALYZE`はサンプリングを行っています。`default_statistics_target`の設定値に基づいて、テーブルの行をランダムに抽出し、分布を推定しているのです。
- 行数推定(n_distinct): カラムごとのユニークな値の数を算出。
- ヒストグラムの構築: 範囲検索のコスト計算に使用。
- MCV(Most Common Values): 等価比較(`=`, `IN`)のコスト計算に使用。
ここでのポイントは、「サンプリングの偏り」です。データが時間軸で明確に偏っている場合、ランダムサンプリングでは分布を正しく捉えられないことがあります。この場合、単に`ANALYZE`を打つだけでは解決せず、`ALTER TABLE … SET STATISTICS`で対象カラムの解像度(バケット数)を上げるという、もう一段深いチューニングが必要になります。
パフォーマンストラブルシューティングの定石
もしあなたが、「なぜかこのクエリだけ実行計画が安定しない」という問題に直面したら、以下の手順を試してみてください。
1. `pg_stats`の確認:
該当カラムの`n_distinct`や`correlation`(物理的な並び順とインデックスの相関)を見てください。`correlation`が極端に低い場合、インデックススキャンはランダムIOの嵐となり、プランナはコストを低く見積もっても実際は壊滅的な遅さになります。
2. `EXPLAIN ANALYZE`で乖離を可視化:
`actual rows`と`estimated rows`を比較してください。ここが数桁違うなら、統計情報の更新タイミングか、分布の偏りが原因です。
3. 必要な箇所だけ`ANALYZE`:
テーブル全体を叩く必要はありません。`ANALYZE tablename(column_name);`とすることで、特定の列だけ統計を再計算させることができます。これは高負荷なテーブルで有効な戦術です。
最後に:自動化を過信しない勇気
「`autovacuum`がやってくれる」というのは半分正解ですが、半分はエンジニアの怠慢を許す甘い言葉です。
特に、大規模なデータ移行や、特定の時間帯に集中するバッチ処理があるならば、その直後に意図的な`ANALYZE`をパイプラインに組み込むべきです。統計情報は、PostgreSQLというエンジンの「視力」です。
皆さんのデータベースが、今日も適切な実行計画を選び取れるよう、時折このコマンドで「視力検査」をしてあげてください。そうすれば、PostgreSQLは必ず期待以上のパフォーマンスで応えてくれますよ。
—
それでは、また次回の深掘りでお会いしましょう。質問や「こんな時どうしてる?」という話があれば、ぜひ共有してください。
コメント