| name | consistency-and-durability-verification |
| description | 验证系统的一致性与持久性承诺。在设计或修改复制协议、写路径、崩溃恢复逻辑时使用。当需要证明已确认的写入不丢失、副本间不发散、崩溃后能恢复到一致状态,或评审 fsync/校验和/事务语义时使用。 |
一致性与持久性验证
概述
存储和分布式系统的核心承诺只有两条:已确认的数据不丢(持久性),所有副本讲同一个故事(一致性)。这两条承诺无法靠代码审查保证——它们必须被系统地验证,因为违反它们的 bug 平时不可见,只在崩溃、分区和并发交错下显形。
核心纪律:先把承诺写成精确的可证伪陈述,再用故障注入 + 模型检查 + 不变量校验三层手段去证伪它。
何时使用
- 设计或修改复制协议(Raft、主从、链式复制、仲裁写)
- 修改写路径、WAL、fsync/组提交策略、缓存刷盘逻辑
- 实现或修改崩溃恢复、重放、快照逻辑
- 评审副本一致性、校验和、端到端数据完整性设计
- 响应"数据对不上"类事故,需要验证修复是否真正守住承诺
不适用于:一般功能测试(用 test-driven-development)、性能验证(用 performance-optimization)。
流程
1. 把承诺写成精确陈述
模糊的承诺无法验证。逐条写清楚:
- 持久性:客户端收到 ack 的写,在任意 ≤ f 个节点同时崩溃后仍可读回
- 一致性:任意时刻、任意副本上的读,满足 [强一致 / 读己之写 / 有界陈旧 / 最终一致]
- 原子性:多对象写入要么全部可见,要么全部不可见(含崩溃中点)
- 恢复:任意时刻 kill,重启后系统能到达一致状态且不报旧值
每条陈述必须指明:承诺在什么故障模型下成立(崩溃还是拜占庭?几个节点?分区多久?)。超出故障模型的场景不属于违约。
2. 审计写路径的持久化语义
逐个回答,答案必须来自代码而非口头:
- ack 在持久化的哪一点之后发出?(写 WAL?fsync 返回?多数派确认?)
- 每一层缓存(应用缓冲、page cache、盘内写缓存)的刷盘语义是什么?
- fsync/fdatasync 的每个调用点对应的持久化承诺是什么?组提交是否会延迟 ack 超出预期?
- 设备可能"说谎"吗(掉电后上报已持久化但实际丢失)?端到端校验和能否兜底?
- 崩溃中点恢复:重放是否幂等?部分应用的事务/批次如何被识别和回滚?
3. 崩溃恢复点校验(kill point 枚举)
把写路径切成关键阶段,在每两个阶段之间注入崩溃,恢复后校验五层契约:
- 系统能打开并服务(不自锁)
- 已 ack 的写不丢
- 未 ack 的写不得部分应用
- 读不到比 ack 时点更旧的值
- 元数据与数据自洽(索引不指向不存在的数据)
配合 failure-injection-testing 的注入编排执行;每个 kill point 的失败用例固化为回归测试。
4. 副本一致性验证
- 在线:周期性对账(merkle tree / 校验和比较),发现副本发散立即告警
- 离线:用历史记录检查验证一致性模型——收集并发操作历史(调用/返回时间 + 参数 + 返回值),用线性一致性检查器(Jepsen/Knossos/Porcupine 思路)判定是否存在合法串行化
- 读路径:在读路径嵌入断言(读到比已确认版本更旧的值即报错),让违约在测试期显形
5. 静默损坏防护验证
- 端到端校验和覆盖:写入时计算、读取时验证、后台巡检(scrub)定期重算
- 验证损坏被检测后走的是隔离/修复路径,而不是把坏数据返回给客户端
- 用 dm-flakey 等注入位翻转,确认每层校验和按设计拦截
6. 模型检查(设计变更时)
协议级变更(选主、成员变更、日志匹配规则)在实现前用 TLA+ 或等价工具做形式化规约和模型检查。实现后,用确定性模拟(seed 驱动的模拟网络/磁盘/时钟)大规模探索交错序列,失败 seed 固化为回归用例。
常见合理化借口
| 借口 | 现实 |
|---|
| "我们用了 Raft,一致性是现成的" | 论文的正确性不等于你实现的正确性。成员变更、快照、预投票都是常见的实现级 bug 来源 |
| "fsync 都调了,数据不会丢" | fsync 只保证它覆盖的那一段。应用缓冲、组提交窗口、盘内缓存每个都是丢失点 |
| "重启一下就恢复了" | 恢复过程本身可能是数据丢失点(误回滚已 ack 的写)。恢复路径必须被测试,不是被假设 |
| "副本天天跑,不一致早发现了" | 没有对账就没有发现机制。副本发散是静默的,直到某次故障切换读到了旧数据 |
| "崩溃中点的概率太低不用测" | 概率低 × 调用次数多 = 必然发生。kill point 测试就是要把低概率变成必然复现 |
| "性能要紧,校验和可以省" | 省掉端到端校验和等于放弃发现静默损坏的唯一手段。先量化开销再谈取舍 |
红旗警告
- 承诺用"高可用""强一致"等形容词表述,没有可证伪的精确陈述
- ack 时机与持久化时点对不上,或没人能不看代码就回答
- 恢复路径没有测试覆盖,只靠"重启试试"
- 副本间没有周期性对账机制
- 校验和只覆盖部分链路(如只在网络层,不覆盖落盘和内存)
- 协议变更没有规约评审就直接改实现
- 一致性违约靠用户报表发现,而不是系统自身告警
验证