「なぜ実行計画が急に狂うのか?」――統計情報と `default_statistics_target` の深淵
データベースエンジニアとして長く現場に立っていると、幾度となく「昨日まで爆速だったクエリが、今日になって突然フルスキャンを始めた」という悲劇に遭遇します。
もちろん原因は多岐にわたりますが、その多くはオプティマイザが「データ分布を見誤った」ことに起因しています。そして、その判断の根拠となるのが、PostgreSQLが保持する統計情報です。今回は、この統計情報の精度を司る `default_statistics_target` という、一見地味ながらも極めて重要なパラメータについて、少し深掘りしてみようと思います。
—
オプティマイザは「勘」で動いているわけではない
PostgreSQLのクエリオプティマイザは、統計情報に基づいて実行計画を立てるコストベースの戦略をとっています。特に、`ANALYZE` コマンドが収集するヒストグラムや最頻値(MCV: Most Common Values)の精度は、結合順序の決定やインデックスの使用可否を左右する生命線です。
ここで登場するのが `default_statistics_target` です。デフォルト値は「100」。これは、ヒストグラムを作成する際に、列のデータをどれだけ細かく(バケット数として)分割するかを定義する値です。
「とりあえず100でいいだろう」と放置している現場をよく見かけますが、数百万行、数千万行とデータが蓄積され、かつデータ分布が極端に偏ったテーブルにおいて、この「100」という数字が常に最適であるとは限りません。
統計情報の「解像度」を上げるべき時
カーディナリティ(列内のユニークな値の数)推定が外れると、クエリプランナーは往々にして、実際には数件しかヒットしないはずの条件に対して、数十万件のヒットを予測し、非効率なシーケンシャルスキャンを選択してしまいます。
特に以下のようなケースでは、デフォルトの統計精度では太刀打ちできません。
- データ分布の偏りが大きい: ある値にはデータが集中し、ある値にはほとんど存在しないような、べき乗則に従うようなデータ分布。
- 複合条件のクエリ: 複数の列にまたがる絞り込みが頻繁に行われる場合。
- ヒストグラムのバケット不足: 100バケットでは、データの微妙な変化を拾いきれず、平均的な分布として近似されてしまう。
もし `EXPLAIN ANALYZE` で「Actual Rows」と「Estimated Rows」の乖離が著しいなら、それはオプティマイザに「もっと細かく見てくれ」と頼むべきサインです。
調整の勘所:全体を上げるか、列単位で絞るか
ここで陥りがちな罠が、`postgresql.conf` で `default_statistics_target` を一律に 500 や 1000 に引き上げてしまうことです。
確かに精度は向上しますが、以下のトレードオフを忘れてはなりません。
1. ANALYZEの負荷増大: 統計取得にかかる時間が長くなり、インクリメンタルな負荷がシステム全体を圧迫します。
2. システムカタログの肥大化: `pg_statistic` テーブルが肥大化し、オプティマイザの計算量も増えます。
プロの推奨は「必要な列だけを狙い撃つ」ことです。
全体を一括で上げるのではなく、特定のカラムに対して個別に指定する方法を強くおすすめします。
— 特定の列だけ統計精度を上げる(例: 500バケットに増やす)
ALTER TABLE orders ALTER COLUMN status SET STATISTICS 500;
こうすることで、重要なインデックス列や検索条件の主軸となる列に対してのみ精度を高め、全体的なパフォーマンスとメンテナンスコストのバランスを最適化できます。
最後に:チューニングは「観測」から始まる
結局のところ、データベースのチューニングに銀の弾丸はありません。`default_statistics_target` を調整した後は、必ず `ANALYZE` を実行し、その結果が実際のクエリ実行計画にどう反映されたかを「観測」してください。
統計情報は、データベースという巨大な脳が持つ「記憶の解像度」です。その解像度が低ければ、いくらCPUやメモリを積んでも、脳は誤った判断を繰り返します。
皆さんの現場でも、もし「なぜかこのクエリだけ遅い」という謎の挙動があれば、一度 `pg_stats` を覗いてみてください。そこに、まだ見ぬ最適化のヒントが隠されているはずです。
それでは、良いクエリライフを。
コメント