TT Lab
开始
学习 学习路径 课程

压力测试

“慢了 8%”这句话里少了噪声的大小

在 TT Lab 中继续学习

一句话总结

两次运行之间的差异是不是回归,不能只凭差异的大小来定。必须先测量什么都没改变时产生的差异,也就是噪声的大小。

为什么需要它

有一个团队把性能测试接进了发布流水线。规则很简单——如果 p50 比上一次发布慢了 5% 以上,就拦住发布。看起来合理,而且第一周确实拦了三次。

问题在于,这三次中有两次是误报。被回滚的变更,一个只是删掉了一行日志,另一个只是修改了注释。后来用没有改变任何东西的提交,把同样的测试运行五次,p50 竟然拉开到了 7%。阈值 5% 比噪声还小。

误报本身就是成本,但更大的伤害是消耗信任。如果门禁经常空转,人们就会学着把门禁关掉,或者去点“重试”。而当真正的回归经过时,没有人会停下来。

反过来,如果把阈值设得太宽松,门禁什么也拦不住,却给人一种在拦的错觉。这两种失败出自同一个根源——确定阈值时没有测量噪声。测量噪声的成本,只是把同样的测试多运行几次的时间,而这段时间,总是比调查一个被错误回滚的变更所花的时间短。

工作原理

判定需要三样东西。测什么,把多次测得的值怎样合并成一个,以及把这一个与什么比较。

测什么。平均值会被尾部拖着走,最大值则取决于一次事故。p50 稳定,但看不到尾部,p99 能看到尾部,但样本少时会剧烈抖动。门禁最好用波动小的值,而尾部另行监控。

把多次测得的值怎样合并。如果只测一次就结束,就无从得知那一次是不是一次倒霉的运行。所以要在同样的条件下运行多次,并使用这些值的中位数。中位数不会被某一次异常的运行拖走。

与什么比较。这是关键所在。如果把阈值固定成“5%”之类的,就没有人知道这个数字是从哪里来的。取而代之的是,把基线重复测量得到的各值的离散程度,直接用作阈值。最简单的形式是最大值减去最小值,想更稳健,就用中位数绝对偏差的若干倍。无论哪种,重要的是阈值是当时在那台机器上实际测得的值。

这条规则有一个自然的性质。增加重复次数,噪声的估计会更好,就能捕获更小的回归;如果把重复减少到一次,阈值就变成 0,任何差异都会被报告为回归。所以“要运行几次”,是用时间换灵敏度的决定,而不是偏好。

在现场相遇的样子

最常见的失败,是基线只测一次。把昨天发布的一个测量值当作基线,与今天的一个值比较,就根本没有可用来确定阈值的材料。这时人们会拿出固定的百分比,而那个数字通常是在会议室里定下的。

第二种是把条件的变化读成回归。如果基线是在清闲的凌晨测的,而新的测量是在有其他任务同时运行的白天测的,这两个数字从一开始就不是可以比较的一对。必须在同一台机器、同一个时段、同样的负载形态下交替测量,噪声的定义才能成立。

第三种是基线逐渐过时。如果把基线测一次留着,几个月里都只与那个值比较,这期间内核会升级,底层库会变化,机器会被更换。那时的噪声和现在的噪声是不同的值,与过时基线的差异不是回归,而是岁月。最安全的做法是,基线要与新的测量在同一天、同一台机器上一起重新测量。

第四种是不依规则,凭眼睛判定。把两张柱状图并排放在一起说“确实变慢了”的那一刻,这个判定就成了无法复现的东西。下一个人看着同样的数据得出不同的结论,也没有人能说他错了。如果把规则写成脚本,判定就会留下记录,以后发现阈值错了,该修正什么也就清楚了。而且这个脚本必须给出退出码,而不是给人读的报告——因为流水线能读,才能真正拦住什么。

第五种是错过的不是回归,而是改善。如果门禁只朝一个方向设置,性能变好了也无从知道。如果变好一侧的变化远远超过噪声,也必须记录下来——通常是好消息,但偶尔是测试开始跳过工作的信号。

下一项实验要做什么

启动一个在基础延迟上叠加了可复现抖动的对象,在同样的条件下测量五次,先求出噪声的大小。然后分别把延迟增加 5% 的版本和增加 50% 的版本各测五次,建立按五次运行的中位数相互比较的规则,判定哪一个超出了噪声。从基线的五次运行中,挑出最快的和最慢的,模拟一次性的比较,就会用数字看到:即使是同样的代码,门禁也会报告回归。最后制作一个接收基线目录和新测量目录、给出退出码的门禁脚本,并让评分器用它自己制作的三组输入来运行这个门禁,确认通过和失败两种情况。