「postgresのxid周回は普通気にしなくても良い」と言いたいまとめ


## はじめに

postgresを9系の頃に利用し始めて、今はもう16とか17とかも使っている。
でも別に深い機能とか高負荷とかの利用はしていない。
そんなライトな運用でも何となく以前から「xid周回は気を付けろ」という話はあったが、
デフォルトでautovacuumしてるせいか、特に問題になったことはない。
一度、16のデフォルト設定で、「postgresのxid周回は普通気にしなくても良い」と言いたいまとめを行ってみる。

---

<details><summary>エグゼクティブサマリ</summary>

# 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）が超えると、**可視性判定（「行が見えるか」）が破綻する**

```mermaid
flowchart TD
    A1[XIDは32bit符号なしカウンタ<br>範囲：0〜約42億] --> A2
    A2[可視性判定は<br>int32符号付きで差分比較] --> B1[安全な差分は±2^31＝約21億]

    B1 --> C1[古いXIDと現在のXIDの差分が21億以内<br>→ 可視性OK]

    B1 --> D1[ageが21億を超えると<br>比較結果が逆転]
    D1 --> D2[古い行が「未来の行」と誤認され見えなくなる]
    D2 --> D3[→ 可視性が破綻 = データ整合性が壊れる]

    D3 --> E1[PostgreSQLはこの前に<br>FATALで書き込み停止]
    F1[★事前にautovacuum + freezeで防止すべき] --> E1

```

---

## ■ 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周回自体は“心配しなくていい技術的な話”です。

</details>


---

# 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の割り当てを拒絶します。
```

```mermaid
sequenceDiagram
    participant App as アプリ
    participant PG as PostgreSQL
    participant AutoVac as autovacuum
    participant DBA as 管理者

    App->>PG: トランザクション実行 (xid消費)
    loop 通常運用
        PG->>AutoVac: dead tuple検知
        AutoVac->>PG: 通常autovacuum実行
        Note right of AutoVac: relfrozenxid更新
    end

    Note over PG: xidが進行し、age(datfrozenxid)が増加

    alt age > 2億
        PG->>AutoVac: 強制autovacuum起動（to prevent wraparound）
    end

    alt age > 21億
        PG-->>DBA: FATAL: トランザクション拒否
    end

```

---

## 【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

```mermaid
flowchart TD
    A[テーブルに変更あり] --> B{統計情報で見て変更量が閾値超え？}
    B -- はい --> C[通常autovacuum起動]
    B -- いいえ --> D{relfrozenxidが古い？}
    D -- はい --> E[freeze目的のautovacuum起動]
    D -- いいえ --> F[autovacuum対象外]
    C --> G[dead tuple回収 & relfrozenxid更新]
    E --> G

```
---

## 【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万レコード更新する

**参考リンク**：  
- https://www.postgresql.org/docs/current/routine-vacuuming.html  

---

## 【6】TPSとdead tupleの関係

- **xid消費はトランザクション単位**（1 TPS = 1 xid）
- **dead tupleは「更新された行数」に比例して増える**
- つまり、**TPSが低くても1回のトランザクションで数千万行UPDATEすれば、毎秒数千万のdead tupleが発生可能**

**参考リンク**：  
- https://www.postgresql.org/docs/current/storage-page-layout.html  

---

## 【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を阻害し続ければ、事故は再現可能**

```mermaid
graph TD
    A[高TPSでxid進行] --> B[TRUNCATEの繰り返し]
    A --> C[autovacuum無効 or worker不足]
    A --> D[ロングトランザクション放置]
    B --> E[dead tupleが発生しない]
    C --> F[freezeが間に合わない]
    D --> F
    E --> F
    F --> G[age datfrozenxid > 21億]
    G --> H[トランザクション拒否 DB停止]

```

4/17追記
コミッターの方が消費方法を紹介されていましたので、是非参照してください。

https://masahikosawada.github.io/2023/12/21/Burning-XIDs/

---

## 【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スクリプト例です。

```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時）
```cron
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スコープの関係

```mermaid
flowchart TB
    %% クラスタ全体
    subgraph Cluster_Level
        A1[xid : グローバルに進行]
        A2[txid_current]
    end

    %% データベース単位
    subgraph Database_Level
        B1[datfrozenxid : 全テーブルの最小relfrozenxid ]
        B2[age datfrozenxid ]
    end

    %% テーブル単位
    subgraph Table_Level
        C1[relfrozenxid : テーブルの最古行xid ]
        C2[age relfrozenxid ]
    end

    %% 行レベル
    subgraph Row_Level
        D1[xmin : 作成xid ]
        D2[age xmin ]
        D3[xmin = 2 で凍結済]
    end

    %% 関係線
    A1 --> A2
    A2 --> B2
    B1 --> B2
    B1 --> C1
    C1 --> C2
    A2 --> D2
    D1 --> D2
    D1 --> D3


```

---

## 【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`](https://github.com/postgres/postgres/blob/master/src/backend/access/heap/heapam.c#L9380)
- **概要**:  
  `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`](https://github.com/postgres/postgres/blob/master/src/backend/access/heap/heapam_visibility.c#L555)
- **概要**:  
  行の `xmin`, `xmax`, コミット状態を元に、その行が「現在のトランザクションから見えるか」を判断する。MVCCの根幹処理。


---

## 【8】. XID 可視性の判断ロジック（MVCC）

- **ファイル**: `src/include/access/transam.h`
- **定義例**: [`FrozenTransactionId`](https://github.com/postgres/postgres/blob/master/src/include/access/transam.h#L53)
- **概要**:  
  xid `2` は特別な値として予約されており、凍結済み行に対して割り当てられる。

---

# 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の健康状態を把握していきましょう。