VACUUMとVisibility Map:インデックス設計の裏側にある「見えない支配者」
やあ。最近、データベースのチューニングで「なぜかインデックスが効かない」とか「VACUUMが重すぎてIOがパンクする」なんて悩みにぶつかっていないか?
PostgreSQLを触っていると避けて通れないのがVACUUMとVisibility Map(可視性マップ)だ。これらは単なる「お掃除機能」や「おまけのビットマップ」じゃない。実は、クエリプランナが「インデックスを使うか、テーブルをフルスキャンするか」を決める時の、極めて重要な判断材料になっているんだ。
今日は、教科書にはあまり書いていない「現場の実践的な視点」でこの話を紐解いていこう。
—
VACUUMは「ゴミ掃除」以上の仕事をしている
まず前提の確認だ。PostgreSQLはMVCC(多版同時実行制御)を採用しているから、レコードを更新・削除しても、古いデータは即座には消えない。「デッドタプル」としてテーブル内に残り続ける。
これを回収するのが`VACUUM`だ。でも、ここで意識してほしいのは、VACUUMがインデックスに対しても掃除をしているという事実だ。
インデックスは、テーブルの物理的な場所(TID)を指している。VACUUMがテーブルのデッドタプルを消すと、インデックス内に残った「もう存在しない行へのポインタ」も掃除してやらないと、インデックスがどんどん膨らんでいく(インデックスの肥大化)。
これが「インデックスが効かなくなる」原因の一つだ。
- インデックスが肥大化すると、I/O効率が落ちる。
- クエリプランナは「これだけインデックスがスカスカ(無駄が多い)なら、いっそテーブルをフルスキャンした方が早いな」と判断して、インデックスを無視し始める。
だから、「適切なVACUUM設定は、インデックス性能を維持するための必須条件」なんだ。
—
Visibility Map:クエリプランナの「虎の巻」
次に「Visibility Map(VM)」の話をしよう。これが今回の核心だ。
VMは、テーブルのページごとに「このページにある全てのタプルは、現在誰からも見えていて、誰も更新していない(=完全に可視である)」という情報を保持しているビットマップだ。
これの何がすごいかというと、「Index Only Scan(インデックスのみスキャン)」が可能かどうかを瞬時に判断できる点にある。
通常、インデックスには「どのTIDが最新か」という情報までは載っていない。だからPostgreSQLは、インデックスで当たりをつけてから、わざわざテーブルの本体(ヒープ)を見に行って「このデータ、今本当に有効?」と確認する必要があるんだ。これが通常のインデックススキャンだ。
でも、VMが「このページのデータは全員可視だよ」と保証してくれていれば、テーブル本体を見に行く必要がなくなる。これがIndex Only Scanだ。
— 試しに実行計画を見てみよう
EXPLAIN ANALYZE
SELECT id FROM users WHERE status = ‘active’;
— Index Only Scan が出ていればVMがうまく働いている証拠だ
もしここで `Seq Scan` や `Index Scan` が出ているなら、VMが「データが可視かどうか確信が持てない」と言っているということ。多くの場合、`VACUUM`が追い付いておらず、VMのビットが立っていないのが原因だ。
—
実務で意識すべき「インデックス設計」のコツ
現場で僕がよくアドバイスしているのは、以下の2点だ。
1. VACUUMの頻度を恐れるな
自動VACUUM(autovacuum)をチューニングして、更新の激しいテーブルは早めに掃除させること。特に `autovacuum_vacuum_scale_factor` をデフォルトのまま(20%)にしておくと、数百万行あるテーブルでは掃除が遅すぎてVMがいつまで経っても更新されない。
— よく更新されるテーブルなら、しきい値を下げて頻度を上げる
ALTER TABLE users SET (autovacuum_vacuum_scale_factor = 0.01);
2. 「Index Only Scan」を狙うならカバーリングインデックス
もし特定のクエリを爆速にしたいなら、`INCLUDE`句を使ったカバーリングインデックスを検討してくれ。
— statusだけでなく、よく使う値を含めてしまう
CREATE INDEX idx_users_status_include_id ON users (status) INCLUDE (id);
こうすることで、VMとインデックスが連携し、テーブル本体に一切触れずにクエリが完了するようになる。これがPostgreSQLにおける最強の高速化パターンの一つだ。
—
まとめ:データベースは生き物だ
VACUUMやVisibility Mapを意識するというのは、つまり「データベースの健康状態を管理する」ということなんだ。
インデックスはただ作ればいいわけじゃない。VACUUMが適切に動いて、VMが最新の情報をクエリプランナに提供し続ける。そのサイクルが回って初めて、設計したインデックスが本来の性能を発揮してくれる。
「なんとなく遅いな」と感じたら、`pg_stat_user_tables` で `last_autovacuum` の時間を確認してみてほしい。そこには、君のデータベースがどれだけ「掃除」を待ちわびているか、そのヒントが隠されているはずだ。
また何か壁にぶつかったら、いつでも聞きに来てくれ。チューニングはパズルみたいで面白いだろ?楽しんでいこうぜ!
コメント