问题最初出现在 MatrixDB 集群的 Primary-Mirror 流复制过程中。Mirror 节点在回放 WAL record 时,持续报出 CRC32C 校验错误:
LOG: incorrect resource manager data checksum in record at 0/4F551398
随后,Mirror 开始不断重试 WAL 回放,同步复制链路被阻塞,分布式事务 COMMIT 逐渐出现超时,整个集群也开始进入不稳定状态。
从表面现象来看,这像是一次典型的 WAL 数据损坏问题。但真正困难的地方在于,这个问题并不是稳定复现的。绝大多数 WAL 文件完全正常,只有极少数 segment 会在某个固定位置出现 CRC 错误。这意味着,问题不像传统软件 Bug 那样具有稳定规律,而更像是一次低概率、瞬态的数据污染事件。
对于数据库系统来说,这类偶发问题往往最难定位。因为它既无法通过简单压测稳定触发,也很难从日志中直接找到明确线索。更关键的是,在后续 WAL 二进制对比中,团队发现 24 个 WAL 文件中有 22 个完全一致,只有 1 个文件存在真实损坏,并且中间还间隔了多个完全正常的 WAL segment。
这一现象非常关键。它意味着问题并不是持续性的系统性错误,而更像是某个链路节点中偶发出现的一次异常数据注入。问题也因此开始逐渐从数据库内部,指向更底层的数据传输链路。
由于最终报错发生在 Mirror 回放阶段,因此最自然的推测,是 WAL 文件在写盘过程中发生了损坏。为了验证这一点,团队首先对 Mirror 的 WAL 存储进行了迁移测试,将 WAL 目录软链接到另一块磁盘,希望借此排除存储介质异常。
但测试结果显示,问题依旧存在。这意味着,数据在真正落盘之前,很可能就已经发生了损坏。问题也因此从“磁盘异常”,逐渐转向“传输链路异常”。然而,仅依靠传统日志已经无法继续缩小范围。于是,YMatrix 团队决定不再“猜测”问题,而是直接在 WAL 传输链路的关键节点抓取真实数据。
团队随后设计了多组同步 GDB 捕获实验,对 WAL 在不同阶段的数据状态进行逐点验证。首先,在 Primary 的 walsender 中抓取发送前的 WAL 数据。结果显示,pg_waldump 校验完全通过,CRC32C 正常,WAL record 内容完整。这说明,Primary 本地 WAL 文件没有问题,从磁盘读取到 walsender 输出之间的链路也是健康的。
但当团队进一步在 Mirror 的 walreceiver 中,对 recv() 后、处理前的数据进行抓取时,问题第一次真正浮现出来。walreceiver 接收到的数据已经发生损坏,并且与最终写入磁盘的数据完全一致。这意味着,write() 并不是损坏发生的位置,Mirror 只是“忠实地”把错误数据写入了磁盘。
至此,问题第一次被明确收敛:Primary 发出的 WAL 是正确的,但数据在传输过程中被污染。这一结论的意义非常重要。因为它意味着问题已经不再局限于数据库内部逻辑,而开始进入数据库与操作系统、网络以及硬件之间的交界区域。
为了进一步确认数据库软件层是否存在改写 WAL payload 的可能性,团队随后开始对 PostgreSQL GPDB 的 WAL 流复制源码进行逐段审计。
理论上,一条 WAL 从 Primary 到 Mirror,会依次经过 WAL 文件读取、walsender、libpq buffer、kernel socket、TCP stack、NIC DMA、网络链路以及 Mirror recv buffer 等多个节点。任何一个环节,都有可能引入异常。
但真正深入源码之后,团队发现整个 WAL 复制链路其实异常“薄”。
大量路径本质上只是:
memcpy → send() → recv() → memcpy → write()
整个过程中,并不存在压缩、协议重编码、数据转换等复杂逻辑,在未启用 SSL/GSS 的情况下,也不存在加密处理。绝大多数链路,本质上都只是标准内存拷贝与系统调用。
这意味着,数据库软件层几乎不存在“主动改写 WAL 数据”的路径。换句话说,如果 sender 发出的 WAL 是正确的,而 receiver 收到的数据已经损坏,那么问题更可能发生在数据库软件之外,也就是 Linux 网络栈、DMA 链路,甚至更底层的硬件传输阶段。问题开始进一步向基础设施层收敛。
随后,团队对 sender 与 receiver 的 WAL segment 做了全量二进制对比。结果非常耐人寻味。24 个 WAL 文件中,有 22 个完全一致,只有 1 个文件存在真实损坏。这意味着问题并不是持续性的链路错误,而更像是一次瞬时的数据污染事件。
但真正让问题发生“质变”的,并不是“只有一个文件损坏”,而是损坏区域本身的内容。正常 WAL 数据通常具有较高熵值,更接近随机二进制流;但异常区域却呈现出了非常明显的结构化特征:
FF E1 EA DA 01 00 00 0000 FF FF FF FF 00 00 00
这段异常数据的长度,精确为 64 字节。对于数据库开发者而言,64 字节或许只是一个普通数字;但在硬件世界里,它却极具指向性。64 字节通常意味着一个 CPU cache line、一个 DMA descriptor,或者一个典型 PCIe payload chunk。而异常数据中频繁出现的 FF FF FF FF,又非常像 PCIe error poisoning 或 DMA 控制结构中的典型标记。
更重要的是,这段结构化数据并不存在于 sender 原始 WAL 中,却被完整插入到了 receiver 的 WAL 流里。这意味着,它并不是 WAL 本身的一部分,而是一段“外来的结构化数据”。至此,问题已经开始明显超出数据库软件范畴,团队也逐渐将怀疑方向转向 NIC DMA descriptor 泄漏、PCIe TLP error poisoning,以及 DMA 链路异常等更底层的问题。
这是整个案例里最容易被误解,却也最关键的一点。很多人会默认认为:“既然 TCP checksum 校验通过,那么数据一定是正确的。”但实际上,TCP checksum 的能力远没有想象中强大。
TCP 能保证的是,数据在网络传输过程中没有发生变化;但它无法保证,参与 checksum 计算的数据本身一定正确。如果损坏发生在 checksum 计算之前,例如 NIC DMA 内部、PCIe DMA 写内存阶段,或者硬件 offload 过程中,那么 TCP checksum 覆盖到的,很可能已经是“被污染后的数据”。在这种情况下,即使 recv() 完全正常,TCP 连接没有任何异常,数据库依然可能收到错误 WAL。
这也是为什么 PostgreSQL 体系中,除了 TCP 校验之外,仍然需要维护 WAL CRC32C、page checksum 以及 end-to-end consistency 等机制。因为数据库系统必须能够防御那些:“TCP 认为正确,但数据实际上已经损坏”的场景。而这次问题,本质上正属于这一类异常。
回过头来看,这次问题最初只是一次 WAL 回放失败。但随着排查不断深入,问题最终一路延伸到了 Linux 网络栈、DMA 机制以及 PCIe 硬件链路。这类问题最大的特点,就是低概率、难复现、日志不可见,传统监控体系几乎无法覆盖。
很多时候,人们习惯于把数据库理解为一个软件系统,但实际上,在今天的大规模实时数据场景下,数据库已经越来越接近硬件边界。NUMA、CPU cache、DMA、NIC offload、PCIe、Linux 网络栈……这些过去被认为属于“系统层”的内容,如今都可能直接影响数据库稳定性。
对于现代数据基础设施而言,真正的可靠性,也早已不再只是数据库软件本身,而是整个数据链路在每一个层级上的一致性与可验证性。
这次 WAL 异常排查,最终从数据库内核一路深入到了 Linux 网络栈、DMA 机制以及 PCIe 硬件链路。这类问题之所以难以定位,并不只是因为涉及的技术层级复杂,而是因为它本身处在一个“边界地带”:既不像典型的软件 Bug 那样可复现,也不像传统硬件故障那样会有明确报错,而是以极低概率、瞬时性、且缺乏日志痕迹的方式出现,使得基于单层系统的分析方法很容易失效。
这类问题的推进也不再依赖某一层的“经验判断”,而是必须把整个数据链路拆开,从数据库内部一直追溯到操作系统,再到网络传输,甚至硬件 DMA 的实际行为,通过逐层验证来确认“数据在每一跳是否仍然可信”。
在这个过程中可以看到一个更本质的变化:数据库系统本身正在逐渐从“软件组件”,演变为一个横跨操作系统与硬件链路的完整数据基础设施。数据的正确性,不再只由数据库内部逻辑保证,而是取决于整条链路在多个层级上的一致性。未来,YMatrix 将继续围绕分布式数据库内核、流式实时架构、Agent 时代数据底座以及高可靠数据链路等方向,持续推进更深入的底层技术探索与工程实践。
感谢你的阅读,YMatrix 期待与志同道合的你一起同行。
推荐阅读









