Dev Study
← データベースの仕組み
レッスン 5 / 13·約 9 分·第6期 データベースの仕組み

32ビットが尽きる日 — 止まるのではなく、書けなくなる

リクエストがアプリケーションに届くまで
  1. ブラウザURL入力
  2. DNS名前→IP
  3. ルータ経路選択
  4. ロードバランサ振り分け
  5. Nginx受け口
  6. アプリ処理
  7. DB保存今ここ

固定ビット幅のカウンタが一周する問題は、他の層でも形を変えて現れます。ここではデータベースでの現れ方を扱います。WAL・分離レベル・インデックスはまだ扱っていません。

まず結論

トランザクションID(XID)は **32ビット**です。長く動き続けたクラスタでは番号が一周し、公式の言葉では "transactions that were in the past appear to be in the future — which means their output become invisible"。**過去が未来に見え、結果が見えなくなります。** 対策は `FrozenTransactionId` という特別なXIDで、常にどの通常XIDより古いと見なされます。

接近すると警告が出て、最後には新しいXIDの割り当てを拒否します。ただしその状態は**停止ではありません**。公式は "any transactions already in progress can continue, but only read-only transactions can be started" と書きます。**読み取り専用トランザクションだけは開始できる**——「落ちる」と覚えると復旧の判断を誤ります。

? 読む前に予測

XIDの周回が差し迫り、PostgreSQL が新しいXIDの割り当てを拒否し始めました。このときデータベースはどんな状態になっているでしょうか。

当てることが目的ではありません。外した予測ほど記憶に残るので、直感で選んでください。

抽象原理

有限の幅を持つ番号が、一周する

トランザクションには番号が振られます。この番号がトランザクションID、略してXIDです。そして幅が決まっています。

But since transaction IDs have limited size (32 bits) a cluster that runs for a long time (more than 4 billion transactions) would suffer transaction ID wraparound: the XID counter wraps around to zero, and all of a sudden transactions that were in the past appear to be in the future — which means their output become invisible. In short, catastrophic data loss.

32ビット。40億トランザクションを超えて動き続けたクラスタでは、カウンタがゼロに戻ります。

問題はここからです。番号が戻ると、過去のトランザクションが未来に見えます。どちらが先かを番号の大小で判断していたので、一周すると判断が反転します。そして結果はエラーにならず、"their output become invisible" ——見えなくなります。

データは在るのに参照できない。だから公式は "catastrophic data loss" と呼びます。エラーで止まってくれるほうがまだ扱いやすい種類の障害です。

回避の条件も書かれています。

To avoid this, it is necessary to vacuum every table in every database at least once every two billion transactions.

二十億トランザクションごとに、すべてのデータベースのすべての表を掃除する。 これが前のレッスンで見た4番目の理由——非常に古いデータの喪失を防ぐこと——の中身です。まったく更新していない表も対象になる理由が、ここにあります。

✓ ここまでの確認

XIDの周回が起きると何が問題になるのか、公式の記述に最も近い形で述べているのはどれですか。

具体実物

警告 → 拒否。しかし「停止」ではない

掃除が追いつかないまま周回点に近づくと、2段階で反応します。数字は原文どおりに押さえてください。

the system will begin to emit warning messages like this when the database's oldest XIDs reach forty million transactions from the wraparound point

まず周回点まで forty million で警告が出始めます。

If these warnings are ignored, the system will refuse to assign new XIDs once there are fewer than three million transactions left until wraparound

無視し続けると、残り fewer than three million で新しいXIDの割り当てを拒否します。このとき出るエラーがこれです。

ERROR: database is not accepting commands that assign new transaction IDs to avoid wraparound data loss in database "mydb" HINT: Execute a database-wide VACUUM in that database.

文面をよく読んでください。拒否されているのは commands that assign new transaction IDs です。すべてではありません。そして公式は HINT: で対処まで書いています——再起動ではなく、そのデータベース全体の掃除です。

In this condition any transactions already in progress can continue, but only read-only transactions can be started.

拒否状態で何ができるか(公式の記述による)表
進行中のトランザクション 継続できる
読み取り専用トランザクション 新たに開始できる
新しいXIDを要するコマンド 拒否される
プロセス 停止していない

停止していません。 進行中のものは続けられ、読み取り専用は新たに開始できます。ここを「落ちた」と覚えると、再起動しようとして時間を失います。必要なのは、XIDを進められる状態に戻すこと——つまり掃除です。

そしてこの性質は監視を惑わせます。読み取りが通るため、SELECT で生死を見るだけの監視は正常と報告しかねません。

✓ ここまでの確認

`autovacuum` を無効に設定した場合、周回防止のための処理はどうなりますか。

⇄ 2つを見比べる
具体例 A

vacuum_freeze_min_age = 50 million

設定パラメータの文書に "The default is 50 million transactions." とあります。役割は**下限**です。これより古ければ凍結の対象にしてよい、という境目を与えます。関連する `vacuum_freeze_table_age` は "The default is 150 million transactions." で、これも別の値です。

下限として働く側設定ファイル
vacuum_freeze_min_age = 50 million
vacuum_freeze_table_age = 150 million
 
役割: これ以上古ければ凍結の対象にする「下限」
出典: 設定パラメータの文書("The default is ...")
具体例 B

autovacuum_freeze_max_age = 200 million

こちらは**上限**です。これを超える前に autovacuum を起こすための境目です。値は設定パラメータの文書に "which is why the default is a relatively low 200 million transactions" とあります。

上限として働く側設定ファイル
autovacuum_freeze_max_age = 200 million
 
役割: これを超える前に autovacuum を起こす「上限」
出典: 設定パラメータの文書
(... the default is a relatively low 200 million transactions)

50 million と 200 million。どちらがどの設定値かを、値の暗記に頼らずに言い当てる方法はあるでしょうか。

名前の一部が答えになっています。min と max、そしてどちらに autovacuum が付いているか。

この章のまとめ

  • XIDは 32ビット。"more than 4 billion transactions" 動くと周回し、"transactions that were in the past appear to be in the future"、結果は "become invisible"。公式の言葉で "catastrophic data loss"。
  • 回避の条件は "vacuum every table in every database at least once every two billion transactions"。更新していない表も対象になる。
  • 対策は FrozenTransactionId。"does not follow the normal XID comparison rules and is always considered older than every normal XID" ——比較の輪から抜けさせる。
  • 警告は forty million から、拒否は fewer than three million で。桁が違い、間に猶予がある。
  • 拒否状態は停止ではない。"any transactions already in progress can continue, but only read-only transactions can be started." 再起動では直らない。
  • vacuum_freeze_min_age = 50 million(下限)、autovacuum_freeze_max_age = 200 million(上限)。取り違えやすいので役割で覚える。
  • "the system will launch autovacuum processes to prevent wraparound even when autovacuum is otherwise disabled." 切っても周回防止では起動される。

自分の現場で確かめる

読んだだけでは身につきません。手元の環境で実際に見てみてください。

理解度チェック

読んだ直後は「分かった」と感じやすいものです。実際に解いて確かめてください。

問題 1 / 3正解: 0

「周回が迫るとデータベースが停止するので、まず再起動して復旧させる」。この理解を公式の記述に照らして評価してください。

用語集(4語)
transaction ID wraparound(XID周回)
公式の説明は "the XID counter wraps around to zero, and all of a sudden transactions that were in the past appear to be in the future — which means their output become invisible. In short, catastrophic data loss." 32ビットという有限の幅から生じる。
FrozenTransactionId
公式の定義は "PostgreSQL reserves a special XID, `FrozenTransactionId`, which does not follow the normal XID comparison rules and is always considered older than every normal XID." 通常の比較規則に従わず、常にどの通常XIDより古いと見なされる。
vacuum_freeze_min_age
設定パラメータの文書に "The default is 50 million transactions." とある。関連して `vacuum_freeze_table_age` は "The default is 150 million transactions."。**200 million ではない**点に注意。
autovacuum_freeze_max_age
デフォルトは 200 million。設定パラメータの文書に "which is why the default is a relatively low 200 million transactions" とあり、routine-vacuuming 側の括弧書きでも "(The default, 200 million transactions, translates to about 50MB of `pg_xact` storage and about 2GB of `pg_commit_ts` storage.)" と示される。