一个合并队列每小时能落地多少拉取请求,答案不在流水线数量上。它被两个数字卡死:队列里的变更失败有多频繁,以及CI跑一次要多久。加更多流水线,只是让更多工作一起被浪费掉。
这套逻辑和乐观锁在争用下的表现是同一个形状。每笔事务都要对着它之前提交的所有内容做校验,一次中止就把排在后面的工作全部丢掉。加worker只会增加竞争者,吞吐量由中止率和事务时间决定。代理把负载推高,天花板却留在原地。
用学校食堂打饭来理解:每个孩子都要把餐盘给厨师看,厨师拿它和之前每一个餐盘比对。厨师能同时看二十个盘子,可一旦某个盘子有问题,那个孩子出列,排在他后面的所有人都得重新出示——因为厨师刚才是拿错误的盘子做的比对。平时队伍走得挺顺,直到一个班的孩子涌进来,餐盘多了三倍。厨师没变慢,队伍照样堵住。
失败率和CI时间决定上限
设队列中失败拉取请求的占比为p,CI时间为T。两次失败之间,队列大约能落地1/p个拉取请求;每次失败会丢掉后面最多T的工作量。所以无论多少条流水线,一小时落地的数量都不会超过约1/(p × T)。
按这个公式:CI三十分钟、失败率5%,一小时约40个;开20条流水线,约24个;加到100条流水线,只买到38个,CI账单却是五倍。
失败率来自两处。一是真实的破坏:某个变更在自己的分支上通过了,却和前面某个变更冲突。二是不稳定的测试:代码没动,测试时好时坏。这类问题会累积——五十个测试各自千分之一的概率偶发失败,合起来大约每二十次运行就失败一次,也就是p = 5%。
接近上限时,等待时间会陡涨。每小时8个拉取请求的队列,平均等34分钟;把到达量提到24个,纸面上仍在上限之内,等待却超过100分钟。
先降失败率,再谈加流水线
什么时候这事要紧?仓库用了合并队列,而人或代理提交的拉取请求比上季度多了。CI跑得久、又有几个大家都在重试的测试时,影响最大。如果经常看到“removed from the merge queue”,你已经在为此付账了。
队列变慢,最直觉的修法是加并行流水线。失败一多,这招收益很小,还推高CI账单。等待的增长快过流量的增长。代理随后在过期的main上rebase,彼此冲突,在偶发失败之上又添了真实失败。一个不稳定的测试原本只是烦人,在高负载下它决定了你的吞吐量。
动手前先取上周的两个数字,不需要新工具:数一数队列合并了多少拉取请求、因检查失败被移除了多少,后者除以前者加后者,就是p;再从任意一次最近的队列运行里读出T。算出1/(p × T),和你最忙的那一小时比一比。如果忙时已经逼近上限的一半,等待就已经很长了。
顺序是:先降p——失败测试在剔除前先重试一次,把偶发失败的测试隔离出去;再缩短T,把慢检查移出队列;加流水线放最后。
具体到四个角色。管CI的人,在合并组上只跑快速的必需检查,让一个偶发失败必须失败两次才剔除拉取请求;GitHub Actions里队列触发merge_group事件,重试可用pytest-rerunfailures插件,GitLab则在任务上加retry: { max: 1, when: script_failure }。两次都失败才算失败,5%的偶发率会降到约0.25%,代价是偶发问题被藏起来,所以要配合第三个角色。
管仓库的人,按公式而不是感觉来定队列规模。GitHub用build concurrency设置流水线数(1到100),GitLab默认允许20条并行流水线。改设置前先代入自己的数字:p为5%、CI三十分钟时,二十条流水线按公式约20个/小时,模拟约24个;把p降到1%,同样二十条约33个。后者是更便宜的那个改动。
管测试套件的人,找出偶发测试——同一个提交上先失败后通过的就是。记录下来,按次数排序,把榜首的隔离掉直到修好。管代理的人,让拉取请求进队列之前先跑队列的检查,先在新鲜的main上rebase:冲突在分支上被发现只花一条流水线,在队列里被发现要花掉后面所有流水线。
一周后重算p和T。因失败被移除的数量应该先降,队列里的平均停留时间随之下降,忙时落地量向公式值靠拢。如果p降了而等待没降,说明T成了新的限制,去看哪个检查最慢。
什么时候不该调队列
量低的时候。一小时只落地几个拉取请求的团队远在任何上限之下,等待只来自CI时间,缩短CI就好,别动队列。
失败是真实的而非偶发的时候。如果被剔除的拉取请求大多是坏的,重试只是把同样的答案推迟,应该在分支上更早拦住它们。
量增长到任何队列都装不下的时候。Anthropic的合并量从2026年5月每天约2,500次涨到9月每天超过30,000次,他们不再使用合并队列,把大部分测试移到合并之后、部署之前。这用绿色的main换速度,需要快速回滚,以及能在几分钟内修好main的人或东西。这比调队列是个大得多的改动。
批量合并也有代价。更大的合并组把多个拉取请求放进一条流水线测试,一旦失败就得拆分找出元凶,反而多花运行次数。
改任何东西之前先回答三个问题:上周排队的拉取请求里,因检查失败被移除的占多少?合并组上的必需检查要跑多少分钟?你最忙的那一小时离1/(p × T)有多近?
热门跟贴