▶ Cinematic fable · Watch on YouTube ▶ 影片版寓言 · 在 YouTube 观看

大虞帝国的司历台与太极秘阁,坐落于皇城最幽深的山巅。这里封存着开国八百年来全天下的宗族田契、官民契约与帝王诏令。

为了厘清世间千万宗卷的生效先后,司历台的始祖天工在大殿中央铸造了一座通体玄铜的巨型机巧——浑天循环轮。 铜轮周天密密麻麻刻着四十二亿九千万道自增齿痕。每逢天下官府办妥一桩田土买卖、录入一道朝廷法令,铜轮便“咔哒”向左咬合一齿,为卷宗印上当前那一齿的专属天轨秘号。

按司历台代代相传的断狱铁律: “以轮首之针为准,前半周二百一十四万刻度之卷,皆为已定之青史,当庭验明有效;后半周二百一十四万刻度,皆为未降之谶言,视同白纸虚妄,绝不可付诸执行。” 换言之,任何时刻,天地间唯有过去半圈的文书被朝廷承认;若是卷宗上的号数落在了当前轮针的前方半圈,便算作“来自未来的拟稿”,官府一律判定无效。

盛世百年,四海商贾如云,朝廷赋税与钱庄存底日夜流转。 司历台的铜轮日夜不休地飞旋,齿轮咬合之声如夏蝉暴雨,“咔哒、咔哒”响彻秘阁。 官吏们只顾着翻阅近几年、近几月的火热文书,为每一桩新买卖盖上天轨印记。铜轮转得越来越快,自千而万,自万而亿,不知不觉间,整整两百多万个昼夜与数以亿计的印记随风呼啸而过。

然而,所有人都忘记了秘阁地窖最阴暗的角落。 那里躺着大虞开国太祖分封天下世家的金书铁券,以及八百年前定鼎江山的九州地契。那些竹简绢帛蒙着厚厚尘埃,上面盖着铜轮最初转动时的“第壹号”与“第贰号”古老印章。

那是一个深秋的清晨,浑天轮指针悄然转过了第二百一十四万七千四百八十三刻度的关口——铜轮的前行距离,终于堪堪超越了整座圆环的一半。

临安巨贾苏氏持着祖传的桑田世契前往官衙过户。推官摊开泛黄的太祖地契,命铜轮侍卫拨动刻度尺核验。 侍卫量完,脸色骤变,冷汗如注。 按照司历台的圆轮规矩,当指针走到当前位置时,圆环对面的“第壹号”印记,已经滑出了“过去半周”的青史范围,悍然跌入了“未来半周”的谶言疆域!

“混账!”推官将惊堂木拍得震天响,“你这分明是两百一十万刻度之后、来自未来的伪造谶书!大虞律法写得清清楚楚:未来之文,形同虚设!” 苏氏一族如遭雷击。紧接着,整个天下大乱。 八百年前的开国诏书“消失”了;累世簪缨的功臣家产变成了“尚未生效的废纸”;宗庙祠堂的册籍在法理上化作了青烟。 更可怕的是,那些载着古老文字的绢帛完好无损地躺在地窖木匣里,文字清晰,印泥鲜红——但在整座帝国的律法推演中,它们彻底隐匿于虚空,视而不见!

秘阁典簿官疾步冲入司历台,尖叫声撕裂静夜:“大灾降临!铜轮满半周,古籍皆作来生信!若任由铜轮再转几刻,整座天下的宗卷将尽数湮灭!”

老太史令面色煞白,当机立断拔出尚方宝剑,一剑斩断了浑天轮的引水铜渠! “鸣警世铜钟!锁闭九重宫门!”太史令须发皆张,嘶声高呼,“传令天下官府,即刻止笔封印!自今日始,不准天下录入一两银钱、不准批准一亩田产!哪怕圣旨出阁,亦绝不许再催铜轮向前哪怕一齿!” 天下百官震恐。帝国心脏骤停,一切政务与商贸彻底陷入冰冻死寂。

在这万籁俱寂的停摆之中,太史令自密室请出先皇传下的赤玉宝匣,启出一柄非金非石的朱砂大印——太初永恒印。 此印承载先祖遗训:凡经受此印加盖之卷宗,无论原先盖着何年何刻之印号,一律拔出浑天轮的轮回法则,被敕封为“与天地同寿、亘古永存之基石”。无论铜轮再转几千几万周,凡见此朱砂永恒印者,皆视作万古青史,全域昭然可见!

在紧闭宫门的三天三夜里,满朝官员举着烛火潜入秘阁地窖。 他们在一座座尘封的古架间翻箱倒柜,拂去蛛网,将所有印号垂暮的古老文书挑拣出来,在泛黄的卷首郑重盖下鲜红夺目的太初永恒印。 当最后一卷太祖铁券落上朱红,浑天轮被重新校准、水渠重开,铜钟长鸣,禁令解除,天下重归运转。

经此浩劫,帝国立下两道万世不移的铁防: 其一,设“夜巡校书郎”,常年巡弋秘阁各架,凡见年深日久、将近界限之卷宗,立即主动补盖朱砂永恒印,防患于未然; 其二,在浑天轮外圈铸造两道紧密咬合的警铃:当老旧未印之卷逼近两百一十万大关时,金铃急响,逼令校书郎全速抢修;倘若当真只差三万刻度便要吞噬历史,天机机关将自动自锁轮轴,宁可令全境停摆陷入只读,也绝不容许哪怕一页青史沉入遗忘的深渊!

— — —

这是什么

——到这儿你大概已经认出来了:这正是现代关系型数据库世界(尤其是最知名的代表 PostgreSQL)中最为致命、却又设计极为深邃的体系级挑战:事务标识符回绕(Transaction ID Wraparound / TXID Wraparound) 与防止该灾难的核心机制——元组冻结(Vacuum Freeze)。

在 PostgreSQL 的多版本并发控制(MVCC)架构中,每一行数据(元组 / Tuple)的头部都刻有两个核心字段:xmin(插入该行的事务 ID)和 xmax(删除或更新该行的事务 ID)。为了在无锁状态下实现快照隔离(Snapshot Isolation),系统通过对比当前事务的 ID 与行头部的 xmin/xmax,来判定该行在当前事务的快照视野中是否可见。

这一机制隐藏着一个计算机历史上经典的有限位宽难题:

  1. 32 位循环时序环(32-bit Modular Arithmetic): PostgreSQL 采用 32 位无符号整数表示事务 ID(XID),总容量约为 42.9 亿($2^{32} \approx 4,294,967,296$)。对于高并发的生产数据库,数十亿次事务数月或数年即可耗尽。为了让有限的 32 位计数器能够无限期使用,PostgreSQL 将 XID 视作一个无起点的环形时钟,采用模 $2^{31}$(约 21.4 亿)的模运算判定先后: 对于任何当前的事务 ID $T_{curr}$,落在区间 $[T_{curr} - 2^{31}, T_{curr})$ 的 XID 被判定在过去(对当前事务可见,取决于提交状态);而落在区间 $[T_{curr}, T_{curr} + 2^{31})$ 的 XID 则被判定在未来(尚未发生,对当前事务绝对不可见)。
  2. 回绕灭顶之灾(Wraparound Data Loss): 如果系统持续运行,新事务不断产生,$T_{curr}$ 不断前进。若一张表里有一批极少修改的历史基石数据(例如建表之初插入的系统配置、用户账号),它们的 xmin 保持为很小的旧数值。 一旦数据库总共前进了超过 $2^{31}$(约 21.4 亿)个事务,而这批古老元组未被处理,在模运算的环形几何中,它们原本落后的位置突然“跳跃”到了当前事务前方的前半周! 此时,数据库的 MVCC 引擎(如 HeapTupleSatisfiesMVCC)会认为:这些行是由未来的事务写入的! 瞬间,这批古老数据在所有正在执行的查询中彻底蒸发隐身——虽然物理磁盘上的数据块完好无损,但在逻辑查询层面上,数据全部凭空丢失!
  3. 元组冻结(Vacuum Freeze): 为了破解这一悖论,PostgreSQL 引入了“冻结”(Freeze)概念。 在后台执行 VACUUM 时,系统扫描数据页中由于年代久远(年龄超过 vacuum_freeze_min_age)的元组,将其标记为“已冻结”(早期版本将其 xmin 覆写为特殊的 FrozenTransactionId = 2;现代版本则在元组头部的 t_infomask 中标记 HEAP_XMIN_FROZEN 状态位)。 MVCC 规则明确规定:已冻结的元组永远被视作比全宇宙所有过去与未来的事务都要古老。它不再参与模运算比较,在任何查询视野中都永恒可见。
  4. 多层阶梯式防御与安全停机保护(Failsafe Shutdown): PostgreSQL 为防止 DBA 忽视清理,在内核中固化了层层加码的防御铁律:
    • 激进自动清理(Anti-wraparound Autovacuum):当某张表中最老未冻结事务的年龄达到参数 autovacuum_freeze_max_age(默认 2 亿)时,PostgreSQL 会强制启动无法被取消、无法被 autovacuum = off 禁用的强行冻结清理任务;
    • 紧急避险停机(Emergency Failsafe Halt):若因锁争用、长事务阻塞或硬件过载,导致表年龄逼近极限——距离 $2^{31}$ 回绕仅剩 300 万个事务($2^{31} - 3,000,000$,即年龄达到约 2,144,483,647)时,PostgreSQL 会在日志中疯狂告警,并主动拒绝执行任何新的写入事务(直接报错 FATAL: database is not accepting commands to avoid wraparound data loss),直至彻底关机。此时系统只能在单用户模式(Single-user mode)下启动,由运维人员手工执行全量 VACUUM FREEZE 抢救数据库。

为什么重要

事务 ID 回绕是每一个大型系统工程师与 SRE 必修的高级系统可靠性课:

  1. 有限状态表示无限时间的计算机本原难题: 在存储系统设计中,每一个字节的行头开销都价值连城(若升级为 64 位 XID,每行增加 4~8 字节,千万级行数的表将膨胀数个 GB)。32 位 XID 配合逻辑环与标记位,是在极其苛刻的存储紧凑性与无限时间轴之间的工程平衡。
  2. “物理完好但逻辑灭绝”的最隐蔽数据灾难: 不同于磁盘损坏、断电丢日志等物理故障,回绕事故发生时磁盘没有半个坏道,数据行原封不动地躺在操作系统缓存与存储卷中,却因因果时序断裂而无法被任何 SQL 触及。
  3. 自动化运维(Autovacuum)不是“可选项”,而是系统的“呼吸”: 很多运维为了降低 CPU/IO 开销盲目关闭或限制 autovacuum,却不知在 MVCC 引擎中,清理垃圾行与冻结事务号是维系系统生命的呼吸机制。一旦长事务阻塞全局最老 XID(OldestXmin),就如同一根扎进肺部的毒刺,足以在数月后引发整机只读停摆。
  4. 生产灾难血泪史: 从知名开源监控厂商 Sentry 早期遭遇的全站多小时离线,到各大云原生厂商与金融机构的真实停机事件,TXID 回绕紧急停机是每一个大规模运行关系型数据库团队必须通过监控(如 datfrozenxid 年龄指标)严防死守的终极红线。

隐喻对应表

  • 四十二亿九千万刻度的浑天青铜日晷轮 → 32 位无符号整数事务 ID 空间($2^{32} \approx 42.9$ 亿 XID 环)
  • 轮针前半周为定论青史、后半周为未现谶言 → 模 $2^{31}$ 环形时序判定(过去事务可见,未来事务不可见)
  • 浑天轮转动半周使开国宗卷落入未来而隐形 → 事务号跨越 21.4 亿导致古老未冻结元组在 MVCC 下彻底失明(TXID Wraparound)
  • 躺在地窖木匣中完好却在法理上消失的绢帛 → 磁盘物理数据页完好无损,但逻辑查询全部判定不可见的静默丢失
  • 太初朱砂永恒印将文书超脱于轮盘之外 → VACUUM FREEZE 机制与 HEAP_XMIN_FROZEN 元组标记位
  • 夜巡校书郎日夜巡查除尘补印 → 后台自动清理守护进程(Autovacuum Anti-wraparound Worker)
  • 逼近大关时急促作响的金铃 → autovacuum_freeze_max_age 触发的强行全速清理阈值
  • 斩断水渠、闭门封锁全城交易的断然停摆 → 剩余 300 万事务时数据库触发的紧急避险只读硬停机(Shutdown to avoid wraparound)

High upon the cloud-shrouded peak of the imperial capital stood the Observatory of the Celestial Bureau and the Grand Imperial Archive. Here rested the founding decrees, provincial land charters, and imperial registries accumulated across eight hundred years of dynastic rule.

To order the sequence of worldly transactions without bias, the bureau’s founding mechanists had forged an immense bronze instrument at the center of the hall: The Circular Sky-Wheel. Around the perimeter of the great bronze wheel were chiseled exactly 4,294,967,296 fine teeth. Whenever a magistrate ratified a parcel of farmland or issued an imperial edict, the massive wheel clicked one tooth to the left, stamping the document with the unique celestial coordinate of that exact tooth.

By the founding statutes of the bureau, the law of evidence was absolute: “Measuring from the wheel’s needle, documents stamped within the prior half-circle of 2,147,483,648 ticks are acknowledged as settled history and are fully valid in every court. Documents falling within the opposing half-circle ahead are unfulfilled prophecies; they are deemed blank air, strictly invalid for execution.” In other words, at any given moment, the realm honored only the documents belonging to the past half-circle. If an inscription sat ahead of the current needle, magistrate clerks treated it as an unapproved future draft, granting it zero legal weight.

For generations, peace and commerce flourished. Merchant caravans traded silk and salt across five rivers, and silver flowed through banking houses day and night. The bronze wheel spun ceaselessly. The sound of its teeth engaging—clack, clack, clack—hummed through the archive chambers like summer cicadas. Scribes busied themselves examining the fresh, high-volume transactions of the passing months, affixing celestial coordinates to every new contract. The wheel whirled ever faster: thousands became millions, millions became hundreds of millions, as billions of clicks swept past unnoticed on the currents of time.

Yet everyone forgot the dampest, darkest cellar beneath the archive’s foundation. There rested the sacred iron scrolls granted to the founding nobility and the master land charters chiseled eight centuries prior. Those ancient silks lay cloaked under thick layers of dust, stamped with coordinates #1 and #2 from the very first dawn of the bronze wheel.

On a misty autumn morning, the needle of the Circular Sky-Wheel quietly clicked past the threshold of tick 2,147,483,649. The wheel had advanced just beyond exactly half its circumference from those earliest foundational days.

A prominent merchant named Su presented his ancestral land charter before the metropolitan magistrate to transfer his grandfather’s salt estates. The magistrate unrolled the parchment and ordered the wheel-clerks to align their brass calipers. The clerks measured, and their faces turned ashen. Cold sweat poured down their collars. By the strict circular geometry of the bureau, the moment the needle crossed that halfway meridian, coordinate #1 on the opposite rim slipped out of the “historical past” and landed squarely inside the “forbidden future”!

“Audacious thief!” roared the magistrate, slamming his gavel. “This document bears a date more than two billion ticks into the future! It is a wild prophecy! Imperial statute is explicit: future writings carry no force of law!” The merchant house was shattered. Within hours, panic tore through the provinces. Eight-hundred-year-old dynastic charters evaporated from legal existence. Ancestral estates of the high nobility became invalid scraps of cloth overnight. Most terrifying of all: the ink on the ancient parchments was as crisp as day, resting physically intact in their cedar chests—yet before the eyes of imperial jurisprudence, they were entirely invisible!

The Chief Archivist rushed into the Observatory, his voice trembling: “Catastrophe! The wheel has crossed the half-mark! Our ancient history has turned into future fiction! If the wheel turns another few ticks, the memory of the entire realm will be erased!”

The Grand Astrologer went pale. Without a word, he drew his sword and cleaved the water flume powering the great bronze mechanism in two! “Sound the great bell! Bar the palace gates!” he cried. “Dispatch riders to every province: freeze all ledgers! Cease every transaction! Not one ounce of silver may be accounted, not one mu of land may change hands! Not another tooth shall turn upon this wheel!” The empire’s heart stopped beating. Every court, bank, and merchant house froze in absolute, chilling deadlock.

Amidst this terrifying silence, the Grand Astrologer unlocked an ancestral jade chest and lifted out a seal carved from unyielding stone: The Eternal Cinnabar Seal. This seal carried the founder’s deepest wisdom: Any parchment touched by its vermillion ink was immediately liberated from the circular arithmetic of the sky-wheel. It was consecrated as a “Rock of the Primordial Dawn.” No matter how many millions of revolutions the bronze wheel might turn, any parchment bearing the vermillion mark was forever deemed older than all time—universally visible across every court for all eternity.

For three days and three nights behind barred gates, imperial scholars descended into the dark vaults with torches in hand. They scoured the dusty corridors, blew away spiderwebs, and inspected every ancient roll. Upon every scroll whose coordinate was approaching the dangerous boundary, they stamped the vivid red seal. Only when the final founding charter received the vermillion imprint was the water wheel reconnected. The iron gates swung wide, and the realm breathed once more.

From that day forward, two permanent safeguards guarded the empire against forgetting: First, a company of Night-Watch Curators was assigned to walk the dusty vaults indefinitely, identifying scrolls whose coordinates aged toward the threshold and pre-emptively blessing them with the cinnabar mark. Second, two mechanical tripwires were forged onto the bronze wheel itself: when any unsealed parchment approached the two-billion-tick warning, iron bells clattered violently, forcing all curators into frantic action; and should the wheel ever come within thirty thousand ticks of total oblivion, an internal gear locked the axle solid—freezing the entire realm into read-only silence, choosing paralysis over the eternal death of memory.

— — —

What it is

—By now you have likely recognized it: this is one of the most perilous, yet mathematically profound phenomena in relational database engineering: Transaction ID (TXID) Wraparound and its companion salvation, Vacuum Freeze, quintessential to multi-version engines such as PostgreSQL.

In PostgreSQL’s Multi-Version Concurrency Control (MVCC) architecture, every row (tuple) on disk carries two vital header fields: xmin (the Transaction ID that inserted it) and xmax (the Transaction ID that updated or deleted it). To achieve lock-free Snapshot Isolation, queries compare the executing transaction’s snapshot against these header IDs to determine whether a given row is visible.

This elegant mechanism rests upon a classic computing dilemma:

  1. The 32-bit Circular Timeline (Modulo $2^{31}$ Arithmetic): PostgreSQL represents transaction IDs using 32-bit unsigned integers, providing roughly 4.29 billion distinct values ($2^{32} \approx 4,294,967,296$). In high-throughput workloads, several billion transactions can easily accumulate over years or even months. To allow an infinite sequence of transactions using a finite 32-bit counter, PostgreSQL treats XIDs as a circular clock, evaluating causality via modulo $2^{31}$ (roughly 2.14 billion) arithmetic: For any current transaction ID $T_{curr}$, transactions in the interval $[T_{curr} - 2^{31}, T_{curr})$ are judged to have occurred in the past (visible to the current transaction, subject to commit status). Transactions in $[T_{curr}, T_{curr} + 2^{31})$ are judged to reside in the future (not yet occurred, completely invisible).
  2. The Catastrophe of Wraparound: As new transactions run, $T_{curr}$ steadily marches forward. If a database contains historical rows that are rarely updated (such as tenant tables, core configurations, or financial ledgers created on day one), their xmin remains frozen at their original small numbers. If the database executes more than $2^{31}$ transactions without maintenance on those tables, the circular horizon moves past the midway point. In the circular arithmetic, those ancient xmin values suddenly flip from being in the “past” to residing in the “future”! The MVCC visibility check (HeapTupleSatisfiesMVCC) will evaluate those ancient rows as having been created by an uncommitted transaction from the future. Instantly, those historical rows vanish from every SQL query. The physical bytes remain entirely intact on the storage drive, but logically, the data has evaporated into thin air!
  3. Vacuum Freeze: To prevent this disaster, PostgreSQL introduced the concept of “freezing” tuples. During regular vacuuming (VACUUM), the engine scans table pages for tuples whose xmin is older than vacuum_freeze_min_age. It marks these tuples as “frozen” (in older versions, replacing xmin with FrozenTransactionId = 2; in modern PostgreSQL, setting the HEAP_XMIN_FROZEN bit in the tuple’s t_infomask). By definition in the MVCC visibility rules: A frozen tuple is permanently older than all past, present, and future transactions. It bypasses circular arithmetic entirely and remains permanently visible to every query.
  4. Defense in Depth and Failsafe Shutdown: Because the consequence of wraparound is catastrophic, PostgreSQL embeds strict failsafe thresholds deep within its storage engine:
    • Anti-wraparound Autovacuum: When the age of the oldest unfrozen XID in any table reaches autovacuum_freeze_max_age (default 200 million), PostgreSQL spawns aggressive autovacuum worker processes that ignore user-configured throttling, cannot be canceled, and run even if autovacuum is globally disabled;
    • Emergency Failsafe Shutdown: If locks, long-running queries, or resource starvation prevent freezing and the table age reaches 3 million transactions shy of the $2^{31}$ boundary ($2^{31} - 3,000,000$, or age $\approx 2,144,483,647$), PostgreSQL emits frantic fatal warnings and refuses to accept any new write transactions (FATAL: database is not accepting commands to avoid wraparound data loss), shutting itself down. At that point, the database can only be resurrected in single-user maintenance mode (postgres --single) to manually run VACUUM FREEZE.

Why it matters

Understanding Transaction ID Wraparound is essential knowledge for backend engineers, database architects, and SREs:

  1. Representing Infinite Time in Finite Bits: Every additional byte in a tuple header multiplies across billions of rows. Expanding XIDs to 64 bits would bloat storage footprint, cache residency, and memory bandwidth. PostgreSQL’s compact 32-bit circular timeline with freeze flags is a masterclass in extreme storage optimization balanced against circular arithmetic.
  2. Logical Extinction vs. Physical Corruption: Wraparound is uniquely treacherous because no filesystem block is corrupted and no disk sector is bad. Backups of the raw disk blocks preserve the invalidity. Rebuilding visibility requires understanding the underlying timeline mechanics.
  3. Autovacuum Is Fundamental Hygiene, Not an Optional Utility: Engineers sometimes disable or throttle autovacuum to reduce I/O during heavy workloads, unaware that freezing tuples is the database’s cardiovascular system. Long-running idle transactions or abandoned replication slots that pin OldestXmin are silent ticking time-bombs that eventually trigger emergency read-only outages.
  4. Legendary Production Post-Mortems: From Sentry’s famous multi-hour global outage to documented incident reports at major cloud platforms, TXID wraparound panics are among the most celebrated rites of passage in large-scale database operations. Monitoring datfrozenxid age is a mandatory Tier-1 alerting metric in every production Postgres deployment.

Metaphor mapping

  • The Circular Sky-Wheel with 4.29 billion teeth → The 32-bit unsigned transaction ID circular space ($2^{32} \approx 4.29$ billion XIDs)
  • Prior half-circle as valid history, opposing half-circle as unfulfilled prophecy → Modulo $2^{31}$ circular timeline (past is visible, future is invisible)
  • Wheel turning past the half-circle, turning founding deeds into “future writings” → TXID wraparound causing ancient unfrozen rows to vanish from MVCC visibility
  • Cedar boxes intact while deeds vanish from legal standing → Intact physical heap pages on disk made logically invisible by MVCC checks
  • The Eternal Cinnabar Seal exempting scrolls from circular time → VACUUM FREEZE and the HEAP_XMIN_FROZEN tuple header bit
  • Night-Watch Curators patrolling vaults to inspect aging scrolls → Background autovacuum anti-wraparound workers
  • Clattering warning bells as ancient scrolls approach the danger mark → The autovacuum_freeze_max_age aggressive vacuum threshold
  • Severing the water channel and locking the imperial gates to freeze the realm → Emergency failsafe read-only shutdown when within 3 million transactions of wraparound
Daily Fables每日寓言 2026-09-20