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

调试实战

重试就能过的问题 — 要跑多少次才能说已修好

在 TT Lab 中继续学习

目标

通过试验测出间歇性失败的发生率,并给估计值加上区间。从试验记录中提取只在失败的那次运行中才成立的条件,拉开间隔重新测量,确认观测会让症状消失。最后计算要说已经修好需要跑多少次,并实际跑完这个次数来证明。

为什么重要

“我再试了一下,又好了”听起来像是结束调查的一句话,其实是两条数据:一次失败,一次成功。接下来要做的是多跑几次,做出分数。 间歇性失败之所以危险,是因为它会让人用一次成功来判断是否修好。一个失败率为 10% 的问题,在没有修复的情况下跑一次就通过的概率是 90%。所以必须先计算“需要连续通过多少次”。 本实验中难的不是循环,而是判定的依据。如果只写比例而没有试验次数,就分不清 20 次中的 2 次和 200 次中的 20 次;如果不当场留下失败那次运行的证据,之后就没有可以询问的对象。 评分器不会被随机性左右。评分器会让你的工具去运行它做出的确定性目标(恰好每五次失败一次的命令)并核对数字,而你留下的记录和计算,评分器会用同样的公式重新计算后进行比对。

步骤

  1. 创建并运行 /root/flaky/gen_flaky.py,生成 /root/flaky/job.py。
  2. 用 /root/flaky/measure.py 把 job.py 运行 200 次以上,并在 /root/flaky/rate.json 中写下失败率和威尔逊区间。
  3. 用 /root/flaky/plan.py 和 /root/flaky/runs_needed.json 求出各个置信水平所需的连续通过次数。
  4. 用 /root/flaky/collect.py 在每次试验中留下证据,生成 /root/flaky/facts.jsonl,并把只在失败时才成立的内容写入 /root/flaky/only_when_fail.json。
  5. 拉开试验之间的间隔重新测量,把两次测量并排写入 /root/flaky/observer.json。
  6. 通过计算,在 /root/flaky/policy.json 中确定重试预算、隔离标准,以及判定为绿色所需的连续通过次数。
  7. 把修复版按计算出的次数运行,并在 /root/flaky/proof.json 中留下证据。
  8. 用 /root/flaky/summary.json 和 /root/flaky/flaky_report.md 分四节进行报告。

参考

拿到一个间歇性失败的任务

创建并运行 /root/flaky/gen_flaky.py,生成 /root/flaky/job.py。然后手动多运行几次 job.py,亲眼看到它时好时坏。

把这个脚本原样保存并运行即可。连续运行生成的 job.py 十次左右,退出码为 0 的运行和退出码为 3 的运行会混杂出现。先不要统计多少次失败一次——那是第 2 步的事。

把“偶尔”变成分数

创建 /root/flaky/measure.py,把 job.py 运行 200 次以上,并在 /root/flaky/rate.json 中写入 command、trials、failures、rate、ci_low、ci_high、gap_ms。区间是 95% 的威尔逊得分区间。

失败按退出码不为 0 来计数。只写比例,就分不清 20 次中的 2 次和 200 次中的 20 次,所以要把试验次数一并留下。威尔逊区间的两个公式在参考一节中——即使失败为 0 次宽度也不会变成 0,这就是使用这个区间的理由。

要跑多少次才能说已经修好

创建 /root/flaky/plan.py,并在 /root/flaky/runs_needed.json 中写入 observed_rate、包含置信水平 0.9、0.95、0.99 各自的 runs 的 table,以及 chosen(0.95)。

在没有修复的情况下,n 次全部通过的概率是 (1-p) 的 n 次方。把这个概率压到 1-c 以下的最小 n 就是答案,取对数就能一行算出。别忘了向上取整——不能跑小数次。

在失败的当场留下证据

用 /root/flaky/collect.py 运行 60 次以上,生成 /root/flaky/facts.jsonl,并在 /root/flaky/only_when_fail.json 中写入 trials、failures、exit_codes_on_failure、exit_codes_on_success、token、failure_trials。token 是存在于所有失败的标准错误中、且不存在于任何成功中的词。

间歇性失败无法再次召唤出来。每次试验都要把退出码和标准错误写在那一行里,之后才有可以询问的对象。用眼睛浏览失败记录,token 马上就能看到——它是一个大写的词。成功记录中不能出现这个词,它才算 token。

观测会让症状消失

把试验之间的间隔拉开到 2000 毫秒以上,重新测量 20 次以上,并在 /root/flaky/observer.json 中写入 fast 和 slow 两次测量(trials、failures、rate、gap_ms)以及 changed。放慢测量时,失败必须是 0 次。

加上日志并一步一步地运行,症状消失是常有的事。因为依赖时序的条件,一旦变慢就不成立了。症状消失这件事本身就是线索——它意味着原因在速度或顺序上。用 measure.py 的 --gap-ms 就能造出同样的效果。

计算重试预算和隔离标准

在 /root/flaky/policy.json 中写入 observed_rate、confidence(0.95)、green_runs_required、retry_budget、quarantine_after。retry_budget 是满足 p^(r+1) <= 0.01 的最小 r,quarantine_after 是大于等于 1 的整数。

重试并不是错误的应对,但次数必须有依据。知道失败率,就能计算“全部失败的概率”,预算就是从这里得出的。如果不做隔离,人们会学会忽视失败的习惯,而这种习惯同样会用在真正的失败上。

连同依据一起主张“已经修好”

把修复版(--fixed)运行第 3 步所计算次数以上,并在 /root/flaky/proof.json 中写入 command、required、trials、failures。failures 必须为 0,trials 必须大于等于 required。

通过一次不是证据。因为对失败率为 p 的问题,在没有修复的情况下通过的概率本来就是 1-p。把第 3 步的 chosen.runs 原样取来,跑那么多次,并把记录存成文件。评分器会亲自再运行同样的命令。

把概率汇总成一页纸进行报告

在 /root/flaky/summary.json 中写入 trials、failures、rate、ci_low、ci_high、token、green_runs_required、proof_trials、observer_changed,并在 /root/flaky/flaky_report.md 中分四节进行报告:## 얼마나 자주인가、## 실패할 때만 참인 것、## 관측이 바꾼 것、## 고쳤다고 말하려면(韩文,依次意为“多久发生一次”“仅在失败时成立的事实”“观测改变了什么”“如何才能说已经修好”)。

报告的价值不在结论,而在数字。试验次数和失败次数、区间、所需的连续通过次数都要写下来。加上观测后症状消失这件事也是线索,所以一并写下。