方案评审常变成两种东西:汇报(讲我们做了什么)或挑刺会(各说各的偏好),两者都不产生决策。有效的评审只有一个产出:这个方案在什么条件下成立、在什么条件下该被否掉。
1. 评审要回答的四个问题
| 问题 | 不回答的后果 | 谁最关心 |
|---|---|---|
| 要解决什么问题 | 讨论对象漂移 | 所有人 |
| 为什么这样解 | 只能争偏好,无法比较 | 架构负责人 |
| 代价是什么 | 上线后才发现复杂度与运维负担 | 运维、后续维护者 |
| 怎么验证 | 无法判断成败,只能"上线观察" | 质量、产品 |
四个问题里**"为什么不是别的"最容易缺席**:只讲选定方案时,评审只能判断"听起来对不对",判断不了"是不是当前约束下最好的"。任何一个问题空着,这场评审就还没有可决策的内容。
2. 方案文档的最小结构
| 章节 | 写什么 | 常见缺失 |
|---|---|---|
| 背景与目标 | 现象、影响面、可度量的结果 | 只写现象,不写达成标准 |
| 约束与非目标 | 硬约束清单 + 明确不解决的问题 | 非目标缺失 |
| 候选方案对比 | 至少两个方案 + 同一组维度 | 只写一个方案 |
| 选定方案与理由 | 选它、不选别的具体原因 | 理由写成"更优雅" |
| 风险与回滚 | 会发生的 + 可能发生的 + 退路 | 风险写"暂无" |
| 验证方式 | 上线前后如何判断成立 | 写"上线后观察" |
# 方案:订单查询读路径改造
1 背景与目标:列表 P99 随数据量增长,投诉集中在月末
目标:P99 降到原量级一半以内(按上线前一周基线核对),错误率不上升
2 约束与非目标:不改主键;两周内上线;无专职 DBA
非目标:不做历史归档,不改写路径,不动对账链路
3 候选方案(见对比表) 4 选定方案与理由
5 风险与回滚 6 验证方式
长度不是关键,结构完整度才是:两页六节齐全,胜过十页只讲方案。
3. "非目标"为什么必须写
不写非目标,评审就默认方案要解决"所有相关问题":每轮都有人提新场景,方案被迫膨胀,最后谁都不满意。
| 不写非目标 | 写了非目标 |
|---|---|
| "顺带把写入也优化一下吧" | 写入优化不在本次范围,另立方案 |
| "历史数据也一起迁了" | 本次只迁增量,历史归档单独排期 |
| "多租户场景也要考虑" | 当前单租户,多租户是下一阶段 |
- 本次不改写入路径:写入瓶颈独立存在,混在一起无法验证读优化效果
- 不做跨地域容灾:当前 RTO 不覆盖地域级故障,成本高于收益
- 不支持自定义排序:需求方仅 2 个,先用固定排序 + 白名单
非目标的作用是把边界变成可讨论的对象:不同意就在评审里争"要不要纳入范围",而不是在实现中途被动扩张。
4. 候选方案对比的表达方式
对比表的价值全在维度先对齐。维度不一致时,A 用"开发快"、B 用"扩展好"各自论证,永远比不出结论。
| 维度 | 方案 A:加读库 | 方案 B:查询拆分 + 缓存 | 方案 C:预聚合表 |
|---|---|---|---|
| 改动范围 | 中(配置 + 路由) | 大(改服务边界) | 小(加表 + 定时任务) |
| 一致性 | 复制延迟 | 缓存失效窗口 | 聚合延迟(分钟级) |
| 峰值承受 | 高(读扩容线性) | 中(看命中率) | 高(读聚合结果) |
| 运维复杂度 | 低 | 高(多一个缓存层) | 中(多一个调度任务) |
| 回滚难度 | 低(切回主库) | 高(边界已改) | 低(下线任务 + 切读) |
| 适用前提 | 读多写少 | 有热点、命中率高 | 查询维度固定 |
打分不是必须,同一组维度逐项描述就已足够。硬打总分反而掩盖关键差异——某维度是一票否决时,总分没意义。
1. 维度在评审前对齐,不在评审中新增(新增说明前一版对比不完整)
2. 每格写"结论 + 一句依据",不写"较好/一般/较差"
3. 至少标出一个一票否决维度:一致性是硬伤时,总分不用算
5. 约束的完整列举
约束不是"困难",是不可谈判的前提。漏掉一条,方案就会在上线阶段撞墙。
| 约束类型 | 具体例子 | 漏掉的后果 |
|---|---|---|
| 团队能力 | 无人熟悉 Flink,只有两名后端 | 落地后无人维护 |
| 存量系统 | 老系统无接口,只能读库 | 集成方案作废 |
| 合规 | 数据不得出境、日志留存 6 个月 | 上线被拦下 |
| 时间 | 大促前两周冻结变更 | 排期不可行 |
| 预算 | 无新增中间件预算 | 选型被推翻 |
写全约束有个副作用:它会自动淘汰一些"看起来最好"的方案。这正是价值所在——文档阶段淘汰比实现阶段发现便宜得多。
6. 权衡的显性化
评审里最没用的是"这样更好",有价值的是**"用 X 换 Y"**。
| 弱表达 | 强表达(用 X 换 Y) |
|---|---|
| 缓存能提升性能 | 用一致性窗口(最长 60s)换读延迟下降,前提是详情允许轻微陈旧 |
| 拆服务更清晰 | 用多一跳与运维面换独立扩缩容,前提是能承担两个部署单元 |
| 加索引更快 | 用写放大与存储换查询延迟,前提是写入 QPS 低于阈值 |
| 异步化提升吞吐 | 用最终一致与重试复杂度换峰值吞吐,前提是下游幂等 |
"用 X 换 Y"有三重作用:强迫说出被放弃的东西;让方案可按"换的东西值不值"比较;天然带适用前提,而前提正是风险与回滚讨论的入口。
7. 风险清单与缓解措施
| 类别 | 判定标准 | 处理方式 |
|---|---|---|
| 会发生的 | 概率高、影响明确(如迁移期间双写延迟) | 必须有缓解措施 + 监控指标 |
| 可能发生的 | 概率低但影响大(如缓存雪崩) | 必须有预案 + 触发条件 |
| 极端但不可控 | 概率极低、影响灾难(如机房级故障) | 明确"接受",写清为什么接受 |
- 风险:迁移期双写导致短暂不一致
- 触发:双写延迟 > 200ms 或对账差异 > 阈值
- 缓解:双写失败降级单写 + 告警;每 5 分钟对账
- 兜底:一键切回单写,旧链路保留 7 天
"风险:暂无"是最危险的写法——不表示没有风险,只表示写的人没想;评审看到就该退回。
8. 回滚方案的设计
回滚不是"把代码回退",要回答三个问题:数据怎么退、开关怎么关、多久生效。
| 变更类型 | 回滚难点 | 设计要点 |
|---|---|---|
| 加字段/加表 | 新数据已写入,旧代码不认识 | 双写 + 忽略未知字段;旧版本必须能读新数据 |
| 改语义 | 新旧数据含义不同 | 加版本标记,按版本分支处理 |
| 数据迁移 | 已迁移的数据无法"反迁移" | 迁移前全量备份 + 可重放脚本 + 校验 |
| 开关类变更 | 关闭后仍有残留状态 | 开关要有"关闭后行为"的定义,不只是"停止新流量" |
回滚清单(上线前填):
1. 代码回退:流水线编号 ____,预计 ____ 分钟
2. 开关:开关名 ____,关闭后行为 ____
3. 数据:备份位置 ____,恢复耗时 ____;下游是否同步回退 ____
4. 判定点:出现什么信号就回滚(指标 + 阈值)
最后一格最关键:没有判定点的回滚等于没有回滚。争"要不要回滚"比回滚本身更耗时,判定点必须在上线前定好。
9. 评审会上的常见质疑与应答准备
| 质疑 | 真实指向 | 应答准备 |
|---|---|---|
| "这个方案会不会太复杂?" | 维护成本是否被低估 | 给出组件清单与日常运维动作,说明谁负责 |
| "为什么不直接用 X?" | 是否漏掉显而易见的方案 | 对比表里必须有 X,并说明淘汰依据 |
| "出问题怎么回滚?" | 风险控制 | 回滚清单 + 判定点 |
| "怎么知道它有效?" | 验证方式 | 上线前后同一指标对比 + 观察周期 |
| "这个是不是可以以后再做?" | 优先级与时机的判断 | 说明不做的时间成本(不是"很重要") |
应答的原则是给可核对的东西,而不是更强的语气:反问"你觉得哪里复杂"比"其实不复杂"有效得多。
10. 决策记录(ADR)
评审的产出不是会议纪要,是决策记录。会议纪要记"讨论了什么",ADR 记"决定了什么、为什么、什么条件下会推翻"。
# ADR-014 订单读路径采用预聚合表
- 状态:已接受(2026-11-09)
- 背景:读延迟随数据量增长,见方案文档第 1 节
- 决策:采用方案 C(预聚合表 + 定时刷新),放弃 A/B
- 理由:查询维度固定,可接受分钟级延迟;团队无缓存运维经验
- 代价:新增调度任务;聚合延迟最长 5 分钟
- 推翻条件:出现任意维度组合查询,或延迟要求进入秒级以下
"推翻条件"是 ADR 里最有价值的一栏:它给出这个决策的有效期,而不是让人当永久真理遵守。
11. 常见坑
| 坑 | 表现 | 改法 |
|---|---|---|
| 只讲方案不讲备选 | 评审无法比较 | 至少两个方案 + 同一组维度 |
| 把偏好当理由 | "更优雅""业界都在用" | 换成"用 X 换 Y" + 适用前提 |
| 风险写"暂无" | 上线后集中爆发 | 至少三类风险各一条 |
| 验证写"上线观察" | 无法判断成败 | 指标 + 阈值 + 观察周期 |
| 评审后无结论记录 | 下次重新吵一遍 | 当场出 ADR 草案,指定记录人 |
12. 排错:五种"评审开完仍然没结论"
| 现象 | 原因 | 处理 |
|---|---|---|
| 讨论很久但话题一直在变 | 目标没写清,各人理解不同 | 会前把"要解决什么问题"定成一句话 |
| 每个人都在讲自己方案的好 | 缺少统一对比维度 | 会前发对比表,维度先对齐 |
| 通过后上线仍翻车 | 风险与回滚没落到具体动作 | 风险条目带触发条件与兜底动作 |
| 同样的问题反复评审 | 没有 ADR | 每次评审产出 ADR,含推翻条件 |
13. 边界:评审不解决什么
| 问题 | 评审能给答案吗 | 该用什么 |
|---|---|---|
| 这个方案能不能扛住真实流量 | 不能 | 压测与容量规划 |
| 需求本身是否值得做 | 部分(只判断可行性) | 产品决策与成本收益分析 |
| 实现细节是否正确 | 不能 | 代码评审与测试 |
一句话:方案评审判断"该不该这么做",不判断"做得对不对"。 后者属于实现阶段的验证。
14. 自测
- 评审必须回答的四个问题是什么?缺了"为什么不是别的"会导致什么?
- 为什么"非目标"必须写成可反驳的句子?
- 对比表为什么要求"维度先对齐"?维度在评审中新增说明什么?
- 把"缓存能提升性能"改写成"用 X 换 Y"并补上适用前提。
- 回滚清单里最容易被漏掉又最关键的一格是什么?
- ADR 里的"推翻条件"解决了什么问题?