2476 字
约 8 分钟
18
S03-A25 · 可逆性的设计:这个决定错了要多久能退

裁掉一个字段的决策做了两天,实施花了一小时,回滚花了两周——因为数据已经被清理,下游有三个系统在读它。事后复盘时有人说"当时评估过风险",但评估的是"做不做得到",没有人评估"退不退得回来"。

技术决策的质量通常按"对不对"来衡量,但在不确定的环境里,"错了能不能改"往往比"对不对"更重要。可逆的错决定成本很低,不可逆的对决定也可能因为环境变化而变成灾难。


1. 可逆性的四个等级

等级 回滚方式 时间成本
R1 配置级 改配置重启 分钟
R2 代码级 回滚发布 十分钟到小时
R3 数据级 从备份或双写恢复 小时到天
R4 契约级 需要通知并协调所有相关方 天到周

关键区别在"谁必须参与回滚":R1 与 R2 由自己团队决定,R3 需要数据可用,R4 需要外部配合。级别的判定依据不是技术难度,而是参与方数量。

等级还会随时间漂移:一个当时是 R2 的决定,半年后会变成 R4——因为依赖它的系统变多了。所以可逆性不是决策时的一个静态属性,而是需要定期重估的状态;决策时是 R2,文档里写着 R2,实际已经 R4,是最常见的一类误判。

第三、四级的分界最容易被误判:删掉一个内部接口看起来是 R2,但如果它被别的团队调用,就是 R4。

2. 不可逆动作的前置投入

不可逆动作 前置投入 收益
删字段 先停止写入并观察一个周期 确认无人使用
删接口 先加弃用告警与日志 拿到调用方清单
改语义 新增参数/字段,旧路径保留 双跑对比
数据清理 先归档再做删除 可从归档回填
迁移库 双写与双向校验 可切回

前置投入的收益可以用一句话概括:把"不可逆动作"变成"可逆动作 + 一段观察期"。观察期的成本通常是几天到两周,而不可逆动作失败的成本是数周到数月。

第二项是成本最低的:弃用告警只需要在接口入口记一条日志,跑两周就能拿到真实的调用方清单,而这份清单是不可逆动作的必要前提。

// 弃用的三个动作,顺序不能反
1. 加告警:记录调用方标识与调用量,保留 2 周
2. 通知:按清单逐一确认迁移计划
3. 下线:调用量归零后保留 1 周再删除
// 直接跳到第 3 步,等于赌"没人用"

3. 双写与灰度是可逆性的载体

手段 提供的可逆性 代价
双写 可切回,且可对比 两套写入的一致性处理
灰度 影响面可控 需要分流能力
特性开关 秒级关闭 开关本身需要治理
影子(只读) 验证读路径 下游压力翻倍

第三项最便宜,也最容易腐化:开关数量增长后,没人知道哪个开关还是活的。可逆性因此变成"名义上可逆"——关闭开关会引发另一处未知行为。

开关的另一种腐化形态是嵌套:if (flagA && flagB) 这样的组合出现后,实际生效路径变成四种,而没有人验证过四种组合都正确。

开关治理的最小要求是三条:有归属人、有到期时间、有定期清理。缺一条,它就只是"看起来能关"。

4. 回滚预案要测

预案要素 检查方式
回滚命令存在 演练执行一次
回滚所需权限可用 非本人也能执行
回滚时间在窗口内 计时
数据能回到一致状态 校验比对
回滚后监控正常 观察一个周期

演练的产出不只是一次计时,更是一份"缺口清单":

// 一次回滚演练应产出的五项记录
1. 实际耗时:__ 分钟(对比窗口 __ 分钟)
2. 卡住的位置:__(缺权限/缺脚本/缺数据)
3. 需要补充的权限授予:__
4. 校验手段是否足够:__(能否证明回到一致状态)
5. 下次演练的负责人与时间:__

第 2 项通常是最大的收获:多数团队的第一次演练都会在某一步卡住,而这一步就是真实事故中会多花一小时的环节。

第二项在事故现场最致命:预案在,权限只在个别人手里,而那个人不在。可逆性不是文档属性,是可用性属性。

// 回滚演练的最小脚本形态:每一步都留时间戳
start=$(date +%s)
./scripts/rollback.sh --to "$PREV_VERSION"
echo "rollback $(( $(date +%s) - start ))s"
./scripts/verify.sh --expect "$PREV_VERSION"
// 只有"执行 + 校验 + 计时"三步都跑过,预案才算存在

5. 什么时候该主动放弃可逆性

场景 为什么 前提
可逆性成本高于错误代价 双写维护成本 > 一次重建 数据可重建
短生命周期系统 赶不上一次回滚 影响面小
有强外部约束(合规删除) 必须不可逆 流程与记录到位
试验性功能 结果本就未知 有隔离与开关

还有一个与决策流程有关的观察:可逆性越高的方案,越应该快速决定;可逆性越低的方案,越应该慢下来。现实中常常相反——容易改的方案被反复讨论,难改的方案反而因为"反正要慎重"而匆匆拍板。

第一项的判断方式是算一笔账:为了保住可逆性,你要长期维护两套路径。如果这个维护成本已经超过"出错后重做的成本",那么可逆性是负收益。

双写也不是万能的,它自己会产生两个问题:一致性与维护成本。前者的处理方式是"以一侧为准 + 定时对账",后者的处理方式是设一个明确的结束时间。

// 双写的三个约束,缺一个它就会长期挂着
1. 主从关系明确:以哪一侧为准,另一侧只做校验
2. 对账机制:每日比对差异,差异率与阈值告警
3. 退出条件:校验连续 N 天为 0,则停止旧路径写入
// 缺第 3 条的双写会一直存在,成为永久的维护负担

6. 排错:五种"以为能退实际退不回"

现象 成因 处理方向
回滚后数据不一致 只有单向写入 补双写或补对账
回滚导致下游报错 下游已按新契约改动 契约类变更需协调
关开关没效果 开关已被硬编码覆盖 开关治理与清理
回滚超时 从未计时 演练并记录
回滚权限不足 权限集中在个别人 至少两人可用

第一项最典型:切换到新路径后只写新表,回滚时新表的数据回不去。所以可逆性的前提是"数据能双向流动",而不只是"代码能切回"。

7. 边界:什么情况不必评估可逆性

场景 为什么 做法
纯新增(无人依赖) 删掉即可 直接做
本地开发环境改动 无外部影响 不必评估
已有完整数据重建能力 错误可重放 评估重建时间即可
极小内部工具 影响面为零 不评估
// 决策单上的一行:把可逆性写成可查的字段
reversibility:
  level: R3
  how: "从日备恢复被删数据,需 4 小时"
  who: "DBA 张(备选:李)"
  window: "4h,业务可接受停机 6h"
  reevaluate_at: 2027-03      // 依赖增长后可能已降级

判断口径一句话:错了之后,需要几个团队配合才能退回来。零个,可逆性由自己掌握;一个以上,就必须在决策时把回滚路径写下来。


8. 自测

  1. 可逆性四个等级的判定依据是什么?为什么不是技术难度?
  2. 为什么"删内部接口"可能是 R4 而不是 R2?判断依据是什么?
  3. 弃用告警为什么是不可逆动作的必要前提?三个动作的正确顺序是什么?
  4. 特性开关为什么会从"可逆性载体"退化为"名义上可逆"?开关治理的最小三条要求是什么?
  5. 回滚预案的五个要素中,哪一项在事故现场最致命?为什么?
  6. "可逆性是负收益"的判断方式是什么?它的前提条件是什么?
S03-A25 · 可逆性的设计:这个决定错了要多久能退
https://www.clxhxhhr.top/posts/6414/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。