2026年9月7日
2026年9月7日
PostgreSQLのvacuumとautovacuumの管理方法|監視・調整・止まる原因の調べ方
PostgreSQL では、更新や削除をした古い行(デッドタプル)がすぐには消えず、VACUUM が後から片付ける仕組みになっています。通常は autovacuum が自動で処理しますが、更新の多い大きなテーブルでは追いつかず、テーブルの肥大化や性能低下、さらにはトランザクション ID の周回(ラップアラウンド)による書き込み停止につながることがあります。この記事では、VACUUM が何をしているか、autovacuum がいつ動くか、状態の監視方法、テーブルごとの調整、そして「VACUUM しても片付かない」ときの原因の調べ方を順に説明します。
WordPress 本体は MySQL / MariaDB を前提としており、PostgreSQL は公式にはサポートされていません(2026年10月現在)。この記事は WordPress と並べて運用する別システムなどで PostgreSQL を管理する場面を想定しています。MySQL(InnoDB)には VACUUM という操作はなく、不要な旧バージョンの行はパージスレッドが自動で片付けます。
VACUUMが必要な理由と、やっていること
PostgreSQL は MVCC(多版型同時実行制御)という方式で、行を更新すると古い版を残したまま新しい版を書き込みます。古い版は、それを見ている可能性のあるトランザクションが無くなった時点で不要になりますが、自動では消えません。VACUUM の主な役割は次の四つです。
- 不要になった行の領域を、同じテーブルで再利用できる状態にする。ファイルサイズは基本的に縮まず、末尾の空きページだけが OS に返されます。
- 可視性マップを更新する。これにより、インデックスだけで結果を返す「Index Only Scan」が効きやすくなります。
- 古いトランザクション ID を「凍結(freeze)」し、ID の周回による問題を防ぐ。
VACUUM ANALYZEとした場合や autovacuum の ANALYZE で、実行計画に使う統計を更新する。
VACUUM FULL はこれとは別物で、テーブルを丸ごと書き直してファイルを縮めます。その間はテーブルに対する読み書きがすべて止まり(ACCESS EXCLUSIVE ロック)、書き直し先として一時的にもう1テーブル分のディスク容量が要ります。日常の運用では通常の VACUUM と autovacuum に任せ、VACUUM FULL は肥大化を解消したいときに、メンテナンス時間を取って行うものと考えてください。
autovacuumはいつ動くのか
autovacuum は、テーブルごとのデッドタプル数が次のしきい値を超えると、そのテーブルを VACUUM します。
しきい値 =
autovacuum_vacuum_threshold(既定 50)+autovacuum_vacuum_scale_factor(既定 0.2)× テーブルの行数
既定値のままだと、1,000万行のテーブルでは約200万行が不要になるまで VACUUM されません。大きなテーブルほど片付けの間隔が長くなり、その間に肥大化や性能低下が進む、というのが典型的なつまずきです。ANALYZE も同じ考え方で、autovacuum_analyze_threshold と autovacuum_analyze_scale_factor(既定 0.1)で決まります。
PostgreSQL 13 以降は、挿入だけが続くテーブルでも挿入行数をきっかけに VACUUM が走る設定(autovacuum_vacuum_insert_threshold など)が加わりました。新しいバージョンでは、大きなテーブル向けにしきい値の上限を設ける設定なども追加されています。既定値や使える項目はバージョンで異なるため、SHOW autovacuum_vacuum_scale_factor; のように実際の値を確かめてから調整してください。
状態を監視する
まず、どのテーブルにデッドタプルが溜まっているか、autovacuum が最後にいつ動いたかを確認します。
SELECT relname,
n_live_tup,
n_dead_tup,
round(100.0 * n_dead_tup / nullif(n_live_tup + n_dead_tup, 0), 1) AS dead_pct,
last_vacuum, last_autovacuum, last_autoanalyze,
autovacuum_count
FROM pg_stat_user_tables
ORDER BY n_dead_tup DESC
LIMIT 10;
n_dead_tup が大きいまま last_autovacuum が長く更新されていないテーブルが要注意です。実行中の VACUUM の進み具合は pg_stat_progress_vacuum ビューで見られます。
次に、トランザクション ID の周回までの余裕を確認します。
-- データベースごと
SELECT datname, age(datfrozenxid) AS xid_age
FROM pg_database
ORDER BY xid_age DESC;
-- テーブルごと
SELECT c.oid::regclass AS table_name, age(c.relfrozenxid) AS xid_age
FROM pg_class c
WHERE c.relkind IN ('r', 'm', 't')
ORDER BY xid_age DESC
LIMIT 10;
トランザクション ID は約21億で周回するため、PostgreSQL は age が autovacuum_freeze_max_age(既定 2億)を超えたテーブルに対して、autovacuum を無効にしていても周回防止の VACUUM を強制的に走らせます。それでも凍結が追いつかず限界に近づくと、警告がログに出た後、データを守るために新しい書き込みを受け付けなくなります。xid_age が既定のしきい値を大きく超えて増え続けている場合は、後述の「VACUUM が片付けられない原因」を早めに調べてください。
autovacuum の動作をログに残すには log_autovacuum_min_duration を設定します。指定した時間(ミリ秒)以上かかった autovacuum が記録され、どのテーブルで何行を片付けたかが分かります。0 にするとすべて記録されますが、ログの量が増える点に注意してください。
テーブルごとに調整する
全体の設定を変える前に、更新の多い大きなテーブルだけ設定を変える方法が安全です。
ALTER TABLE orders SET (
autovacuum_vacuum_scale_factor = 0.02,
autovacuum_vacuum_threshold = 1000,
autovacuum_analyze_scale_factor = 0.01
);
-- 設定の確認
SELECT relname, reloptions FROM pg_class WHERE relname = 'orders';
この例では、行数の2%が不要になった時点で VACUUM が走るようになります。しきい値を下げると VACUUM の回数は増えますが、1回あたりの作業量が減り、肥大化を抑えやすくなります。
VACUUM がディスク I/O を使いすぎないよう、autovacuum にはコストに応じた待ち時間(autovacuum_vacuum_cost_delay、autovacuum_vacuum_cost_limit)があります。ディスク性能に余裕があるのに autovacuum が追いつかない場合は、cost_limit を上げると処理が速く進みます。同時に動けるワーカー数は autovacuum_max_workers で決まります。全体設定は postgresql.conf で変更し、多くの項目は設定の再読み込みで反映されますが、バージョンによっては再起動が必要な項目もあります。変更前に設定ファイルのコピーを取り、変更内容を記録しておいてください。
VACUUMしても片付かないときの原因
VACUUM を実行したのにデッドタプルが減らない場合、多くは「まだ誰かが古い行を見ている可能性がある」と判定されているのが原因です。可能性の高い順に確認します。
1. 長時間続いているトランザクション
もっとも多い原因です。特に、トランザクションを開始したまま何もしていない「idle in transaction」の接続が典型で、その接続が始まった時点より新しい不要行は片付けられません。
SELECT pid, usename, state, xact_start,
now() - xact_start AS duration,
backend_xmin,
left(query, 60) AS last_query
FROM pg_stat_activity
WHERE xact_start IS NOT NULL
ORDER BY xact_start
LIMIT 10;
アプリケーションの不具合で COMMIT されていない接続が見つかったら、まずアプリ側を修正します。応急処置として pg_terminate_backend(pid) で切断できますが、処理中のトランザクションは取り消されるため、内容を確認してから行ってください。再発防止には idle_in_transaction_session_timeout の設定が有効です。
2. レプリケーションスロット
使われなくなったスロットが残っていると、古い行と WAL が保持され続けます。SELECT slot_name, active, xmin, catalog_xmin FROM pg_replication_slots; で active が false のまま放置されているものがないか確認します。削除はそのスロットを使う複製が不要と確定してから行います。
3. スタンバイの hot_standby_feedback
スタンバイ(レプリカ)で hot_standby_feedback を有効にしていると、レプリカ側で実行中の長いクエリが、プライマリの VACUUM を止めることがあります。レプリカでの長時間の集計がないか、レプリカの pg_stat_activity も確認します。
4. 準備済みトランザクション(2相コミット)
SELECT gid, prepared FROM pg_prepared_xacts; で古いものが残っていないか確認します。
片付いたかを確かめる
VACUUM (VERBOSE) orders; を実行すると、片付けた行数と「まだ削除できない」行数が表示されるので、原因を取り除いた後に再実行して減ったかどうかで確かめられます。
肥大化したテーブルを縮めたいとき
通常の VACUUM ではファイルサイズは縮まないため、すでに大きく膨らんだテーブルは別の対処が必要です。方法は、メンテナンス時間を取って VACUUM FULL を実行するか、第三者製の拡張機能 pg_repack のように、ロックの時間を短くして書き直すツールを使うかです。pg_repack は PostgreSQL 本体とは別に開発されているため、対応バージョンと使い方をプロジェクトのドキュメントで確認し、検証環境で試してから使ってください。いずれの方法も、事前にバックアップを取り、ディスクの空き容量がテーブルとインデックスの合計以上あることを確かめておきます。
まとめ
- VACUUM は不要な行の領域を再利用可能にし、可視性マップの更新とトランザクション ID の凍結を行います。ファイルを縮めるのは
VACUUM FULLで、強いロックがかかります。 - autovacuum は「しきい値+割合×行数」で動くため、大きなテーブルでは間隔が長くなりがちです。テーブル単位の設定で調整します。
pg_stat_user_tablesでデッドタプルと最終実行時刻を、age(datfrozenxid)で周回までの余裕を監視します。- 片付かないときは、長いトランザクション、放置されたレプリケーションスロット、スタンバイのフィードバック、準備済みトランザクションの順に確認します。