2026年6月17日

2026年6月17日

PostgreSQLのVACUUMでデータベースを最適化する方法|autovacuumの調整とVACUUM FULLのロックに注意

PostgreSQLの性能を保つには、更新・削除で生まれる不要な行(デッドタプル)を、VACUUMで定期的に回収する必要があります。多くの場合は自動で動くautovacuumに任せたうえで、pg_stat_user_tables でデッドタプルの量を監視し、更新の多いテーブルだけ設定を調整するのが基本の運用です。この記事では、VACUUMとVACUUM FULLのロックの違い、状態の確認方法、autovacuumのパラメータの意味と調整方法、VACUUMが効かないときの原因の探し方を説明します。

なぜVACUUMが必要なのか

PostgreSQLはMVCC(多版型同時実行制御)により、読み取りと書き込みを互いに待たせずに処理します。UPDATE は行をその場で書き換えず、新しい版を追加して古い版を残します。DELETE も行に削除の印を付けるだけです。他のトランザクションが、まだ古い版を見ている可能性があるためです。

この古い版がデッドタプルです。放置すると次の問題が起きます。

  • テーブルとインデックスが膨らみ(肥大化、bloat)、同じ件数を読むのに多くのページを読むようになる
  • 統計情報が古くなり、プランナーが不適切な実行計画を選ぶ
  • トランザクションIDの周回(wraparound)に備えた「凍結」処理が進まず、最悪の場合は書き込みを受け付けなくなる

VACUUMは、どのトランザクションからも見えなくなったデッドタプルの領域を「再利用可能」にし、あわせて行の凍結などの管理情報を更新します。

VACUUMとVACUUM FULLの違い

名前は似ていますが、動き方とロックがまったく異なります。

通常の VACUUM は、テーブルに SHARE UPDATE EXCLUSIVE ロックを取ります。これは同じテーブルへのVACUUMや ALTER TABLE などのスキーマ変更とは競合しますが、SELECT・INSERT・UPDATE・DELETE は通常どおり実行できます。そのため、稼働中のサービスでも実行できます。ただし、回収した領域は基本的にテーブル内で再利用されるだけで、ファイルサイズは小さくなりません(テーブル末尾の空きページだけは切り詰められることがあります)。

VACUUM FULL は、テーブルを新しいファイルに書き直して、不要な領域をOSに返します。その代わり、ACCESS EXCLUSIVE ロックを取るため、処理が終わるまでそのテーブルへの SELECT も含むすべての操作が待たされます。さらに、書き直しの間は新旧2つのファイルが存在するため、テーブルとインデックスの合計サイズ程度の空きディスクが必要です。

VACUUM FULLは、大量削除の後などで大きな空き領域をOSに返したい場合に限り、メンテナンス時間を取って実行するもので、日常の定期作業には向きません。サービスを止められない場合は、拡張機能 pg_repack を使うと、短時間のロックだけでテーブルを作り直せます。

デッドタプルの量を確認する

まず、どのテーブルにデッドタプルがたまっているかを pg_stat_user_tables で確認します。

SELECT relname,
       n_live_tup,
       n_dead_tup,
       round(n_dead_tup * 100.0 / 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 20;

見るべきポイントは次のとおりです。

  • n_dead_tup が多く、dead_pct が高い状態が続いているテーブルはないか
  • last_autovacuum が極端に古い、または空(一度も実行されていない)テーブルはないか
  • 更新の多いテーブルで、autovacuum_count が増えているか

これらの値は統計情報に基づく推定値です。

テーブルの実際のサイズは次のように確認できます。

SELECT relname,
       pg_size_pretty(pg_total_relation_size(relid)) AS total_size
FROM pg_stat_user_tables
ORDER BY pg_total_relation_size(relid) DESC
LIMIT 10;

手動でVACUUMを実行する

大量の DELETE やバッチ更新の直後など、autovacuumを待たずに回収したい場合は、手動で実行します。

VACUUM (VERBOSE, ANALYZE) orders;

ANALYZE を付けると統計情報も同時に更新され、VERBOSE は回収したタプル数などを表示します。ロック待ちを避けたい場合は SKIP_LOCKED を加えます。

データベース全体をコマンドラインから処理する場合は vacuumdb を使います。

vacuumdb --analyze --verbose --dbname=mydb

実行中の進み具合は、別のセッションから pg_stat_progress_vacuum ビューで確認できます。大きなテーブルで「止まっているのか、進んでいるのか」を判断するときに便利です。

autovacuumの仕組みとパラメータ

autovacuumは、ランチャープロセスが定期的に各データベースを調べ、条件を満たしたテーブルにワーカーを送ってVACUUMとANALYZEを行う仕組みです。既定で有効(autovacuum = on)で、無効にするのは避けてください。

VACUUMが実行される条件は、次の式で決まります。

デッドタプル数 > autovacuum_vacuum_threshold + autovacuum_vacuum_scale_factor × テーブルの行数

既定値は autovacuum_vacuum_threshold = 50、autovacuum_vacuum_scale_factor = 0.2 です。つまり、行数の20%を超えるデッドタプルがたまるまで実行されません。1万行のテーブルなら約2,000行で実行されますが、1億行のテーブルでは約2,000万行のデッドタプルがたまるまで待つことになり、その間に肥大化が進みます。大きなテーブルで問題が起きやすいのはこのためです。

主なパラメータは次のとおりです。

  • autovacuum_naptime:各データベースを調べる間隔(既定1分)
  • autovacuum_max_workers:同時に動くワーカー数の上限(既定3)
  • autovacuum_analyze_threshold / autovacuum_analyze_scale_factor:ANALYZEの実行条件(既定50と0.1)
  • autovacuum_vacuum_insert_threshold / autovacuum_vacuum_insert_scale_factor:挿入だけのテーブルでもVACUUMを走らせる条件(PostgreSQL 13以降)
  • autovacuum_vacuum_cost_delay / autovacuum_vacuum_cost_limit:I/O負荷を抑えるための速度制限。一定量の処理ごとに休む仕組み
  • autovacuum_work_mem:ワーカーが使うメモリ。未設定(-1)なら maintenance_work_mem の値を使う
  • autovacuum_freeze_max_age:周回対策の強制VACUUMが始まる年齢(既定2億トランザクション)

大きなテーブルだけ設定を変える

scale_factor をサーバー全体で小さくすると、小さなテーブルにまで頻繁にVACUUMが走るようになります。更新の多い大きなテーブルにだけ、テーブル単位で設定するのが扱いやすい方法です。

ALTER TABLE orders SET (
  autovacuum_vacuum_scale_factor = 0.01,
  autovacuum_vacuum_threshold = 1000,
  autovacuum_analyze_scale_factor = 0.02
);

この例では、行数の1%+1,000行のデッドタプルでVACUUMが走ります。1億行のテーブルなら約100万行です。設定はテーブルの reloptions に保存され、\d+ orders で確認できます。

それでも追いつかない場合は、autovacuum_vacuum_cost_limit を上げて速度制限を緩めます。VACUUMが早く終わる代わりにディスクI/Oが増えます。

ALTER SYSTEM SET autovacuum_vacuum_cost_limit = 1000;
SELECT pg_reload_conf();

なお、cost_limit は動いているワーカー全体で分け合うため、autovacuum_max_workers だけを増やしても、1つひとつのワーカーが遅くなるだけのことがあります。

設定を変えたら、autovacuumの動きをログで確認します。log_autovacuum_min_duration に指定した時間以上かかったautovacuumがログに記録されます(PostgreSQL 15以降の既定は10分)。調整中は '1s' などに下げて、どのテーブルでどれだけ時間がかかっているかを見ると判断しやすくなります。

VACUUMしてもデッドタプルが減らないとき

VACUUMを実行しても n_dead_tup が減らない場合、多くはデッドタプルを「まだ誰かが見ている可能性がある」状態が原因です。VACUUMは、最も古いトランザクションが開始した時点以降に不要になった行を回収できません。

最初に、長時間開いたままのトランザクションを探します。

SELECT pid, usename, state, xact_start, backend_xmin, left(query, 60) AS query
FROM pg_stat_activity
WHERE backend_xmin IS NOT NULL
ORDER BY xact_start
LIMIT 10;

state が idle in transaction のまま何時間も経っているセッションは、アプリケーションがトランザクションを閉じ忘れている典型例です。idle_in_transaction_session_timeout を設定すると、一定時間放置されたトランザクションを自動で切断できます。

ほかに、使われなくなったレプリケーションスロット(pg_replication_slots)、レプリカ側の hot_standby_feedback、コミットも取り消しもされていないプリペアドトランザクション(pg_prepared_xacts)も、回収を止める原因になります。

あわせて、周回対策の状況も定期的に確認してください。

SELECT datname, age(datfrozenxid) FROM pg_database ORDER BY 2 DESC;

値が autovacuum_freeze_max_age(既定2億)に近づくと、autovacuumが周回防止のための強制VACUUMを始めます。さらに大きく進んで約20億に近づくと、データ保護のために新しいトランザクションを受け付けなくなります。値が増え続けて下がらない場合は、上で挙げた原因を調べてください。(2026年10月現在のサポート対象バージョンを前提にしています。既定値は版によって変わるため、SHOW で手元の値を確認してください。)

まとめ

  • 通常のVACUUMは読み書きを止めずに動く。VACUUM FULLは ACCESS EXCLUSIVE ロックでテーブルを止めるため、日常の作業には使わない
  • pg_stat_user_tables の n_dead_tup と last_autovacuum で、回収が追いついているかを監視する
  • autovacuumは無効にせず、更新の多い大きなテーブルだけ scale_factor をテーブル単位で下げる
  • 追いつかない場合は autovacuum_vacuum_cost_limit を調整し、log_autovacuum_min_duration で効果を確かめる
  • デッドタプルが減らないときは、長時間のトランザクションやレプリケーションスロットを疑う

お気軽にご相談ください

お見積りへ お問い合わせへ
▲