はじめに
postgresを9系の頃に利用し始めて、今はもう16とか17とかも使っている。
でも別に深い機能とか高負荷とかの利用はしていない。
そんなライトな運用でも何となく以前から「xid周回は気を付けろ」という話はあったが、
デフォルトでautovacuumしてるせいか、特に問題になったことはない。
一度、16のデフォルト設定で、「postgresのxid周回は普通気にしなくても良い」と言いたいまとめを行ってみる。
エグゼクティブサマリ
0. エグゼクティブサマリ
PostgreSQL XID周回に関するエグゼクティブサマリ
■ 結論
PostgreSQLでは、XID(トランザクションID、32bit、42億)の周回そのものは問題ではありません。
真に危険なのは「XIDの差分(age)が約21億を超えること」です。
これは PostgreSQL の内部仕様上、可視性判定(MVCC)において重大な誤動作を引き起こすため、
PostgreSQL はその手前で 自動的に書き込みを停止し、整合性を保護します。
しかし、サービス提供中に、DB停止、詰まりサービス停止を発生させてはならないため、
XIDの差分(age)が2億を超えて増加し続けていないか、 定期的に監視を継続してください。
■ なぜ21億が限界なのか?
- XIDは32bitの符号なし整数(最大値:約42億)
- しかし内部では
int32
( 符号付き )で比較されるため、±2^31(≒21億)が実質のカウンタ範囲 - この差分(age)が超えると、可視性判定(「行が見えるか」)が破綻する
■ PostgreSQLの整合性担保の安全装置(多段防御)
段階 | 動作 | 発動条件 |
---|---|---|
① | 通常 autovacuum | テーブルの更新率や age に基づき随時発動 |
② | 強制 autovacuum | age(datfrozenxid) ≥ 2億(デフォルト) |
③ | 書き込み拒否(FATAL) | age(datfrozenxid) ≥ 2^31(約21億) |
→ この仕組みにより、XIDは何度でも安全に再利用される
■ 現実的に注意すべきポイント
「XID周回を恐れる必要はない」
実際に注意すべきは、freezeが追いつかず age が積み上がること
よくある原因:
- autovacuumが無効(デフォルトは有効)
- worker不足(デフォルトは3)
- ロングトランザクションの放置
- 巨大テーブルの更新負荷(1億レコードでSSDでも数十秒の可能性あり)
- 統計情報の陳腐化
- 不要なレプリケーション設定残存
■ 推奨される定期監視(週次または月次)
項目 | 目的 | 取得例 |
---|---|---|
age(datfrozenxid) |
全体のXID進行度 | SELECT age(datfrozenxid) FROM pg_database; |
age(relfrozenxid) |
テーブル単位のfreeze遅延確認 | SELECT relname, age(relfrozenxid) FROM pg_class ... |
ロングTXの有無 | freezeブロックの兆候 | SELECT * FROM pg_stat_activity WHERE state='idle in transaction'; |
autovacuumの実行状況 | 正常稼働の確認 |
pg_stat_user_tables のautovacuum_count など |
■ まとめ
- ✅ XIDが42億で周回すること自体は仕様であり問題なし(凍結されていれば再利用可能)
- ❗ ageが21億を超えると可視性が壊れ、整合性リスク(=致命的)
- ✅ 通常はautovacuumで安全に防止されている
- ❗ autovacuumが阻害され続けると最初の予兆、実害として発生し得るのは、強制autovacuumによる性能劣化
- 速度感としては、100TPS継続なら約23日で2億のXIDを消費。担当システムの速度感把握が必要
つまり:心配すべきは「周回」ではなく、「凍結が間に合っているかどうか」。
正常な設定と監視さえしていれば、XID周回自体は“心配しなくていい技術的な話”です。
1. PostgreSQLにおける xid・autovacuum・wraparound に関する整理まとめ(概念寄り)
【1】PostgreSQLのxidの基本
項目 | 内容 |
---|---|
xid(Transaction ID) | PostgreSQLがトランザクションを一意に識別するための32bit符号なし整数(最大4,294,967,296 ≒ 42億)。MVCCでの可視性の管理に必須 |
xidのwraparound | xidは42億の半分で1周し、古いxidを新しいと誤認してしまう危険(整合性崩壊)がある |
xidの消費単位 | 1トランザクションにつき1つのxidが消費される。更新レコード数には依存しない |
xidのスコープ | データベースクラスタ単位 |
参考リンク:
- https://www.postgresql.jp/docs/current/transaction-id.html
- https://www.postgresql.org/docs/current/mvcc.html
postgresにおける「トランザクション」とは?
PostgreSQLにおける「トランザクション」とは、一貫性あるデータ状態を保証するための操作の最小単位です。
明示的な BEGIN ~ COMMIT だけでなく、1SQLごとに自動でトランザクションが生成されることもあります。
- ユーザーが開始した明示的トランザクション
- システム内部で実行される自動トランザクション
の両方を含みます。
例えば:
- 通常のSQLでの BEGIN; ... COMMIT;
- 自動VACUUMで発行される内部トランザクション
- インデックス再構築、ANALYZEなど
これらもすべて「トランザクション」として扱われ、それぞれに xid が割り振られます。
更新系と参照系の違い
種類 | 必ず xid が割り当てられるか? | 可視性制御 | WAL出力 |
---|---|---|---|
更新系(INSERT/UPDATE/DELETEなど) | はい | MVCCで管理 | あり(WAL出力) |
参照系(SELECTなど) | 実際には xid 割り当て無し(xid = 0) | スナップショット取得のみ | なし |
- 参照系は、読み取り専用トランザクションとして扱われ、xidを消費しません(xid = 0扱い)。
- そのため、大量のSELECTが発生してもxidの枯渇(wraparound)問題には関係ありません。
vxid / xid の関係と動作タイミング
項目 | 内容 |
---|---|
Virtual Transaction ID(vxid) | トランザクション開始時に常に割り当てられる(読み取り専用でも)。可視性の確認のため必須 |
Transaction ID(xid) | 実際に行の変更操作が発生した時点で初めて割り当てられる。読み取り専用トランザクションでは xid = 0(無効) |
スナップショットの取得 | トランザクションの最初のSQL実行時に取得され、そのスナップショットを元に行の可視性を判定 |
明示的トランザクション中にSELECTだけの場合 | vxidはあるが、xidは割り当てられない。途中でUPDATEがあればその時点でxidが付与される |
- xid および vxid は、データベース単位ではなく、クラスタ全体(インスタンス単位)で共有されます。
- よって、スキーマやデータベースが違っていても、同一クラスタ内であれば共通の xid / vxid カウンタが使われます。
- vxidの形式 backend_pid/local_xid(例:12345/1)
なぜ vxid に wraparound 問題が起きないのか?
- 理由1:プロセスごとのローカルID(スコープが限定的)
- local_transaction_id はバックエンドプロセスごとにカウントされるため、クラスタ全体での一意性は不要
- 周回しても そのプロセス内の範囲でしか意味を持たない ため、他と衝突することがありません
- 理由2:vxidの寿命が短く、一時的な識別子
- vxid はトランザクションの実行中のみ有効
- トランザクションが終了すれば、vxidの情報も破棄される
- よって、「古いvxidが残って比較に影響する」ような状況がありません
【2】wraparoundリスクよりこっちの方が近い、危ない
条件 | 説明 |
---|---|
age(datfrozenxid) > 2^31 = 2,147,483,648 |
PostgreSQLはトランザクションIDがデフォルトで21億以上古くなると、新しいトランザクションを拒否(FATAL) して整合性を守ろうとする。これが「wraparound防止措置」。42億の半分じゃん! |
autovacuum_freeze_max_age (デフォルト2億)を超過 |
PostgreSQLは強制的にautovacuumを起動し、xidのfreezeを試みる(「to prevent wraparound」のログ)。21億の10分の1じゃん!!何か凄く近く感じるじゃん ! |
→freezeが間に合わないと datfrozenxid
が古いままになり、最終的にwraparoundリスクの制御ライン(21億)を突破し、DBが書き込み不能に陥る (300万件手前)
参考リンク:
- https://www.postgresql.org/docs/current/routine-vacuuming.html
- https://www.postgresql.org/docs/current/runtime-config-autovacuum.html
- https://www.postgresql.jp/document/16/html/routine-vacuuming.html
こうした警告も無視し続け、周回するまでのトランザクションが300万より少なくなると、システムは新しいXIDの割り当てを拒絶します。
【3】xidの消費速度と時間の目安
TPS | 2億xidに到達する時間 |
---|---|
1000 TPS | 約2日7時間 |
100 TPS | 約23日 |
10 TPS | 約231日 |
1 TPS | 約6.3年 |
※ 高負荷の場合xid消費はアプリのDMLが主因であり、システムカタログやpg_statsinfoなどの拡張ツールによる消費は微小
参考リンク:
- https://www.postgresql.org/docs/current/monitoring-stats.html#PG-STAT-DATABASE-VIEW
- https://www.slideshare.net/hadoopxnttdata/postgresql-xid-wraparound-another-issue
【4】autovacuumの2つの目的とトリガ
種類 | トリガ条件 | 目的 | xid消費 |
---|---|---|---|
通常のautovacuum | dead tuple数が (50 + 0.2 × reltuples) を超える |
不要な行バージョン(dead tuple)の回収 | DELETEやUPDATEありならxid消費あり |
freeze目的のautovacuum |
age(relfrozenxid) > autovacuum_freeze_max_age (デフォルト:2億) |
xidの凍結(freeze)を行い、wraparoundを防止 | xidは消費されない(既存行の内部情報のみ更新) |
参考リンク:
- https://www.postgresql.org/docs/current/runtime-config-autovacuum.html#GUC-AUTOVACUUM-FREEZE-MAX-AGE
- https://www.postgresql.jp/document/16/html/routine-vacuuming.html
【5】autovacuumが回避される/freezeできないシナリオ
シナリオ | 原因 | 結果 |
---|---|---|
小規模テーブルを量産 | INSERTがしきい値未満(50件未満) | vacuum対象にならない。xidだけが進行する |
TRUNCATEの繰り返し | 行データ自体が消えるため、dead tupleが発生せず、vacuum対象外になる | xidは進むが、freezeは発生しない |
ロングトランザクション | BEGINしたまま放置すると OldestXmin が更新されず、autovacuumが凍結できない | xidは進行し、datfrozenxidが古いまま残る |
巨大テーブルに大量更新 | vacuumに時間がかかる上、worker数(デフォルト3)が不足 | 他テーブルが後回しになり、freezeが追いつかない |
autovacuumの設定制限 | worker数・cost_limitが小さい | スループット不足でvacuumが遅延・未実行になる |
統計情報陳腐化 | analyzeしきい値未満 | vacuum対象にならない。xidだけが進行する |
不要なレプリケーション設定残存 | walが削除できない。レプリカ先ではまだ必要なXIDだと判断されてしまう | vacuum対象にならない。xidだけが進行する |
- 「create、49レコードinsert、truncate」を毎秒繰り返す
- 1億レコードテーブル10個を毎秒2000万レコード更新する
参考リンク:
【6】TPSとdead tupleの関係
- xid消費はトランザクション単位(1 TPS = 1 xid)
- dead tupleは「更新された行数」に比例して増える
- つまり、TPSが低くても1回のトランザクションで数千万行UPDATEすれば、毎秒数千万のdead tupleが発生可能
参考リンク:
【7】運用リスクの整理
リスク | 発生ポイント | 対策 |
---|---|---|
性能劣化 | アプリ動作中に強制autovacuumが発生するとI/Oを使いすぎる(freeze) | コスト調整、worker数増加、VACUUM FREEZEの計画実行 |
wraparoundによるDB停止 | xidが21億進行し、凍結が間に合わない |
pg_stat_database で age を監視、定期的な凍結確認 |
設計上の落とし穴 | TRUNCATE多用、小規模テーブル量産、ロングTX | freeze対象から外れやすい構成を避けること |
- autovacuumは排他ロックではないため、性能としては、リソース逼迫が懸念となる
参考リンク:
- https://www.postgresql.org/docs/current/monitoring-stats.html#PG-STAT-DATABASE-VIEW
- https://www.postgresql.org/docs/current/routine-vacuuming.html#AUTOVACUUM
The autovacuum daemon attempts to work this way, and in fact will never issue VACUUM FULL.
【8】事故再現可能な極端シナリオ(机上実験)
条件 | 内容 |
---|---|
毎秒1000トランザクション × 数日運用 | xidが2億以上進行 |
autovacuum無効 or 強制停止 | vacuumが一切動かない or freeze不能 |
TRUNCATE + INSERTの繰り返し | xid進行するがvacuumされない構成 |
ロングTXでOldestXminが進まない | freezeできない構成完成 |
→ これらを意図的に組み合わせ、vacuumを阻害し続ければ、事故は再現可能
【9】ロングトランザクションの制御について
項目 | 内容 |
---|---|
PostgreSQLのデフォルト | BEGIN〜COMMIT/ROLLBACK しない限り無期限に保持可能 |
設定値 |
idle_in_transaction_session_timeout = 0 (無効) |
対策 | 明示的にtimeoutを設定(例:60000ミリ秒 = 60秒) |
参考リンク:
- https://www.postgresql.org/docs/current/runtime-config-client.html#GUC-IDLE-IN-TRANSACTION-SESSION-TIMEOUT
- https://www.postgresql.org/docs/current/monitoring-stats.html#PG-STAT-ACTIVITY-VIEW
【10】まとめ:安心と警戒のバランス
- PostgreSQLはデフォルトでautovacuumによるfreezeが有効なため、通常設計でwraparound事故、防止機能でのDB機能停止事故はまず起きません
- しかし、複数の「意図的な高負荷設計・運用」が組み合わさると、事故は再現可能です
- 運用的には、「ログ出力、性能劣化(age ≈ 2億)」という初期兆候の時点で気付けば、機能停止事故(age ≈ 21億) はほぼ確実に回避できます
- 初期兆候としている2億歳になることも、かなり極端な設計、実装、運用と思います
- 定期的にログの確認、システムカタログやビューの確認を継続しましょう
【11】監視sh案
以下は週次・月次にCSV形式でローテーション出力するbashスクリプト例です。
#!/bin/bash
# 出力先ディレクトリ
OUTDIR="/var/log/pgwrapmon"
mkdir -p "$OUTDIR"
# タイムスタンプ
DATE=$(date +"%Y%m%d_%H%M%S")
# 出力ファイル
OUTFILE="$OUTDIR/pgwrapmon_${DATE}.csv"
# PostgreSQL接続設定(適宜変更)
PGUSER="postgres"
PGDATABASE="postgres"
PGOPTIONS="--tuples-only --csv"
# SQL取得
psql -U "$PGUSER" -d "$PGDATABASE" $PGOPTIONS <<EOF > "$OUTFILE"
-- xid進行とfreeze age(DB単位)
SELECT now() as timestamp,
datname,
age(datfrozenxid) as db_age,
txid_current() as current_xid
FROM pg_database;
-- xid age(テーブル単位:上位20)
SELECT now() as timestamp,
c.relname,
n.nspname,
age(c.relfrozenxid) as rel_age,
s.n_live_tup,
s.n_dead_tup,
s.autovacuum_count,
s.analyze_count
FROM pg_class c
JOIN pg_stat_user_tables s ON c.oid = s.relid
JOIN pg_namespace n ON c.relnamespace = n.oid
WHERE c.relkind = 'r'
ORDER BY rel_age DESC
LIMIT 20;
EOF
cronへの登録は以下の感じです。
crontab -e
週次(日曜深夜3時)
0 3 * * 0 /usr/local/bin/pg_xid_monitor.sh >> /var/log/pgwrapmon/cron.log 2>&1
月次(1日深夜3時)
0 3 1 * * /usr/local/bin/pg_xid_monitor.sh >> /var/log/pgwrapmon/cron.log 2>&1
- xid進行が急増していないか → current_xid の増加差分をチェック
- 特定テーブルのrel_ageだけ上がり続けている → freeze漏れがないか確認
- autovacuum_countがゼロでn_dead_tupだけ増加しているテーブル → autovacuum設定の閾値未満か、異常か
出力CSVイメージ(例:pgwrapmon_20250330_030000.csv)
Part 1:データベース単位(pg_database + xid進行)
timestamp,datname,db_age,current_xid
2025-03-30 03:00:00,postgres,105230000,389201234
2025-03-30 03:00:00,mydb,98430000,389201234
2025-03-30 03:00:00,template1,98000000,389201234
- db_age:各データベースの age(datfrozenxid)(= xidがどれだけ古いままか)
- current_xid:この時点での全体のxid番号(= 進行度確認用)
Part 2:テーブル単位(pg_class + pg_stat_user_tables:上位20件)
timestamp,relname,nspname,rel_age,n_live_tup,n_dead_tup,autovacuum_count,analyze_count
2025-03-30 03:00:00,sensor_log,public,192350000,12483723,238472,120,98
2025-03-30 03:00:00,orders,public,188270000,34567890,10234,86,74
2025-03-30 03:00:00,users,public,170300000,98234,150,21,13
...
- rel_age:テーブル単位の age(relfrozenxid)(= そのテーブルの最古行がどれだけ古いか)
- n_dead_tup:dead tuple の蓄積傾向(autovacuumの必要性の指標)
- autovacuum_count, analyze_count:vacuum/analyzeの実行履歴(動いているかどうかの実績)
2. PostgreSQL における age / xid / frozenxid の関係整理(実装寄り)
【1】xid・凍結・age の関係まとめ
● xid の基本
- PostgreSQLの各トランザクションには一意の
xid
(Transaction ID)が割り当てられる(32bit、最大42億) - 各レコードには
xmin
(作成xid)、xmax
(削除xid)がシステムカラムとして存在する - トランザクションの可視性は
xmin
/xmax
に基づいて判断される(MVCC)
参考:
- https://www.postgresql.org/docs/current/mvcc.html
- https://www.postgresql.org/docs/current/transaction-id.html
● 凍結(freeze)の目的と処理
- 古すぎて全トランザクションから「確実に過去」と判断できる
xmin
は凍結される - 凍結された
xmin
は特別な値(例:2
= FrozenTransactionId)に置換され、wraparound対象から除外される - 凍結されると、その行の
xmin
は以後 age の対象にならない
参考:
- https://www.postgresql.org/docs/current/routine-vacuuming.html
- https://www.postgresql.org/docs/current/storage-page-layout.html
【2】age の種類とスコープ
関数 | スコープ | 説明 |
---|---|---|
age(xmin) |
行単位 | その行が作成されてからのトランザクション差分 |
age(relfrozenxid) |
テーブル単位 | テーブル内で最も古い未凍結行のxidとの差分 |
age(datfrozenxid) |
データベース単位 | すべてのテーブル中で最も古い未凍結xidとの差分 |
txid_current() |
クラスタ全体 | 現在進行中のxid(ageの基準点) |
※
age(x)
=txid_current()
-x
(内部ではラップアラウンド対応の計算)
参考:
- https://www.postgresql.org/docs/current/functions-info.html#FUNCTIONS-INFO-TXID
- https://www.postgresql.org/docs/current/routine-vacuuming.html
【3】relfrozenxid / datfrozenxid の関係
- 各テーブル:
relfrozenxid
により「テーブル内の最古の未凍結行のxid」を記録 - 各データベース:
datfrozenxid
により「データベース全体の最古xid」を記録 -
datfrozenxid
= データベース内のすべてのrelfrozenxid
の最小値
参考:
- https://www.postgresql.org/docs/current/catalog-pg-class.html
- https://www.postgresql.org/docs/current/catalog-pg-database.html
【4】システムカタログの扱い
-
pg_class
やpg_attribute
などのシステムカタログも通常のテーブルとして管理される - 当然、
relfrozenxid
を持ち、freeze対象となる - つまり、ユーザデータがなくてもxidやageは進行し、wraparoundリスクは存在する
参考:
- https://www.postgresql.org/docs/current/catalogs.html
- https://www.postgresql.org/docs/current/routine-vacuuming.html#VACUUM-FOR-WRAPAROUND
【5】wraparound予兆監視の意味
-
age(datfrozenxid)
:データベース全体の安全指標(21億超でFATAL) -
age(relfrozenxid)
:テーブル単位の遅延検出・監視に使える - これらを組み合わせることで、精度の高いwraparoundリスク管理が可能
参考:
- https://www.postgresql.org/docs/current/routine-vacuuming.html#VACUUM-FOR-WRAPAROUND
- https://www.postgresql.org/docs/current/monitoring-stats.html#PG-STAT-DATABASE-VIEW
可視性と凍結とageスコープの関係
【6】まとめ
-
xid
はクラスタ共通、すべてのageの基準 - ageはスコープに応じて以下のように使い分ける:
- 行単位: 可視性判定
- テーブル単位: freeze遅延の発見
- データベース単位: wraparound予兆の最前線
-
relfrozenxid
をfreezeしていけばdatfrozenxid
も更新され、wraparoundリスクを抑制できる
3. PostgreSQL における wraparound / xid / freeze の関連ソースコードまとめ
【1】. トランザクションID(XID)の割り当て
-
ファイル:
src/backend/access/transam/varsup.c
-
関数:
GetNewTransactionId()
-
概要:
PostgreSQL が新しいトランザクションを開始する際に、一意の XID を生成・割り当てる処理。
ShmemVariableCache->nextXid
をインクリメントしながら使用。 -
参考:
https://github.com/postgres/postgres/blob/master/src/backend/access/transam/varsup.c
【2】. 行の凍結(freeze)処理
-
ファイル:
src/backend/access/heap/heapam.c
-
関数:
heap_tuple_freeze
-
概要:
HEAP_XMIN_FROZEN
フラグが設定され、以降の可視性判断・vacuum 対象外になる。
【3】. 自動バキューム(autovacuum)処理
-
ファイル:
src/backend/postmaster/autovacuum.c
-
関数:
autovacuum_main()
,do_autovacuum()
-
概要:
autovacuum ランチャーとワーカーがテーブルの更新状況を監視し、dead tuple の蓄積やrelfrozenxid
の古さをもとに VACUUM/FREEZE を実行。 -
参考:
https://github.com/postgres/postgres/blob/master/src/backend/postmaster/autovacuum.c
【4】. テーブルごとの最古のXID(relfrozenxid)の管理
-
ファイル:
src/include/catalog/pg_class.h
-
概要:
各テーブルのrelfrozenxid
は PostgreSQL カタログpg_class
に記録される。freeze処理が実行されると更新される。 -
参考:
https://github.com/postgres/postgres/blob/master/src/include/catalog/pg_class.h
【5】. データベース全体の最古XID(datfrozenxid)の管理
-
ファイル:
src/include/catalog/pg_database.h
-
概要:
各データベース単位でdatfrozenxid
がpg_database
カタログに記録される。
DB内のすべてのrelfrozenxid
の最小値が基準となる。 -
参考:
https://github.com/postgres/postgres/blob/master/src/include/catalog/pg_database.h
【6】. 統計情報とANALYZE処理
-
ファイル:
src/backend/commands/analyze.c
-
関数:
do_analyze_rel()
-
概要:
ANALYZE による統計情報の収集と更新処理。オプティマイザが使用するヒストグラムや行数情報などを収集。 -
参考:
https://github.com/postgres/postgres/blob/master/src/backend/commands/analyze.c
【7】. 可視性判断(MVCC)
-
ファイル:
src/backend/access/heap/heapam_visibility.c
-
関数:
HeapTupleSatisfiesMVCC
-
概要:
行のxmin
,xmax
, コミット状態を元に、その行が「現在のトランザクションから見えるか」を判断する。MVCCの根幹処理。
【8】. XID 可視性の判断ロジック(MVCC)
-
ファイル:
src/include/access/transam.h
-
定義例:
FrozenTransactionId
-
概要:
xid2
は特別な値として予約されており、凍結済み行に対して割り当てられる。
4. PostgreSQL における有限IDの使い切り(枯渇)と対策の有無まとめ
xid以外にアーキテクチャ上何か気を付けることないの?という確認です。
→結論、設計上のNGあり、となります。
比較表:主要リソースの枯渇リスクと対策の有無
リソース | ビット数 | 枯渇可能性 | 極端な例 | 対策の有無 | 危険度 | 説明 |
---|---|---|---|---|---|---|
xid | 32bit | 〇 現実的 | 1000TPS × 90日 ≒ xid 21億進行 | 〇 あり(freeze) | 高 | 整合性に直結、最重要 |
OID | 32bit | △ 理論上可能 | 大量オブジェクト作成と削除の繰返し | × ほぼなし | 低〜中 | アプリがOIDに依存すると危険 |
relfilenode | 32bit | △ 理論上可能 | テーブルの作成・削除を大量実行 | × なし(内部再利用) | 中 | 一意IDに使うと危険 |
sequence | 64bit | △ 設定次第で可能 |
MAXVALUE=1000 などの設定ミス |
〇 CYCLE / RESET | 中 | 枯渇時に挙動選択可 |
LSN(WAL) | 64bit | × 非現実的 | 秒間数GB WALを数年続ける | × なし | 低 | 実質気にしなくてOK |
ctid | - | × 枯渇なし | ページ内に多数の行 → 再利用される | 〇 自動管理 | 低 | 一意IDに使うと危険 |
VXID | 内部 | 〇 枯渇し得る | 並列TX多数 + ロングTX放置 | 〇 自動クリア | 中 | ロック競合・スローダウン要因 |
backend ID | 内部 | 〇 上限あり |
max_connections を超える接続 |
〇 パラメータ制御 | 中 | OS制限に依存 |
なぜ xid は「特別に」対策されているのか?
可視性管理、DBの機能不全に直結するため
- PostgreSQL の行ごとの可視性(MVCC)は
xmin
/xmax
によって決まる - xidが周回すると、古いトランザクションが「未来の行」を見えてしまう=整合性破壊
そのため、2層の防御策がある:
対策 | 説明 |
---|---|
1.autovacuum_freeze_max_age
|
xid ageが2億超で強制freeze発動 |
2.最終防衛ライン(age > 2^31) | FATALエラーで書き込み停止(wraparound保護) |
他リソースは「使い切っても致命傷にはならない」が、設計次第で事故になる
リソース | リスク例 |
---|---|
OID | アプリがOID値に依存 → 再利用で誤動作 |
sequence | 上限設定ミス(MAXVALUE)で即停止。自動採番が動かない |
VXID | ロングトランザクションで枯渇 → ロック取得できない状況に |
WAL/LSN |
pg_wal ディレクトリ肥大化 → ディスク枯渇、PITR障害など |
極端な机上シナリオ例(検証目的)
リソース | シナリオ例 |
---|---|
OID | テーブル作成→削除を4億回実行(実行には数ヶ月〜年単位) |
sequence |
MAXVALUE=1000 設定で1001回目にINSERT失敗 |
VXID | 10万並列TX + ロングTX放置で競合発生、トランザクション開始不能 |
運用上の推奨対応
- ディスク容量不足はpostgresアーキテクチャ上の有限のカウンタ、という今回の主題ではないが、普通に不足し得るので監視すべき
-
pg_wal: 容量監視・保管数制限(
archive_cleanup_command
等) - walのアーカイブ領域もあれば同様
まとめ
- xidはPostgreSQLの整合性の心臓部。特別な保護設計がされている
- 他リソースも「枯渇=停止」になるリスクがあるため、設計段階から意識すべき
- 長期運用・高TPS環境では、有限リソースの枯渇を前提にした監視と対策が重要
おわりに
間違った記述あれば是非ご指摘お願いいたします。
基本的には、デフォルト設定で、凝ったことしない、極端な高負荷な利用はしない、ロングトランザクション放置とかしない、要らないレプリカ設定残存させない、
ということであれば、
統計情報更新、自動vacuum実行などの履歴を見て、管理対象の速度感を記録していく、
という感じで大丈夫だと思っています。
過度に「気にしなくても良い」。定期的に監視してそのDBの健康状態を把握していきましょう。