「統計情報が古い」でクエリが死ぬ前に。PostgreSQLのANALYZEを使いこなす現場の知恵
現場でPostgreSQLを触っていると、たまに遭遇するんですよね。「さっきまで爆速だったクエリが、急に数秒もかかるようになった……」という現象。
大抵の場合、原因は実行計画(Execution Plan)の劣化です。PostgreSQLのオプティマイザは、テーブルの統計情報を元に「どうやってデータを読み込むのが一番速いか」を計算しています。つまり、統計情報が現実と乖離していると、オプティマイザは平気で的外れな実行計画を立てるわけです。
今日は、そんな「悲劇」を未然に防ぐ、あるいは起きてしまったときに即座に鎮火するための`ANALYZE`の手動実行について、現場の勘所を共有します。
—
なぜ「自動収集」じゃダメなときがあるのか
PostgreSQLには`autovacuum`という優秀な守護神がいて、通常はバックグラウンドで統計情報を更新してくれます。でも、これには「閾値」があるんです。
例えば、テーブルのサイズが巨大な場合、デフォルトの設定だと「全件の20% + いくつか」の変更がないと`ANALYZE`が走りません。数百万行あるテーブルで、10万行くらい一気に更新した直後だと、オプティマイザはまだ「古いデータ」の状態だと思い込んで、フルスキャンを選択したり……なんてことは日常茶飯事。
そんなとき、「今すぐ統計を更新しろ!」と手動で命令を下すのが、我々エンジニアの腕の見せ所です。
—
手動ANALYZEの基本
使い方は至ってシンプルです。SQLからコマンドを投げるだけ。
— テーブル全体の統計情報を更新
ANALYZE target_table_name;
— 特定の列だけピンポイントで更新(これが地味に便利!)
ANALYZE target_table_name (column_a, column_b);
実務で「これだけは知っておけ」というポイント
ここからが教科書にはあまり書いていない、現場のTIPSです。
1. 大量更新の直後に実行する癖をつける
バッチ処理や移行スクリプトで数万件以上のデータを`UPDATE`や`INSERT`した後は、スクリプトの最後に必ず`ANALYZE`を組み込みましょう。これだけで、その後の検索パフォーマンスが劇的に安定します。
2. 特定列の「分布」が偏ったときに効く
例えば「ステータス」フラグのような、特定の値にデータが集中している列の場合、`ANALYZE`を叩くことで、オプティマイザがその偏りを正しく理解してくれます。「特定のステータスだけやたら遅い」というクエリの改善には、その列を指定した`ANALYZE`が特効薬になることが多いです。
3. ロックの挙動に注意
`ANALYZE`はテーブル全体に共有ロック(AccessShareLock)を取得します。基本的には読み取りを阻害しませんが、極めて巨大なテーブルで実行すると、わずかながら負荷がかかります。深夜のメンテナンスバッチで流すのが定石ですが、本番稼働中にやるなら、サーバーの負荷状況を確認してからにしましょうね。
—
「遅い」と感じたら、まずは実行計画を疑う
もしクエリが遅いと感じたら、すぐさま`EXPLAIN ANALYZE`を叩いてみてください。
EXPLAIN ANALYZE SELECT FROM users WHERE status = ‘active’;
出力結果の中に「`rows=…`(予測行数)」と「`actual rows=…`(実際の行数)」という項目がありますよね? ここが極端にズレていたら、それは統計情報が古い証拠です。
「あ、これ統計が追いついてないな」と思ったら、迷わず手動で`ANALYZE`を打つ。そしてもう一度`EXPLAIN ANALYZE`を叩いて、実行計画が改善されたか確認する。このサイクルを素早く回せるようになると、トラブルシューティングのスピードが一段階変わりますよ。
—
まとめ
- `autovacuum`を過信しない: 大量更新時は統計情報が古くなりがち。
- 適材適所の`ANALYZE`: テーブル全体だけでなく、特定の列を狙い撃ちする。
- 癖づけが大事: 大規模なデータ更新バッチには、必ず`ANALYZE`を仕込んでおく。
データベースは生き物です。統計情報は、その生き物の「今の姿」をオプティマイザに伝えるための大切な手紙のようなもの。最新の情報を適宜送ってあげることで、PostgreSQLは最高のパフォーマンスで応えてくれます。
また現場で詰まったら、いつでも聞きに来てくださいね。一緒にコードを見ていきましょう!
コメント