VACUUMという名の「終わらない掃除」と、その深淵なるアーキテクチャ
PostgreSQLを長く触っていると、誰しも一度は「VACUUM」という言葉に頭を抱える瞬間が訪れるはずです。新米エンジニアの頃は「なぜ勝手に掃除が走ってCPUを食うんだ」と呪い、少し慣れると「autovacuumの設定チューニングで徹夜した」という武勇伝が生まれる。
しかし、PostgreSQLのコアアーキテクチャを理解した今となっては、このVACUUMこそがMVCC(多版同時実行制御)という美しくも厄介な仕組みを支える「心臓部」であると痛感しています。今日は、マニュアルの行間を読み解きながら、VACUUMが裏で何をしているのかを少し深く掘り下げてみましょう。
—
デッドタプル:なぜ「捨てる」という簡単なことができないのか
PostgreSQLのMVCCにおいて、UPDATEやDELETEは物理的な破壊を伴いません。古いデータには「削除フラグ(正確にはXMIN/XMAXのトランザクションID比較)」が立てられるだけです。これにより、他のトランザクションは参照の一貫性を保てるわけですが、代償として「デッドタプル(死んだタプル)」という亡霊がページ内に蓄積し続けます。
VACUUMの仕事は、この亡霊を特定し、空いた領域を「将来のINSERTやUPDATEのために再利用可能にする」ことです。ここで重要なのは、VACUUMは即座にOSへファイルを返却するわけではないという点です。あくまで「ページ内の空き領域マップ(Free Space Map: FSM)」を更新し、PostgreSQL内部で使い回せるようにする作業なのです。
VACUUMの「ヒープとインデックス」という二重奏
VACUUMのプロセスを内部的に分解すると、主に2つのフェーズに大別されます。
1. ヒープスキャンとデッドタプルの特定
まずはテーブル(ヒープ)をスキャンし、不要になったタプルを洗い出します。ここで特定されたタプルのオフセット番号は、メモリ上の「バッファ」に一時格納されます。
2. インデックスのクリーンアップ
ここが意外と見落とされがちですが、ヒープを掃除したら、当然そのタプルを指しているインデックスエントリも消さなければなりません。インデックスの構造を書き換えるのはコストの高い処理です。そのため、PostgreSQLはヒープスキャンで集めた情報を基に、インデックスをまとめて掃き出す「インデックス・スキャン・フェーズ」に移行します。
※最近のPostgreSQL(特にv14以降)では、このインデックスクリーンアップの挙動も劇的に進化していますが、基本は「ヒープの掃除」と「インデックスの掃除」の連動にあります。
パフォーマンストラブルの「温床」をどう見抜くか
現場でよく遭遇する「VACUUMが追いつかない」問題。これは単なる設定値のミスではないことが多いです。
- 長いトランザクションの放置: これが最も多い原因です。たとえ数ミリ秒のクエリでも、BEGINしてCOMMITを忘れた接続が一つあるだけで、そのトランザクションが終了するまで、PostgreSQLは「それ以降のデッドタプル」を物理的に消去できません(xminの巻き戻しを防ぐため)。結果、テーブルが肥大化し、フルスキャンが遅くなるという悪循環に陥ります。
- インデックスの肥大化(Bloat): インデックスはヒープほど単純には再利用されません。特に頻繁に更新されるカラムにインデックスを貼ると、VACUUMが追いつかず、インデックスの「スカスカ具合」が進行します。こうなると、インデックス・スキャンが本来の性能を発揮できなくなります。
エンジニアとしての「付き合い方」
結論として、VACUUMは「悪者」ではなく、PostgreSQLがパフォーマンスを維持するための「生命維持装置」です。
autovacuumの設定をいじる前に、まず「なぜ掃除が必要なほどゴミが溜まっているのか?」を考えるべきです。アプリケーション側で不要なSELECT FOR UPDATEを多用していないか? 長いバッチ処理がヒープの削除をブロックしていないか?
運用でカバーするのではなく、設計段階で「VACUUMの負荷をどう最小化するか」を考えるのが、熟練エンジニアの流儀ではないでしょうか。もし皆さんの環境でVACUUMのログが毎秒流れるように出ていたら、それはPostgreSQLが悲鳴を上げているサインかもしれません。ぜひ一度、`pg_stat_user_tables` の `n_dead_tup` を眺めてみてください。そこには、あなたのDBの現在の健康状態が如実に表れています。
さて、今日はこのあたりで。次は「VACUUM FULLとREINDEX CONCURRENTLY、どちらを選ぶべきか」という地獄の議論についてお話しできればと思います。それでは、良きデータベースライフを。
コメント