2449 字
约 8 分钟
14
S27-A20 · 对照组:你怎么证明"是它带来的改善"

一次"加了缓存"的实测显示 P99 从 210ms 降到 90ms,团队据此认为"缓存有效"。而两周后另一个团队在同一系统上只测出 30ms 的改善——因为那两周里还上了"索引优化",而它贡献了一半。

实测最常见的逻辑错误不是"测错数",而是"没有对照组,于是把"所有变化"归给"我做的那一个变化"。这个判断有点反直觉:测量精度提高 10% 的收益,远小于"加一个对照组"的收益。因为前者只是让数字更准,后者决定"这个数字指向谁"。
而对照组的设计比测量本身更决定结论的有效性。


1. 三种"对照"的强度

设计 强度
前后对比(同一环境,改前/改后) 弱(时间带来的变化混进来)
A/B(同时跑两种,只差一个变量) 强
交替(A/B 交替跑,摊平漂移) 最强(成本高)

第一项最常用也最弱:"改之前"与"改之后"之间,还发生了很多事第一项的"弱"不在"不严谨",而在"它无法排除":哪怕你确信"只有我改了",也无法排除"数据增长"与"流量变化"——而这两项在真实系统里永远在变。
(其它发布、数据增长、外部流量变化)。

第二项是"能证明因果"的最低要求:两组之间只差"那一个变量"。

2. 前后对比为什么不可靠

混入因素 影响
同时上线的其它改动 归因错误
数据量增长 前后不可比
外部流量结构变化 负载不同
环境漂移(温度、缓存) 噪声变化

第一项的"叙事偏差"值得单独说:人倾向于把好结果归给自己熟悉的那个改动,而"最有名"往往等于"写代码的人最有话语权",于是归因变成了权力问题。
第一项在"密集发布"的团队里几乎必然发生:两周内合了 20 个 PR,而"改善"被归给了最有名的那一个。

第三项在"大促前后"最明显:改前是平常流量,改后是大促流量,于是"变慢"可能完全是流量导致的。第三项还有一个"时序"陷阱:改动的上线时间恰好与"某季节性事件"重合(月末结算、开学、节假日),于是"改前 vs 改后"实际是"淡季 vs 旺季"。

3. A/B 的三种落地方式

方式 说明
双跑(同一请求跑两条路径) 精确,但成本翻倍
灰度分流(按用户/流量) 真实,但需观测对齐
影子流量(复制请求到新路径) 真实且安全,需额外容量

第二项的"10%"不是随便取的:太小(1%)样本不足,太大(50%)风险过高。经验值是"5%–20%",且要跑够"至少覆盖一个业务周期"(如完整一天)。
第二项最实用:10% 流量走新路径、90% 走旧路径,同时对比两组指标。它需要"两组流量的构成相似"(随机分流,不是按用户 ID 尾号)。

第三项在"新路径有风险"时用:复制真实请求到新实现,只比对结果不返回。代价是双倍计算。第三项的独特价值是"能比对结果":它不仅比"快慢",还能比"新旧实现的输出是否一致"——于是它能同时发现"性能改善"与"语义偏差"。

// 灰度分流的三个前提(缺一项结论就不可靠)
1 随机分流(按 hash(uid) 取模,而不是"先来的走 A")
2 两组同时段对比(不是"上周 vs 本周")
3 指标口径一致(同一采集点、同一分位)

"分流"必须按"不可伪造的键"做,否则两组会不可比:

// 三种分流键的可用性
hash(uid) % 100 < 10     → 同一用户始终在 B 组(体验一致,样本独立)推荐
hash(request_id) % 100   → 同一用户会在两组间跳,指标被"平均"(不可用)
按时间切(前 10 分钟 B)  → 与"流量结构随时间变化"混淆(不可用)
// 判据:分流要"与人绑定",而不是"与请求绑定"

4. "只差一个变量"怎么保证

做法 效果
一次只改一个 需要纪律
特性开关(可分别开关) 最有效
分支对比(两套部署) 强,但成本高

第二项的落地细节是"开关的粒度与"可观测"对齐":一个开关应当对应"一个能被单独观测的改动",否则"开了但分不清谁起作用"。
第二项是"工程化的对照":把每个改动放在一个独立开关后面,于是"只开 A"与"只开 B"可以分别测。它把"对照"从"排期问题"变成"配置问题"。

代价是"代码里多分支",所以"开关要有到期时间"(与前面的开关台账一致)。

5. 怎么报"贡献度"

内容 必须写
对照组设置 谁与谁比
单变量是什么 唯一差异
各自绝对值 不只有"改善%"
是否有交互 两者一起上是否更差

第四项最容易被忽略:两个优化同时上,可能互相抵消第四项的典型例子:"加缓存"减少了数据库压力,于是"加索引"的收益看起来变小——两者都有效,但"合并收益"远小于"各自收益之和"。
(如"加缓存"与"改批量"都改了同一段路径)。所以"分别测"之后还要"合起来测一次"。

// 一次合格的归因报告(四行)
对照:A 组(旧路径,90% 流量)vs B 组(仅开缓存,10% 流量)
单变量:只开缓存开关,索引与批量均未改
结果:P99 210ms → 148ms(-30%),P50 基本不变
交互:A+B 同开时 P99 = 132ms(叠加有效,无抵消)

"混入因素"可以先用"排除法"清一遍,比"加对照"更便宜:

// 四问排除(每条一分钟)
1 同期有没有别的发布?       有 → 归因不可靠
2 数据量有没有变?           变 → 前后不可比
3 流量结构有没有变(渠道/时段)?变 → 同上
4 环境有没有变(机器/参数)?    变 → 噪声变了
// 任一为"是" → 必须做 A/B,而不是前后对比

6. 排错:五种"归因错误"

现象 成因 处理方向
改善比预期大很多 混入其它改动 用 A/B 重测
别人复现不出 环境/时段不同 固定对照
两项优化"各有效"但合起来更差 交互 加"合起来测一次"
只有"改善%"没有绝对值 报告不全 补绝对值
小样本上"显著" 噪声 先测噪声幅度

第四项(只有改善%没有绝对值)会让"结论强度"不可判断:"改善 30%"可能是 210→147ms,也可能是 3→2.1ms——而后者对体验毫无意义。
第五项与"样本量"是同一个问题:差异小于噪声时,"显著改善"只是巧合。

最后一个"归因记录"的模板:把"对照设置"写进实测记录的固定字段,于是"能不能信"变成"读一下就知道",而不是"问作者"。

// 实测记录里的归因块(四行)
control: "A 组旧路径 90% vs B 组仅开缓存 10%(按 hash(uid) 分流)"
single_variable: "只有 cache.enabled 不同"
result: { p99_a: 210, p99_b: 148, p50_a: 62, p50_b: 60 }
interaction: "A+B 同开 p99=132(叠加,无抵消)"

7. 边界:什么情况前后对比就够

场景 为什么 做法
改动极大(10 倍级) 混入因素影响小 前后对比
独立环境(无其它发布) 无混入 前后对比
只判断"方向"不判断"幅度" 不需要精确 前后对比
一次性验证(不对外报数) 结论不被引用 前后对比

判断口径一句话:这个结论会不会被别人拿去支撑一个决定。最后一条与"团队"有关的边界:对照组是"工程纪律",而不是"额外工作"。做法可以很轻:上线时"只开一半流量",观察一小时再全量——这个动作顺便就产生了对照。
会,就必须有对照(A/B 或交替);只自己看一眼,前后对比可接受。


8. 自测

  1. 三种对照设计的强度差别是什么?为什么"前后对比"最弱?
  2. "前后对比"会混入哪四类因素?为什么密集发布时几乎必然出错?
  3. A/B 的三种落地方式里,哪一种最实用?它的三个前提是什么?
  4. "特性开关"为什么能把"对照"从排期问题变成配置问题?它的代价是什么?
  5. 为什么"两项优化分别有效"还要"合起来测一次"?
  6. 判断"是否需要对照"的那个问题是什么?
S27-A20 · 对照组:你怎么证明"是它带来的改善"
https://www.clxhxhhr.top/posts/6568/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。