オプティマイザの「直感」を紐解く:pg_statisticと統計情報の深淵
PostgreSQLのクエリプランナが、なぜあんなにも正確(あるいは時に絶望的)な実行計画を立てるのか。その核心にあるのは、魔法ではなく「統計情報」という名の冷徹なデータです。
現場で「なぜこのクエリだけ遅いのか?」と頭を抱えたとき、多くのエンジニアは `EXPLAIN ANALYZE` を叩きます。しかし、その先に広がる「コスト算出の根拠」まで踏み込めている人は、意外と少ないものです。今日は、PostgreSQLのオプティマイザが拠り所とする `pg_statistic` の深淵を覗いてみましょう。
統計情報の「生」に触れるということ
まず大前提として。`pg_statistic` はシステムカタログそのものであり、直接 `SELECT` を発行するものではありません。ここは「禁断の領域」に近いです。理由は単純で、そのデータ構造が人間にとって解読困難なまでに圧縮・エンコードされているから。
我々が日常的に扱う `pg_stats` ビューは、この難解なバイナリを人間が読める形に整形してくれている「通訳」のような存在です。しかし、トラブルシューティングの現場では、その通訳の向こう側で何が起きているかを知る必要があります。
なぜ「ヒストグラム」が嘘をつくのか
`pg_stats` を眺めていると、`most_common_vals`(最頻値)や `histogram_bounds`(ヒストグラムの境界値)といった項目が目に留まります。オプティマイザはこれらを使って、ある条件(`WHERE x = 10` など)に何行ヒットするかを推定します。
ここで興味深いのが、「列の相関」の問題です。
PostgreSQLの統計情報は、基本的に「各列独立」で保持されます。つまり、`A列` と `B列` に強い相関関係(例えば、都道府県と市区町村の関係など)があっても、プランナはそれを個別に計算し、単純に掛け合わせてカーディナリティ(推定行数)を算出します。
これが「実データは10行なのに、プランナは10万行と見積もってフルスキャンを選ぶ」という悲劇の正体です。もし皆さんが、特定のカラムペアに対するプランの狂いに苦しんでいるなら、それは `pg_statistic` の限界にぶつかっている証拠。この場合、`CREATE STATISTICS` を使って「拡張統計情報」を作成し、オプティマイザに相関関係を教え込むのが、熟練者の定石です。
「NULL」と「頻度」の微細なバランス
`null_frac`(NULL値の割合)や `n_distinct`(一意な値の数)も、プランナにとっては死活問題です。特に `n_distinct` が `-1`(列の数に対してユニークな値が多いことを示す)となっている場合、プランナは「インデックスを使うのが賢い」と判断しがちです。
逆に、統計情報が古くなっていたり、`ANALYZE` のサンプリングレート(`default_statistics_target`)が低すぎて実態を捉えきれていない場合、オプティマイザは「適当な」数値を補完して実行計画を組みます。
ここで重要なのは、「統計情報の鮮度」よりも「分布の複雑さ」です。データが偏っている(スキューがある)場合、デフォルトの統計ターゲット(100)では精度が足りないことが多々あります。特定のカラムでプランが暴れるなら、迷わずそのカラムの統計ターゲットを上げてみてください。
— 特定カラムの統計精度を上げる魔法の一行
ALTER TABLE users ALTER COLUMN status SET STATISTICS 500;
ANALYZE users;
最後に:プランナを信じるな、だが理解せよ
DBエンジニアとして長く現場にいると、「PostgreSQLは優秀だが、魔法使いではない」という事実に何度も直面します。
統計情報は、オプティマイザにとっての「世界地図」です。その地図が古かったり、主要な道路(相関関係)が省略されていたりすれば、どれほど優れたアルゴリズムを積んでいても、プランナは迷走します。
`pg_stats` を通じて統計情報を読み解くことは、クエリが「なぜその道を選んだのか」という思考プロセスをトレースすることに他なりません。パフォーマンストラブルの解決策は、往々にしてインデックスの追加ではなく、こうした「情報の解像度」を上げることに隠されているのです。
皆さんのデータベースに眠る統計情報が、今日も正確な判断を下してくれますように。それでは、また次の深淵でお会いしましょう。
コメント