PostgreSQL 12 min read

PostgreSQLのVACUUM ― DELETEしても容量が減らない理由とVACUUM FULL・autovacuum

※この記事にはアフィリエイト広告(プロモーション)が含まれます。

PostgreSQL では、DELETE した行はすぐには消えません。行には「削除済み」の印が付くだけで、表のファイルの大きさはそのままです。この削除済みの行(dead tuple)を片付けるのが VACUUM です。

この記事のログは PostgreSQL 18.6(Ubuntu 26.04 の標準パッケージ)で採りました。表のサイズは pg_relation_size()、行数は pg_stat_user_tables で見ています。

まず見る(結論)

  • VACUUM は削除済みの行の場所を再利用できるようにするだけで、基本的にファイルは縮まない。縮むのは表の末尾がまとめて空いたときだけ
  • ファイルを縮めるのは VACUUM FULL。表を作り直すので、実行中は SELECT も止まる
  • VACUUM しても行が消えないときは、古いトランザクションが残っている。pg_stat_activity の backend_xmin を見る
  • 普段は autovacuum が自動で VACUUM を流している。手で流すのは大量に削除した直後くらい

DELETE しても表の大きさは変わらない

20万行(約46MB)の表から18万行を DELETE しました。t_scatter は10行に9行を飛び飛びに、t_tail は id が 20001 以上の行(表の後ろ側)を消しています。

DELETE FROM t_scatter WHERE id % 10 <> 0;   -- 180000行
DELETE FROM t_tail    WHERE id > 20000;      -- 180000行

SELECT relname, pg_size_pretty(pg_relation_size(relid)) AS heap, n_live_tup, n_dead_tup
  FROM pg_stat_user_tables;
  relname  | heap  | n_live_tup | n_dead_tup
-----------+-------+------------+------------
 t_scatter | 46 MB |      20000 |     180000
 t_tail    | 46 MB |      20000 |     180000

どちらも 46MB のままです。n_dead_tup が削除済みの行の数です。

VACUUM で縮むのは末尾が空いた表だけ

両方の表に VACUUM (VERBOSE) を流しました。

VACUUM (VERBOSE) t_scatter;
INFO:  finished vacuuming "vdb.public.t_scatter": index scans: 1
pages: 0 removed, 5883 remain, 5883 scanned (100.00% of total), 0 eagerly scanned
tuples: 180000 removed, 20000 remain, 0 are dead but not yet removable

VACUUM (VERBOSE) t_tail;
INFO:  table "t_tail": truncated 5883 to 589 pages
INFO:  finished vacuuming "vdb.public.t_tail": index scans: 1
pages: 5294 removed, 589 remain, 5883 scanned (100.00% of total), 0 eagerly scanned
tuples: 180000 removed, 20000 remain, 0 are dead but not yet removable
表 DELETE 後 VACUUM 後
t_scatter(飛び飛びに削除) 46 MB 46 MB
t_tail(末尾を削除) 46 MB 4712 kB

VACUUM がファイルを切り詰めるのは、表の末尾に空のページが続いているときだけです(truncated 5883 to 589 pages)。途中に生きている行が散らばっている t_scatter は、大きさが変わりませんでした。

VACUUM の後に15万行を入れても t_scatter は 46MB のままで、空いた場所が再利用されていました。

VACUUM しても行が消えないとき

別のセッションで REPEATABLE READ のトランザクションを開いたまま、行を削除して VACUUM しました。

VACUUM (VERBOSE) t_snap;
INFO:  finished vacuuming "vdb.public.t_snap": index scans: 0
pages: 0 removed, 1471 remain, 1471 scanned (100.00% of total), 0 eagerly scanned
tuples: 0 removed, 50000 remain, 25000 are dead but not yet removable

25000 are dead but not yet removable は、削除済みだがまだ消せない行の数です。開いているトランザクションが削除前の行を見られる状態なので、VACUUM は消しません。原因のセッションは backend_xmin が入っている行で分かります。

SELECT pid, state, backend_xmin, now() - xact_start AS xact_age, left(query, 60) AS q
  FROM pg_stat_activity
 WHERE backend_xmin IS NOT NULL AND pid <> pg_backend_pid();
 pid  | state  | backend_xmin |    xact_age     |                         q
------+--------+--------------+-----------------+-----------------------------------------------------
 1952 | active |          771 | 00:00:02.084101 | begin isolation level repeatable read; select count(*) from

そのトランザクションが終わった後に同じ VACUUM を流すと、25000 removed になりました。xact_age が長いセッションや、state が idle in transaction のまま放置されたセッションがあると、データベース全体で削除済みの行が溜まり続けます。

VACUUM FULL で縮める

ファイルを縮めるには VACUUM FULL を使います。生きている行だけを新しいファイルに書き直すので、t_scatter は 46MB から 4712 kB になりました。

VACUUM (FULL, VERBOSE) t_scatter;
INFO:  vacuuming "public.t_scatter"
INFO:  "public.t_scatter": found 0 removable, 20000 nonremovable row versions in 5883 pages
DETAIL:  0 dead row versions cannot be removed yet.

VACUUM FULL は実行中ずっと表を排他ロックします。別のセッションが同じ表を SELECT している最中に、lock_timeout を2秒にして流した結果です。通常の VACUUM は通り、VACUUM FULL はロックを取れずに2秒で止まりました。

SET lock_timeout = '2s';
VACUUM t_scatter;
VACUUM

SET lock_timeout = '2s';
VACUUM FULL t_scatter;
ERROR:  canceling statement due to lock timeout

大きな表で使うときは、止めてよい時間帯に流します。書き直す間は元のファイルと新しいファイルが両方あるので、生きている行のぶんの空きディスクも要ります。

autovacuum

autovacuum は既定で有効です。18.6 の既定値は次のとおりでした。

psql -Atc "SELECT name, setting, unit FROM pg_settings WHERE name IN ('autovacuum', 'autovacuum_naptime', ...) ORDER BY name"
autovacuum|on|
autovacuum_max_workers|3|
autovacuum_naptime|60|s
autovacuum_vacuum_insert_threshold|1000|
autovacuum_vacuum_scale_factor|0.2|
autovacuum_vacuum_threshold|50|

削除済みの行が「autovacuum_vacuum_threshold + 行数 × autovacuum_vacuum_scale_factor」を超えた表に、autovacuum_naptime(60秒)ごとに VACUUM がかかります。10万行の表で5万行を削除すると、1分以内に last_autovacuum に時刻が入り、n_dead_tup は 0 になりました。表のサイズは 23MB のままでした。autovacuum もファイルは縮めません。

n_dead_tup が大きいまま last_autovacuum が古い表があれば、前の節の長いトランザクションを疑います。

行を消してもディスクが空かないのは MySQL(InnoDB)も同じです。仕組みと戻し方の違いは「MySQLでDELETEしてもディスクが減らない」で扱っています。

PostgreSQL をもっと深く知るための本

PostgreSQL実践入門 ──アーキテクチャ、運用監視、性能改善

技術評論社・2026年2月

アーキテクチャから運用監視・性能改善までを扱う。付録に PostgreSQL 18 の新機能と設定項目の差分がある。

Amazonで見る

[改訂3版]内部構造から学ぶPostgreSQL ―設計・運用計画の鉄則

技術評論社・2022年11月・PostgreSQL 14 ベース

内部構造の理解から、設計と運用計画の立て方を学ぶ。

Amazonで見る

Amazonのアソシエイトとして、FIRE SE は適格販売により収入を得ています。