「なあ、最近クエリが急に遅くなったって相談、よく聞くんだよね。で、調べてみると大抵が『統計情報の鮮度』でつまづいてる。今日はそのあたりの話をしようか。」
—
統計情報が「古い」と何が起きるか?
PostgreSQLのオプティマイザ(プランナ)は、非常に賢いけれど、一つだけ決定的な弱点がある。「今、テーブルにどれくらいのデータがあるか」を自分ではリアルタイムに把握していないんだ。
奴らは `pg_statistic` というカタログに記録された「統計情報」という名のカンニングペーパーを見て、実行計画を立てる。もしこのカンニングペーパーが、実際のテーブルの中身と乖離していたら?
「10件しかないからインデックスを使うよりフルスキャンの方が速いな」と判断した結果、実は裏で100万件に増えていて、システムが死ぬ。……これが、現場で起きるクエリ遅延の典型的なパターンだね。
自動ANALYZEの仕組みを理解する
このカンニングペーパーを定期的に書き換えてくれるのが `autovacuum` デーモンだ。具体的には、以下の計算式でANALYZEのタイミングを測っている。
「しきい値 = autovacuum_analyze_threshold + (テーブルの全タプル数 × autovacuum_analyze_scale_factor)」
デフォルトの設定だと、`scale_factor` は `0.1` (つまり10%)。
例えば100万行あるテーブルなら、10万行の変更があった時点でANALYZEが走る。……これ、大規模なテーブルだと「遅すぎないか?」って思わない?
現場で直面する「10%の罠」
1億行あるテーブルで、1000万行更新されるまでANALYZEが走らないとしたら、その間ずっとオプティマイザは古い情報でクエリを捌くことになる。これじゃあ、パフォーマンスが安定するはずがないよね。
逆に、数千行しかない小さなマスタテーブルで `0.1`(10%)を設定していても、そもそもANALYZEのコストが低いから問題にはなりにくい。
ここで僕がいつも後輩に伝えているのは、「テーブルの規模感に合わせて、このしきい値を個別調整しよう」ということだ。
実践的なチューニング:個別設定のススメ
`postgresql.conf` で全体設定をいじるのもいいけれど、特定の巨大テーブルだけ個別に設定するのが、プロのやり方だ。
— 例えば、特定の巨大テーブルだけ、変更率1%でANALYZEを走らせる設定
ALTER TABLE orders SET (
autovacuum_analyze_scale_factor = 0.01,
autovacuum_analyze_threshold = 1000
);
こうすることで、巨大テーブルの統計情報の鮮度を高く保ちつつ、小さなテーブルまで過剰にANALYZEしてシステム負荷をかけるリスクを回避できる。
僕が気をつけている「判断基準」
- データ更新が激しい巨大テーブル: `scale_factor` を 0.01 ~ 0.05 くらいまで下げる。
- 逆に、ほとんど更新されない巨大テーブル: 統計情報が狂うことは少ないから、デフォルトのままでいいか、あるいは `0.2` くらいまで緩めてもいい。
- そもそも「いつ更新されたか」を確認する: これ、意外と忘れがちなんだけど、まずは今の統計情報がいつ更新されたかを確認する癖をつけよう。
SELECT relname, last_analyze, last_autoanalyze
FROM pg_stat_user_tables
ORDER BY last_analyze DESC;
最後に:完璧を目指さないこと
忘れないでほしいのは、「頻繁にANALYZEすればいいわけではない」ということだ。ANALYZEだってCPUやI/Oを消費するタスクだ。あまりに頻繁に走らせれば、それ自体がパフォーマンスを劣化させる原因になる。
「クエリの実行計画が狂って深刻なトラブルになるリスク」と「ANALYZEの負荷」のバランスを取る。この匙加減こそが、DBエンジニアの腕の見せ所だよ。
まずは、今抱えているクエリ遅延のテーブルが、いつANALYZEされたのかを確認するところから始めてみて。意外な発見があるはずだ。
また何かあったら聞きに来てくれ。一緒にクエリを最適化しよう。
コメント