第1章:先把恢复承诺写成可检查的业务事实
一家订单系统承诺成功提交后扣减账户余额。平时,应用把一次下单包装成事务:新增订单,写入负数分录,再更新余额。数据库故障后,人们往往先问备份有多大、导入要多久。更有价值的第一问是:恢复目标对应哪一次业务确认?如果这个问题没有答案,即使导入过程完全成功,也可能恢复到一个不被客户接受的世界。
用账本定义成功
随书业务从余额十万开始,所有金额使用整数最小单位。每个订单恰有一条支出分录,金额相反;账户余额等于初始余额加上全部分录之和。三条关系分别约束对象对应、金额含义和总账平衡。只有余额正确还不够,两笔订单一正一负地错位可能抵消;只有订单数量相同也不够,恢复出来的可能是不同客户或不同金额。
实验因此同时保存完整有序订单行、余额、分录总和与摘要。摘要方便比较和传递,但原始行仍保留在报告里,使差异能够定位到具体订单。这里的数据集很小,可以直接逐行检查。生产大表可能采用分区分块的规范化摘要,再对不一致块逐行核对。变化的是核对规模,核对的业务含义不能丢失。
给恢复目标一个名字
恢复通常有三类目标。第一类是回到某个备份快照,承认快照之后的交易暂时不存在;第二类是保留误删之前的有效事务,排除误删及其后不打算保留的变化;第三类是主库失效后以副本继续服务,接受或处理副本尚未拥有的确认记录。这三类目标的判定集不同,不应由同一个“恢复成功”标签掩盖。
我们给每个关键事务显式业务标签,例如误删前保留、误删后新增、已确认但未复制、新主专属写入。标签只用于实验解释,不参与MySQL的提交协议。恢复后直接查询这些ID,便能区分目标。生产可用订单号、支付流水号、幂等请求键等稳定标识,避免只根据大致发生时间猜测哪些请求丢失。
RPO表达允许或实际发生的数据损失范围,RTO表达从某个故障起点到某个服务恢复终点的时间。它们必须附带边界。本书实际报告丢失的一笔事务及其订单号,并记录客户端观测窗口;手工RTO从发起停止旧主到新主业务提交成功并核对结束。故障发现、代理路由和客户端重试没有进入程序,所以没有被算成完成。
把承诺拆成条件
“最多丢五分钟数据”至少依赖备份和日志能保存到哪里、日志能否读出、目标环境能否启动、恢复操作能否在预算内完成。复制副本落后五分钟,和每五分钟拷贝一次日志,并不是同一种保护。前者可能已经接收而尚未应用,后者可能根本没有保存源库最后一段日志。遇到源盘不可读时,两者的可恢复范围不同。
“十分钟恢复服务”也包含多个阶段:发现故障、判断故障、阻止旧主、选取数据源、恢复或提升、核对、切换入口、允许应用继续提交。若只给SQL导入计时,会把很多影响用户的时间藏起来。演练报告可以分别记录各阶段,再用整体边界给出总数。阶段拆解的作用是找到瓶颈,不是把困难阶段排除后宣传更好看的数字。
不变量不能替代业务确认
一个自洽的数据库也可能丢失合法订单。恢复到昨天的备份通常账务平衡,但今天客户收到的成功回执都不在里面。因此验收包含两层:数据库内部不变量,以及外部已确认事务集合。实验用源库客户端成功返回模拟确认;生产可能需要支付通道、消息回执或应用请求日志共同判定。不能要求损坏的数据库自己证明所有曾经确认的事实。
还要定义不可自动合并的情况。如果旧主和新主各接受不同订单,双方都有合法分录和余额,单边核对都能通过,但全局已经分叉。恢复流程必须先控制写入权,再决定数据如何修复。把两个库的行机械并集导入,可能重复支付或破坏依赖,属于新的业务变更,不能冒充普通复制追平。
本章练习
为一个库存服务写三条恢复后必须成立的关系,并列出两种关系都成立但客户仍可能受损的情况。再为RTO写清开始事件和结束事件。答案不能只写“恢复成功”或“服务正常”,应能由日志、请求和业务数据共同检验。完成这一步,后面每条备份命令才有明确的交付对象。