11.1 flake 治理:重试不是答案而是症状


11.1 flake 治理:重试不是答案而是症状

本节摘要:本节治理浏览器智能体系统里最磨人的失败形态:flake——时好时坏的用例与任务。先给定义与代价:同一输入在不同运行里结果不同,它比稳定失败更贵,因为它腐蚀信号(分不清真缺陷还是噪声,告警信任崩塌)。根源三分类:时序类(等待不足、竞态——断言跑在渲染前、双击与防抖的赛跑)、数据类(测试数据漂移、共享状态——账号互踢、租户数据被别的任务改掉)、环境类(网络抖动、资源竞争、依赖服务不稳——池过载、镜像里的字体缺失)。然后是本章的立场口号:重试不是答案而是症状——给 flake 加重试等于给发烧加退烧药,烧退了病还在;重试只对「已分类的瞬态失败」合法(第 6.2 节的分类器先工作),未分类的 flake 必须先归因再处置。度量上把稳定率升为一等指标:单用例通过率、隔离运行对照全量运行(差异即污染信号)、flake 分。治理流程五步闭环,配分层门禁挡新 flake。双语实现稳定率统计。

学习目标

  • 说出 flake 比稳定失败更贵的原因(信号腐蚀)。
  • 用三分类(时序、数据、环境)给 flake 归因并举出每类的典型指纹。
  • 复述重试的合法使用条件与「症状论」的含义。
  • 计算单用例稳定率、隔离对照差、flake 分。
  • 执行五步治理闭环与分层门禁。

一、flake 的定义与代价

flake 的严格定义:同一输入、不同运行、不同结果。它比稳定失败贵三个数量级,贵在信号层:

受害者 稳定失败 flake
开发者 修就是了 先花半小小时重现——还未必重现得了
CI 信号 红就是红 「再跑一次就好了」文化蔓延
告警系统 可信 信任崩塌,真警报被当噪声划掉

第 9.2 节提过自然语言用例的抖动率更高(示意值百分之一到百分之五),但 flake 不是 AI 用例的专利——纯 Playwright 一样有时序与数据类的抖动。治理对象是整个系统:测试、固化任务、Agent 任务,只要有时好时坏就在治理范围内。

二、根源三分类与典型指纹

类别 典型指纹 根因 治理方向
时序类 「再跑一次就过」概率高;日志显示断言时刻页面未就绪 等待不足、竞态 显式等待终态(第 6.1 节断言前等待);消竞态(幂等设计)
数据类 换账号或时段就变;失败信息显示数据不符 测试数据漂移、共享状态 数据自足(第 9.2 节用例自足纪律);按任务隔离账号(第 7.1 节)
环境类 集中在高峰时段;特定实例上高发 网络抖动、资源竞争、依赖不稳 池扩容或降载(第 10.1 节);环境对齐(第 7.2 节 shm 类环境税)

归因的取证材料就是第 8.3 节的事件流加第 6.1 节的三件套:时间戳对齐(失败是否集中在高峰)、实例分布(是否特定实例高发)、失败瞬间的快照(页面当时到底长什么样)。flake 的第一现场往往只在证据里——这是留痕系统在稳定性工程里的又一份回报。

三、重试的症状论

「重试不是答案而是症状」的完整表述:给未归因的 flake 加重试,等于用下一次的运气掩盖这一次的病因。重试的合法条件只有一条:失败已被分类器归入瞬态类(第 6.2 节),即已知根因是会自愈的(网络抖动),且重试有预算上限。除此之外的重试都在制造技术债的复利:

未归因 flake ──加重试──▶ 表面稳定 ──▶ 归因动力消失 ──▶ 病根存活 │ │ └──────────── 某天条件变化,病根以更难的形态复发 ──────────┘ ​

工程上的替代动作:新 flake 一律先进「隔离区」(quarantine 标记,不计入门禁但可见),附归因工单;归因完成(三分类之一加具体根因)后该修修、该删删,才允许回到正式集。测试框架的自动重试功能可以保留给已分类的瞬态失败,重试通过的用例要打标——「这次是重试才过的」本身就是稳定率统计要扣分的输入。

四、稳定率度量:双语统计

把稳定率升为一等指标,两个基础口径:单用例通过率(近 N 次运行的通过占比)、隔离对照差(单独运行的通过率减去全量运行中的通过率——差异大即存在共享状态污染)。flake 分取少数结果占比(零最稳):

# stability_report.py —— 用例稳定率统计(写法示意) import json from collections import defaultdict from pathlib import Path RUNS_DIR = Path("ci-runs") # 每次 CI 落一份 {test, status, retried} 记录 def load_history() -> dict: stats = defaultdict(lambda: {"pass": 0, "fail": 0, "retried": 0}) for file in sorted(RUNS_DIR.glob("*.jsonl")): for line in file.read_text(encoding="utf-8").splitlines(): row = json.loads(line) stats[row["test"]][row["status"]] += 1 if row.get("retried"): stats[row["test"]]["retried"] += 1 return stats def flake_score(runs: dict) -> float: total = runs["pass"] + runs["fail"] return min(runs["pass"], runs["fail"]) / total if total else 0.0 for name, runs in sorted(load_history().items()): total = runs["pass"] + runs["fail"] rate = runs["pass"] / total if total else 0.0 print(f"{name}: 稳定率 {rate:.0%},flake 分 {flake_score(runs):.2f},重试通过 {runs['retried']}") ​
// stability_report.mjs —— 用例稳定率统计(写法示意) import { readFileSync, readdirSync } from "node:fs"; const RUNS_DIR = "ci-runs"; // 每次 CI 落一份 {test, status, retried} 记录 /** 汇总历史记录 @returns {Map<string, {pass: number, fail: number, retried: number}>} 统计 */ function loadHistory() { const stats = new Map(); for (const file of readdirSync(RUNS_DIR)) { for (const line of readFileSync(`${RUNS_DIR}/${file}`, "utf8").split("\n")) { if (!line.trim()) continue; const row = JSON.parse(line); const s = stats.get(row.test) ?? { pass: 0, fail: 0, retried: 0 }; s[row.status] += 1; if (row.retried) s.retried += 1; stats.set(row.test, s); } } return stats; } for (const [name, s] of loadHistory()) { const total = s.pass + s.fail; const rate = total ? s.pass / total : 0; const flake = total ? Math.min(s.pass, s.fail) / total : 0; console.log(`${name}: 稳定率 ${(rate * 100).toFixed(0)}%,flake 分 ${flake.toFixed(2)},重试通过 ${s.retried}`); } ​

门禁阈值示例(示意值,按团队基线定):新合入用例近二十次运行稳定率低于九成五自动进隔离区;存量用例稳定率跌破九成出工单。

五、治理流程与分层门禁

五步闭环:发现(稳定率看板或隔离对照差报警)→ 归因(三分类加证据取证)→ 修复(时序改等待、数据改自足、环境改配置或容量)→ 隔离(修不了或低价值的移出正式集,显式记录原因)→ 回归(修复后观察窗口内的稳定率验证)。每一步都有产物进第 8.3 节事件流——治理本身也要可审计。

分层门禁的立场:flake 只出不进。新增用例先在预发环境跑二十次(快进快出的小任务集)再进正式集;对存量 flake 定期清账(修、删、隔离三选一,不许第四种「留着不管」)。门禁的价值在保护第 9.1 节回归任务集的信号纯度——任务集的可信度是第 11.3 节敢把它设为发布门槛的前提。

六、常见坑

坑 现象 排查与处理
重试当万能药 抖动被掩盖、根因积累 症状论:先分类,重试只给瞬态
阈值一刀切 全线告警或全线沉默 按用例分层定基线,定期校准
忽略重试标记 「过了」其实是重试过的 retried 字段进统计与门禁
隔离区变垃圾场 进去就没人管 隔离带归因工单与期限
只看通过率不看隔离差 共享状态污染漏诊 隔离运行对照纳入月度体检

本节要点回顾

  • flake 比稳定失败贵在信号腐蚀:重现成本、CI 文化、告警信任三线崩塌。
  • 根源三分类:时序(等待与竞态)、数据(漂移与共享)、环境(抖动与容量);取证靠事件流加三件套。
  • 重试症状论:合法条件是已分类的瞬态加预算上限;未归因 flake 先隔离区加归因工单。
  • 稳定率双口径(单用例通过率、隔离对照差)加 flake 分(双语实现);门禁立场「只出不进」。
  • 五步闭环(发现、归因、修复、隔离、回归)每步留痕,保护回归任务集的信号纯度。

稳定的基线有了,接下来让系统「坏了有人知道」:下一节给错误一套分类学与告警分级——五类 taxonomy、三级告警、成功率看板,把第 8.3 节的事件流变成值班同学看得懂的仪表盘。

作者与出处
原作者:灏天文库
整理人: 灏天文库整理
本站整理收录,版权归原作者/开源协议所有;欢迎通过原文链接访问源仓库。
发布者: 作者: 灏天文库 转发
评论区 (0)
U