PostgreSQLではUPDATEやDELETEを大量に実行すると、不要になった行(不要タプル)がテーブル内部に残ります。通常はVACUUMによって不要領域が回収されますが、実行してもテーブルサイズが減らない、検索性能が改善しないといった問題が発生することがあります。
この記事では、大量更新や削除を繰り返したテーブルでVACUUMが十分に機能しない場合に確認すべき原因や調査方法、改善策について詳しく解説します。
PostgreSQLのVACUUMが行っている処理を理解する
PostgreSQLでは、UPDATEやDELETEを実行しても、すぐに古い行データが物理的に削除されるわけではありません。これはMVCC(Multi-Version Concurrency Control)という仕組みにより、トランザクション管理のために古い行バージョンを一定期間保持する必要があるためです。
VACUUMは、この不要になった行を再利用可能な領域として解放し、テーブルやインデックスの肥大化を防ぐ役割があります。
ただし、VACUUMは基本的に不要領域をOSへ返却する処理ではなく、PostgreSQL内部で再利用できるようにする処理です。そのため、VACUUM後もテーブルファイルのサイズが変わらないことがあります。
まず確認すべき不要領域とテーブル肥大化の状態
大量のUPDATEやDELETE後に問題が発生した場合、最初に確認したいのはテーブル内にどれだけ不要な行が残っているかです。
PostgreSQLではpg_stat_user_tablesビューを利用して、更新や削除された行数を確認できます。
例えば以下のような項目を確認します。
| 項目 | 確認内容 |
|---|---|
| n_dead_tup | 不要になった行数 |
| n_live_tup | 現在有効な行数 |
| last_vacuum | 最後にVACUUMされた日時 |
| last_autovacuum | 自動VACUUMの実行状況 |
例えば、数百万件のUPDATEを実行した後にn_dead_tupが大量に増えている場合、VACUUMが追いついていない可能性があります。
長時間トランザクションがVACUUMを妨げていないか確認する
VACUUMが十分に不要行を削除できない原因として非常に多いのが、長時間実行中のトランザクションです。
PostgreSQLでは、古い行バージョンを参照する可能性があるトランザクションが存在すると、その行を安全に削除できません。
例えば、数時間前に開始したままCOMMITされていないSELECT処理が残っている場合、そのトランザクションより古い不要行はVACUUM対象として回収できないことがあります。
確認する場合はpg_stat_activityで実行中のトランザクション時間を調査します。
特に以下のような状態がある場合は注意が必要です。
- 開始から数時間以上経過したトランザクションが存在する
- アイドル状態のままトランザクションを保持している接続がある
- アプリケーション側でCOMMIT漏れが発生している
autovacuumの設定が処理量に合っているか確認する
大量UPDATEやDELETEが発生するシステムでは、標準設定のautovacuumでは処理が追いつかない場合があります。
PostgreSQLのautovacuumは、一定割合以上の変更が発生すると起動します。しかし、大規模テーブルでは「20%変更されるまで待つ」というデフォルト設定では遅すぎるケースがあります。
例えば10億行あるテーブルの場合、20%の変更とは2億行です。その時点で初めてVACUUMが開始されるため、不要領域が大量に蓄積する可能性があります。
このような場合はテーブル単位でautovacuum_vacuum_scale_factorを小さく設定するなど、負荷に合わせた調整が必要です。
インデックスの肥大化も確認する
VACUUMを実行しても検索性能が改善しない場合、テーブルではなくインデックスが肥大化している可能性があります。
UPDATEが多いテーブルでは、古いインデックスエントリが残り続けることがあります。VACUUMは不要領域を再利用可能にしますが、インデックスファイル自体のサイズを縮小する処理ではありません。
インデックス肥大化が疑われる場合は、REINDEXやpg_repackなどのツールを検討します。
例えば、日次で大量データ更新を行うログテーブルでは、テーブルのVACUUMだけではなく、インデックスのメンテナンス計画も必要になります。
VACUUM FULLが必要なケースと注意点
通常のVACUUMでは、不要領域を内部的に再利用するだけで、ディスク使用量を直接減らすことはできません。
テーブルサイズを物理的に縮小したい場合はVACUUM FULLを利用できます。しかし、VACUUM FULLはテーブルを書き直す処理であり、対象テーブルに強いロックが発生します。
そのため、オンライン処理が必要な本番環境では、実行時間や影響範囲を十分に検討する必要があります。
大量UPDATE・DELETE後に行う調査手順
VACUUMが期待通りに動作しない場合は、以下の順番で確認すると原因を切り分けやすくなります。
- pg_stat_user_tablesで不要行数を確認する
- 長時間トランザクションの有無を確認する
- autovacuumの実行履歴と設定値を確認する
- テーブルサイズとインデックスサイズを確認する
- 必要に応じてREINDEXやVACUUM FULLを検討する
例えば、大量DELETE後に容量が減らない場合でも、すぐにVACUUM FULLを実行するのではなく、まず不要行が残っている原因を確認することが重要です。
まとめ:VACUUMが効かない場合は原因調査が重要
PostgreSQLで大量UPDATEやDELETE後にVACUUMが十分に機能しない場合、単純にVACUUM不足と判断するのではなく、長時間トランザクション、autovacuum設定、テーブルやインデックスの肥大化などを確認する必要があります。
特にMVCCによる行管理を理解すると、なぜ不要データがすぐ削除されないのか、なぜVACUUM後もサイズが変わらないのかを正しく判断できます。
大規模なPostgreSQL環境では、定期的な統計情報の確認と適切なメンテナンス設定を行うことで、性能低下やストレージ圧迫を防ぐことができます。


コメント