【テクニカル・上級編】 MCV(Most Common Values) – PostgreSQL

統計情報の「死角」を見抜く:PostgreSQLのMCV(Most Common Values)と付き合う技術

PostgreSQLのクエリチューニングをしていると、必ずと言っていいほど「プランナがなぜこの実行計画を選んだのか」という壁にぶつかります。特に、テーブルの統計情報が十分に更新されているはずなのに、見積もりが大きく外れてNested Loopが暴走する……そんな経験、みなさんにもあるはずです。

その犯人の多くが、`pg_statistic` に格納された MCV(Most Common Values)、つまり「頻出値リスト」の取り扱いにあります。今日は、このMCVが内部でどう動き、我々エンジニアがどこで躓きやすいのか、少し深い話をしようと思います。

—

MCVは「魔法の杖」ではない

PostgreSQLのプランナは、クエリの選択率(Selectivity)を計算する際、まずMCVを参照します。特定の列に「偏り」がある場合、全行をスキャンするまでもなく、統計情報からその値の出現頻度を引いてこられるからです。

しかし、ここで意識しておくべき重要な事実があります。MCVはあくまで「サンプリングに基づいた近似値」であるということです。

`ANALYZE` が実行される際、PostgreSQLはテーブル全体を読み込むのではなく、設定された `default_statistics_target` に基づいてサンプルを採取します。もし、たまたまそのサンプルに特定の頻出値が含まれていなかったら?あるいは、データの分布が急激に変化してMCVが陳腐化していたら?プランナは「この値は滅多に出ないはずだ」と誤認し、最悪のプランを弾き出します。

なぜ「見積もりの乖離」が起きるのか

実務でよくあるのが、ステータスフラグや区分コードといった「低カーディナリティの列」でのトラブルです。

例えば、`status = ‘COMPLETED’` のような検索条件があったとします。もしこの値が全体の9割を占めていたとしても、MCVのリストから漏れていたり、あるいはリスト内の順位が低かったりすると、プランナは統計情報の「ヒストグラム」を使って選択率を推測しようとします。

ここで起きるのが、有名な「ヒストグラムの罠」です。ヒストグラムは値の範囲で情報を保持するため、特定の定数に対する等価検索(`=`) においては、MCVよりも精度が大きく落ちます。

トラブルシューティングの勘所

現場で実行計画が怪しいと感じたとき、私はまず以下のステップを疑います。

1. `pg_stats` の確認:
まずは `pg_stats` ビューを見て、対象列の `n_distinct`(一意な値の数)と `most_common_vals` を確認します。ここの `most_common_freqs`(頻度)が、実際のデータの感覚と合致しているか、直感に問いかけてみてください。
2. 統計ターゲットの引き上げ:
「なんとなく」でデフォルトの100のままにしていませんか? 重要な列であれば、`ALTER TABLE … ALTER COLUMN … SET STATISTICS 1000;` といった形で解像度を上げる勇気も必要です。
3. 相関関係の考慮(Extended Statistics):
これが最も見落とされがちです。複数の列にまたがる条件(例:`category = ‘A’ AND sub_category = ‘X’`) がある場合、個別の列のMCVだけでは限界があります。PostgreSQL 10以降であれば、`CREATE STATISTICS` を使って列間の相関をプランナに教えてあげることで、見積もりの精度が劇的に改善します。

最後に:データベースと対話するということ

データベースのオプティマイザは、常に「限られた情報の中で最善を尽くそうとする懸命なアルゴリズム」です。彼らが誤った判断を下すとき、それはアルゴリズムの欠陥ではなく、私たちが「データという名の現実」を彼らに正しく伝えられていないからかもしれません。

MCVを理解することは、オプティマイザの「目」を調整することと同義です。パフォーマンスが壁に当たったとき、実行計画のコスト値の裏側にある「統計情報のゆらぎ」に思いを馳せてみてください。

チューニングとは、ただクエリを書き換えることではなく、DBエンジンとの対話を通じて、この統計情報の歪みを正していく作業なのだと、私は思っています。

皆さんのクエリが、明日も最適に実行されますように。

コメント

タイトルとURLをコピーしました