跳到主要内容

合并队列成了新的瓶颈

· 阅读需 11 分钟
Tian Pan
Software Engineer

你的编程智能体(Coding agents)刚刚让编写代码成为了交付软件中最廉价的部分。但它们并没有让代码落地变得更便宜。AI 采用率高的团队合并的 Pull Request (PR) 数量几乎是以前的两倍——而他们的交付指标却几乎没有变化,因为每一个 PR 仍然必须挤进同一个代码评审流水线、同一个 CI 集群以及同一个合并队列(Merge Queue),而这些设施当初是按人类打字速度设计的。约束并没有消失,而是向后移动了,移到了系统中那个最窄的管道:从“已批准”到“进入主分支(main)”之间的串行化路径。

这是一个经典的约束理论(Theory-of-constraints)故事,大多数工程组织目前正身处其中却尚未察觉。当一名开发者可以在并行工作树中指挥 5 到 10 个智能体时,PR 的体量就不再与员工人数挂钩。但合并吞吐量仍然取决于一些更为死板的东西:你的 CI 每小时能验证多少个 main 分支的候选状态。这个数字受限于测试套件的时长、执行器(Runner)容量、抖动率(Flake rate)和队列机制——而这些都没有随着智能体变快而变快。

悄然失效的容量数学题

合并队列的存在是为了强制执行一个不变性:main 分支永远是绿的(测试通过状态)。它通过针对 main 的“未来”状态(当前末端加上队列中排在前方的所有变更)来测试每个 PR,而不是针对编写 PR 时那个陈旧的分支点。这种保证正是你在几十个并发变更可能产生交互时所需要的,但也正是它让吞吐量的成本变得昂贵:正确性是用串行化换来的。

算一算串行队列的账。如果你的合并组 CI 端到端需要 30 分钟,那么一个严格排序的队列每小时最多只能合并 2 个 PR——如果没有任何失败,每天最多 48 个。一个 50 人的团队,如果每人每天产生 2 个 PR,就已经超过了这个上限。现在让每个开发者运行几个智能体,请求到达率翻了三倍,而服务率却保持不变。排队论在这里是残酷的:当到达速度接近处理容量时,等待时间不再是线性增长,而是呈爆炸式增长。上个季度只增加了 10 分钟延迟的队列,在这个季度可能增加 4 小时,而 CI 的任何环节都没有变慢。

分批处理(Batching)是标准的避风港,而且确实有效:将 5 个 PR 放在一次 30 分钟的运行中进行测试,你就获得了 5 倍的吞吐量——只用了 30 分钟的算力,而不是两个半小时。GitHub 自己的单体仓库(Monorepo)就是一个参考案例。超过 500 名工程师通过合并队列每月合并约 2,500 个 PR;从旧的部署列车模型迁移到动态合并组后,平均交付时间缩短了三分之一,同时安全并发部署的变更量翻了一番。但分批处理也有失败模式,而智能体异常擅长触发这种模式。

智能体是抖动放大器

当一个批次失败时,队列必须找出罪魁祸首。简单的策略是剔除整个批次并重新测试所有内容;更聪明的队列会采用二分法(Bisect),但这仍然需要 O(log n) 次完整的 CI 运行。无论哪种方式,只要有一个红色测试结果,就会导致后面所有 PR 赖以测试的投机状态失效,队列必须从失败点重新开始。这种级联失败(Cascade)是合并队列运行中成本最高的操作——而它的发生频率是由你的“抖动测试(Flaky test)”率决定的,而非代码质量。

Uber 的数据让这个利害关系变得具体:在他们建立专门的抖动管理之前,他们的 iOS 主线仅有 52% 的时间是绿色的,他们最终在单体仓库的 600,000 个测试中识别出了大约 1,000 个抖动测试。仅仅千分之几的不稳定测试就足以堵塞所有人的队列。

智能体让情况显著恶化,原因有三个复合因素:

  • 更多的条目,更多的掷骰子机会。 如果每 200 次运行会出现一次抖动,而你的队列每天处理 40 个批次,那么你每周都会发生级联失败。当每天处理 120 个批次时,你每天都会发生级联失败。抖动率没变,但暴露在风险下的次数变了。
  • 智能体无休止地重试。 人类看到偶然的失败会叹口气,点击重新运行,并在 Slack 上提一句。但一个被指示为“完成 PR 合并”的智能体会反复将同一个变更重新排队,每次尝试都会烧掉完整的 CI 运行时间,并为排在后面的所有人重新触发同样的级联失败。在人类操作频率下只是轻微浪费的重试循环,在智能体频率下变成了拒绝服务攻击(DoS)。
  • AI 编写的变更带有更多的潜在破坏性。 跨工具研究发现,AI 辅助编写的 PR 包含的问题大约是纯人工 PR 的 1.7 倍。其中一些问题仅在合并组中才会暴露——这意味着更多的合法失败与抖动交织在一起,使得寻找罪魁祸首变慢,也让乐观策略变得更冒险。

这种复合效应才是关键。每个因素单独看都是可控的;但加在一起,它们会让一个原本每月出一次状况的队列变成持续震荡的状态。团队的感受是“CI 变抖动了”,但测试并没有变——变的是流量。

加载中…
References:Let's stay in touch and Follow me for more thoughts and updates