迁移模式与双向恢复:代码可回滚不证明数据可逆
切到新库后已写入新字段和新业务状态,再把流量指回旧库——旧服务既看不到新写也无法解释新状态。本页把迁移写成带守卫的状态机,定义写主权、水位与双向恢复的真实边界。快照与版本守卫的机制见CDC、事件和投影;expand-contract 的步骤见零停机 schema 迁移,本页讲系统级迁移的切换与恢复。
迁移不是一次切路由
Strangler 模式逐步将调用从旧实现路由到新实现;关键在边界适配、行为兼容、流量观测和退役条件。迁移期路由层本身会成为新故障点。分阶段拆分可先在单一代码库明确模块 API,再搬移数据所有权,之后分离部署。把数据所有权最后考虑会留下共享库耦合,使服务无法独立演进。
将旧积分服务迁到新库,阶段为兼容准备、快照回填、增量追齐、影子比对、受控读切换、写主权切换和旧路径退出。每阶段有进入/退出条件、当前权威源和恢复者。所谓双向恢复不是允许两边无约束同时写,而是明确切换前后数据如何返回或前向修复。双写、新旧读比较和回填会产生不一致窗口,需要稳定身份、差异队列、幂等修复和切换水位。
一致水位与旧写拒绝
回填基于快照版本,实时变更带顺序水位,目标按对象版本拒旧覆盖新;删除同样保留墓碑。切写时使用明确epoch/路由代际,旧worker和旧客户端即使恢复也不能继续在旧域承诺成功。
迁移2TiB、净复制100MiB/秒,理论约5.83小时;若同时产生80MiB/秒需追增量,净追赶仅20MiB/秒,时间显著拉长。增大并发前先看主库I/O、复制滞后与前台SLO,避免回填本身制造事故。
将迁移切换写成状态机
教学状态可为OLD_AUTHORITY→COPYING→SHADOW_READ→NEW_AUTHORITY→RETIRED。前四个状态都要写清写入唯一主权、读路径、允许重试和中断后重启方式。COPYING失败可从快照水位继续,不代表可丢弃已消费增量;SHADOW_READ仅比对,不能将影子返回值偷偷作为业务裁决;NEW_AUTHORITY后回退必须先证明旧域已获得全部必要新语义与新写。
设切写窗口要求30秒,事前增量落后10万条,目标净追赶2000/s,则仅清积压需50秒,已不满足窗口。正确动作是切换前先追齐到允许阈值,或增加经过验证的净追赶能力、缩小批次并协调暂停写;不是在计划里直接写"停写30秒切换"。净能力必须扣除新到达量和前台SLO需要的余量。
校验分为结构与业务两层:schema兼容、业务键唯一、对象版本和墓碑;再核账户余额/流水汇总、订单终态及Outbox是否重复恢复。影子查询中数据持续变化时,应采用同水位比较或解释合法时间差,不能把所有差异自动修成旧值。
两种恢复路线
方案A保留兼容双写与反向同步,回退灵活但冲突、循环与维护成本高;方案B在某切点后禁止回退旧写,仅支持前向修复,协议更简单但需更强验证和备份。选择由新语义可否映回、允许中间态与恢复时限决定,不能口头承诺"随时回滚"。代码可回滚不证明数据可逆:新写、schema及已发生副作用可能不可映回,需显式逆向协议或前向修复。
渐进替换为小步迁移提供思路,但本例的写主权、账本与水位仍需自己证明。演练在每阶段中断、重启并重放旧消息,检查数据、唯一业务键与余额汇总;只验证路由404/200不足以验收。
迁移责任矩阵与回滚证据
本页前面把迁移写成状态机,但每个状态下"谁有权写、谁负责恢复"没有落到组织上。责任矩阵把状态机的每个状态映射到三个字段:唯一写主权方、恢复负责人、观测信号;再加上第四列——该状态允许的回滚证据类型。
矩阵的骨架:OLD_AUTHORITY 旧系统写、旧团队恢复、观测写入成功率;COPYING 旧系统仍写、迁移团队负责追赶、观测积压与复制滞后;SHADOW_READ 旧系统写、迁移团队看差异率;NEW_AUTHORITY 新系统写、新团队恢复、观测新写入成功率;RETIRED 新系统写、旧路由流量归零。五个状态写主权方只有两个值(old/new),不可能出现双方都写——出现即违规。
回滚证据分四类:代码回退(旧二进制可运行)、流量回退(路由可切回)、数据回退(新数据能无损映射回旧模型)、前向修复(无法回退时的修复协议)。评审常见的错误是把前两类当作"可以随时回滚":t1 新写产生了旧模型无法表达的状态后,流量切回只是让旧服务读到看不懂的数据。矩阵的回滚列必须显式写"data: 不支持",并给出前向修复的责任人与步骤。
反例:迁移团队在 NEW_AUTHORITY 阶段发现差异率上升,直接切回 SHADOW_READ 路由。代码与流量确实回退了,但期间已产生的新语义(例如新的退款状态)旧模型无法表示,回退后这些订单在旧系统里显示为"未知状态"。正确动作是:要么先阻止新语义写入(回退到受限状态),要么承认不可回退、走前向修复,而不是宣称"回滚成功"。
执行 python3 labs/evolution_models.py migration_responsibility:预期输出五状态矩阵,rollback_evidence.data=false,以及规则"回滚需要代码/流量/数据或前向修复四类证据,不能全部假定"。脚本只做责任映射,不执行真实迁移。
两个合理选择:A 每个状态都配双向同步以支持随时回滚,灵活但双写冲突、循环与维护成本高;B 明确切点后只支持前向修复,协议简单但要求更强的校验与备份。本例选 B,并把"数据不可回退"写进迁移计划的第一页,而不是附录。
架构师变化条件
切写窗口收紧到 10 秒:事前落后必须压到 2000 条以内或净追赶提到 1 万/秒,迁移计划里写清哪个数字先验证。旧系统无法新增 epoch 字段时,旧写拒绝只能靠路由层与资源端守卫的组合,需证明守卫覆盖全部写入口。若迁移中途合规要求"可随时回滚",要评估反向同步成本并把不可逆边界显式推迟——每次变化重写状态机守卫与最坏失败点演练清单。
闭卷验收
本页 8 道闭卷题收进了下方自测面板:先回忆,再展开参考答案与评分点,最后给三档自评。