PostgreSQLのVACUUMとautovacuum運用設計|テーブル肥大化とXID周回を防ぐ設定の考え方

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOMELinux技術 リナックスマスター.JP(Linuxマスター.JP)Linuxtipsデーターベース管理 > PostgreSQLのVACUUMとautovacuum運用設計|テーブル肥大化とXID周回を防ぐ設定の考え方
「autovacuumを有効にしているのに、テーブルがどんどん肥大化していく」「VACUUM FULLをかけたら本番テーブルがロックされてサービスが止まった」「XID周回という言葉を聞いたが、自分のサーバーは大丈夫なのか不安だ」

PostgreSQLを本番環境で運用し始めると、こうした問題に直面します。原因のほとんどは、PostgreSQLのMVCC(マルチバージョン並行性制御)の仕組みとVACUUMの役割を体系的に理解していないことです。

この記事では、VACUUMとautovacuumの動作原理を基礎から解説し、テーブル肥大化とXID周回(Wraparound)障害を防ぐ実践的な設定方法を紹介します。Rocky Linux 9 / Ubuntu 24.04上のPostgreSQL 16で動作確認しています。

この記事のポイント

・VACUUMはデッドタプルを再利用可能にし、VACUUM FULLは物理圧縮するが排他ロックが必要
・autovacuumの起動条件はthreshold + scale_factor × 行数で決まる
・XID周回障害を防ぐにはage(relfrozenxid)を定期監視してFreeze VACUUMを計画的に実行
・大量更新テーブルはALTER TABLEでautovacuumパラメータをテーブル単位で上書きする


「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

PostgreSQLでVACUUMが必要になる理由(MVCCとデッドタプル)

PostgreSQLは、同時アクセスの整合性を確保するためにMVCC(Multi-Version Concurrency Control)を採用しています。MVCCでは、行をUPDATEまたはDELETEしても古いバージョンの行は即座には消えません。古いバージョンの行データは「デッドタプル(dead tuple)」として物理的にテーブルファイル内に残り続けます。

デッドタプルが蓄積すると、次の3つの問題が起きます。

テーブル肥大化(Table Bloat):行を削除しても実際のファイルサイズは縮まらず、ディスク容量を圧迫します。
クエリ速度の低下:シーケンシャルスキャン時にデッドタプルも読み取るため、I/Oが増加します。
インデックス肥大化:インデックスにも古いバージョンへの参照が残り、同様に肥大化します。

VACUUMはこのデッドタプルを回収し、領域を再利用可能にするための仕組みです。

VACUUMコマンドの種類と実行方法

1. VACUUM — デッドタプルを回収する基本コマンド

通常のVACUUMはデッドタプルを回収し、空き領域として再利用可能にします。テーブルに対してShareUpdateExclusiveLockを取得するため、SELECT・INSERT・UPDATE・DELETEと同時に実行可能です。本番環境でもオンラインで実行できます。

-- データベース全体をVACUUM $ sudo -u postgres psql -c "VACUUM VERBOSE;" -- 特定テーブルのみVACUUM $ sudo -u postgres psql -d mydb -c "VACUUM VERBOSE orders;"

注意点として、通常のVACUUMではテーブルファイルの物理サイズは縮小しません。あくまでもデッドタプルのあった領域を「次のINSERT/UPDATEで再利用できる空き領域」としてマーキングするだけです。

2. VACUUM FULL — テーブルファイルを物理圧縮する

VACUUM FULLはテーブルを書き直し、ファイルを実際に縮小します。ただし、AccessExclusiveLockを取得するためテーブルへのすべてのアクセスがブロックされます。本番環境では原則としてメンテナンス時間帯にのみ実行してください。

-- テーブル肥大化が深刻な場合(メンテナンス時間帯に実行) $ sudo -u postgres psql -d mydb -c "VACUUM FULL VERBOSE orders;" INFO: vacuuming "public.orders" INFO: "orders": found 0 removable, 2841023 nonremovable row versions in 38412 pages DETAIL: 0 dead row versions cannot be removed yet. CPU: user: 3.28 s, system: 1.42 s, elapsed: 12.74 s. VACUUM

3. VACUUM ANALYZE — 統計情報もあわせて更新する

VACUUM ANALYZEはVACUUMに加え、クエリプランナーが使用する統計情報も更新します。クエリが突然遅くなった際は、まずこのコマンドを試すのが鉄則です。

-- VACUUM + 統計情報更新を同時に実行 $ sudo -u postgres psql -d mydb -c "VACUUM ANALYZE orders;"

autovacuumの動作原理と設定パラメータ

autovacuumはPostgreSQL組み込みのバックグラウンドデーモンで、テーブルのデッドタプルが一定量を超えると自動的にVACUUMを実行します。postgresql.confで挙動を制御できます。

1. autovacuumが起動する条件(threshold と scale_factor)

autovacuumがVACUUMを開始するのは、テーブルのデッドタプル数が次の条件を超えたときです。

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

デフォルト値(PostgreSQL 16):

autovacuum_vacuum_threshold:50(最低デッドタプル数)
autovacuum_vacuum_scale_factor:0.2(テーブル行数の20%)

つまり100万行のテーブルでは、20万行以上のデッドタプルが溜まって初めてautovacuumが起動します。大規模テーブルでは起動が遅れ肥大化が進むため、後述のテーブル単位設定で調整が必要です。

ANALYZE(統計情報更新)のトリガーは別のパラメータで制御されます。

autovacuum_analyze_threshold:50
autovacuum_analyze_scale_factor:0.1(テーブル行数の10%)

2. 処理速度のコントロール(cost_delay と cost_limit)

autovacuumはI/Oに負荷をかけないよう、コストベースの throttling を使っています。

autovacuum_vacuum_cost_delay:2ms(コスト上限に達したときの休止時間)
autovacuum_vacuum_cost_limit:200(1サイクルで消費できるコストの上限)

VACUUMがページを読む際のコスト単価:

・バッファキャッシュにあるページ(hit):1
・ディスク読み込みが必要なページ(miss):2
・ダーティページの書き込み(dirty):20

コスト合計が200に達すると2ms休止するため、I/O負荷の高い環境ではautovacuumが遅くなります。追いつかない場合はautovacuum_vacuum_cost_delay = 0(throttling無効)やautovacuum_vacuum_cost_limit = 800のように緩和します。

3. 並列数と起動間隔の調整

autovacuum_max_workers:3(同時に起動できるautovacuumプロセス数)
autovacuum_naptime:1min(各テーブルをチェックする間隔)

テーブル数が多いデータベースではnaptime内に全テーブルをチェックしきれないことがあります。autovacuum_naptime = 30sに短縮するか、autovacuum_max_workersを増やして対応します。

XID周回(Wraparound)を防ぐFreeze設定

1. XID周回とは何か

PostgreSQLはすべてのトランザクションに32ビットのID(XID)を付与します。XIDは約21億(2^31)で上限に達し、そのまま放置すると古いタプルが「未来のトランザクション」に見える状態になります。これをXID周回(Wraparound)といいます。

PostgreSQLはWrapround寸前になるとデータベースを読み取り専用にして障害を防ぎますが、これは事実上のサービス停止を意味します。VACUUMはタプルに「FROZEN」マークを付け、XIDを無効化することでこの問題を回避します。

2. relfrozenxid の年齢を監視するSQL

次のSQLで、各テーブルがXID周回まであと何トランザクションあるかを確認できます。実運用では定期的に実行して監視する習慣を付けてください。

-- XID年齢が古いテーブルを上位10件表示 $ sudo -u postgres psql -d mydb -c " SELECT schemaname, relname, age(relfrozenxid) AS xid_age, pg_size_pretty(pg_total_relation_size(oid)) AS size FROM pg_class WHERE relkind = 'r' ORDER BY age(relfrozenxid) DESC LIMIT 10;" schemaname | relname | xid_age | size ------------+---------------+---------+--------- public | orders | 1842301 | 4821 MB public | order_items | 1839450 | 2341 MB public | users | 983201 | 234 MB public | product_cache | 412050 | 18 MB public | sessions | 387901 | 127 MB (5 rows)

xid_ageautovacuum_freeze_max_age(デフォルト2億)に近づいているテーブルは優先してVACUUM FREEZEを実行します。

3. autovacuum_freeze_max_age の設計目安

関連するパラメータ(postgresql.conf):

vacuum_freeze_min_age:50000000(5000万)— この年齢未満のタプルはFreeze対象外
vacuum_freeze_max_age:200000000(2億)— この年齢を超えたタプルをForce Freeze
autovacuum_freeze_max_age:200000000(2億)— この年齢を超えたテーブルは通常の閾値と無関係にautovacuumを強制起動

トランザクション量の多い環境ではautovacuum_freeze_max_ageを1億5000万程度に下げ、余裕を持ってFreeze VACUUMが走るように設計します。

テーブル単位でautovacuumをチューニングする

更新頻度が非常に高い大規模テーブルでは、全体設定だけでは追いつかない場合があります。ALTER TABLEのストレージパラメータでテーブルごとに設定を上書きできます。

-- ordersテーブル:scale_factorを0.02(2%)に下げてautovacuumをより頻繁に起動 ALTER TABLE orders SET ( autovacuum_vacuum_scale_factor = 0.02, autovacuum_analyze_scale_factor = 0.01, autovacuum_vacuum_cost_delay = 0 ); -- 設定を確認 SELECT reloptions FROM pg_class WHERE relname = 'orders'; reloptions --------------------------------------------------- {autovacuum_vacuum_scale_factor=0.02,autovacuum_analyze_scale_factor=0.01,autovacuum_vacuum_cost_delay=0}

1000万行以上のテーブルにはscale_factorを0.01~0.05に下げることを推奨します。デフォルトの0.2では20万行以上のデッドタプルが溜まってから初めてVACUUMが起動するため、肥大化が目立ちやすくなります。

トラブルシュート — autovacuumが追いつかないケース

【確認1】pg_stat_user_tablesでデッドタプルを確認する

-- デッドタプルが多いテーブルを確認 $ sudo -u postgres psql -d mydb -c " SELECT relname, n_live_tup, n_dead_tup, round(n_dead_tup::numeric / NULLIF(n_live_tup + n_dead_tup, 0) * 100, 1) AS dead_ratio_pct, last_autovacuum, last_autoanalyze FROM pg_stat_user_tables ORDER BY n_dead_tup DESC LIMIT 10;" relname | n_live_tup | n_dead_tup | dead_ratio_pct | last_autovacuum | last_autoanalyze -------------+------------+------------+----------------+-------------------------------+------------------------------- orders | 9821034 | 1243198 | 11.2 | 2026-08-25 03:14:22.341256+09 | 2026-08-25 03:14:24.881943+09 order_items | 28341029 | 843022 | 2.9 | 2026-08-25 06:42:10.123400+09 | 2026-08-25 06:42:14.334521+09

dead_ratio_pctが10%を超えているテーブルは、autovacuumの起動頻度を上げるかmanual VACUUMを検討します。

【確認2】autovacuumが現在実行中かを確認する

-- 実行中のautovacuumプロセスを確認 $ sudo -u postgres psql -c " SELECT pid, datname, query_start, state, query FROM pg_stat_activity WHERE query LIKE 'autovacuum%';"

【確認3】手動VACUUMで緊急対応する

autovacuumが追いつかない場合、低トラフィック時間帯に手動VACUUMを実行します。

-- メンテナンス時間帯に手動で実行(VERBOSEで進捗確認) $ sudo -u postgres psql -d mydb -c "VACUUM (VERBOSE, ANALYZE) orders;" -- XID年齢が深刻な場合はFREEZEも付ける $ sudo -u postgres psql -d mydb -c "VACUUM (VERBOSE, FREEZE) orders;"

まとめ

PostgreSQLのVACUUMとautovacuumを適切に設計することは、安定した本番運用の基本です。
やりたいこと コマンドまたはパラメータ
デッドタプルをオンラインで回収する VACUUM テーブル名
テーブルファイルを物理圧縮する(ロックあり) VACUUM FULL テーブル名
VACUUM+プランナ統計を同時更新する VACUUM ANALYZE テーブル名
XID年齢を確認する SELECT age(relfrozenxid) FROM pg_class WHERE relname='テーブル名'
autovacuumの起動頻度を上げる(大規模テーブル) ALTER TABLE t SET (autovacuum_vacuum_scale_factor=0.02)
autovacuumのI/O throttlingを緩和する autovacuum_vacuum_cost_delay = 0(postgresql.conf)
デッドタプル比率を確認する SELECT n_dead_tup FROM pg_stat_user_tables WHERE relname='テーブル名'
XID周回を防ぐFreeze VACUUMを強制実行する VACUUM FREEZE テーブル名

autovacuumを無効化したり、VACUUMを完全に手動管理するアプローチは、XID周回障害やテーブル肥大化のリスクを高めます。まずautovacuumを有効にした上で、監視SQLとpg_stat_user_tablesを使って状況を把握し、問題のあるテーブルだけをテーブル単位でチューニングするのが現場での正攻法です。

Linuxサーバー上でPostgreSQLをはじめとするミドルウェアを実運用する際の設計・チューニングのノウハウは、体系的に学ぶことで現場での判断力が大きく変わります。

PostgreSQLのチューニングを本番で活かすには、Linuxサーバー運用の「型」が基礎になります

autovacuumの設計やXID周回対策は、OSのプロセス管理・ファイルシステム・ディスクI/Oを体系的に理解していると判断がしやすくなります。独学で断片的に覚えるより、現場で実際に使われる設計パターンを一度体系的に身につけることで、PostgreSQLも含めたミドルウェア全体の安定運用ができるようになります。
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、『Linuxサーバー構築入門マニュアル(図解60P)』を完全無料でプレゼントしています。

「独学の時間がもったいない」「プロから直接、現場の技術を最短で学びたい」という本気の方には、2日で実務レベルのスキルが身につく【初心者向けハンズオンセミナー】も開催しています。

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録
宮崎 智広

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として20年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。