← 返回全部文章
Analysis · 2026年9月17日 · 9 分钟阅读

Anthropic 的 CI 救火:从10月到3月,一名工程师,三周完成重构

一次扩容能多撑多久,要看负载的倍增时间,以及持续吞吐实际提高了多少。若吞吐提高到原来的 k 倍,就多出 log2(k) 个倍增时间;扩容时的利用率不会改变这笔收益。利用率决定的是原本就有的可用时间。把这两笔时间分清,才能读懂 Anthropic 公布的数字,也才能算对容量规划的账。

30 秒看懂这次救火

9 月 14 日,Anthropic 的一名工程师回顾了内部一个服务反复撑不住的经过。当时,Claude 已经承担了公司大部分代码的编写。传得最广的是两组增长数字,服务究竟怎么坏、怎么修,反而没那么受关注:

  • 工程师人数只小幅增加,CI 任务量却在六个月内达到原来的 25x,测试数量达到 10x。
  • 工程师每季度交付的代码量约为 2021-2025 年间的 8x;合并代码中,约 80% 由 Claude 编写。

出问题的是测试影响分析服务。它负责判断一次代码变更会影响哪些测试,从而决定需要跑哪些测试。比起增长倍数,带着日期的修复过程更能说明问题:

时间处理办法当时的问题撑了多久
2025 年 10 月核心数翻倍服务吃紧,连续两天触发告警70 天
2026 年 2 月按包分片,把单写者拆成每个包各有一个写者listener 积压,触发告警29 天
2026 年 3 月每天重启到下午过半就触及内存上限不到一天
随后重构:无状态 listener、共享日志单写者本身成为瓶颈一名工程师,三周

前后救火约五个月,最后的重构花了三周。这两段时间摆在一起,才是这篇事后复盘最值得追问的地方。但要判断它对容量规划有什么用,必须先分清:哪些结论能从公开数字推出,哪些还推不出来。其中有一笔账,容量规划里尤其容易算错。

扩容新增的时间,与原本的可用时间

扩容后还能撑多久,要看负载何时再次碰到容量上限。假设负载按固定的倍增时间 D 增长,写成 L(t) = L₀ · 2^(t/D)。系统原本能持续承受的容量为 C₀,当前利用率为 u = L₀/C₀,扩容后容量提高到 k·C₀。这时要分别算三笔时间:

  • 什么都不做,原本还有的可用时间:
    D · log₂(1/u)
  • 扩容后的总可用时间:
    D · log₂(k/u)
  • 这次扩容新增的可用时间:
    D · log₂(k)

把前两项相减,u 就消掉了。也就是说,持续吞吐提高到 2x,就能多争取一个倍增时间。无论扩容时利用率已经是 100%,还是只有 50%,新增的时间都一样。

50% 利用率时提前扩容,确实能让总可用时间更长,因为动手之前本来就还剩一个倍增时间。但这不是扩容多带来的收益。「离下次撑不住还有多久」和「这次扩容多争取了多久」,必须分开回答。

按新增的可用时间来算,各种扩容幅度的收益如下:

扩容后容量为原来的多少新增几个倍增时间D = 39.3 天时D = 70 天时
1.5x0.5823 天41 天
2x1.0039 天70 天
4x2.0079 天140 天
10x3.32131 天233 天
20x4.32170 天303 天
100x6.64261 天465 天

容量的价格通常大致随容量线性增加,换来的时间却只按对数增加。把扩容幅度从 2x 提到 10x,容量是前一种方案的五倍,新增的可用时间却只有 3.3 倍;从 10x 再提到 100x,容量又要增加到十倍,新增时间才再翻倍。多买容量当然能争取时间,只是每多争取一个倍增时间,花费都会更高。

那 70 天,究竟能推出什么

三次临时措施里,只有第一次公布了名义上的扩容倍数:核心数翻倍。因此,最容易让人拿来反推倍增时间的,就是那 70 天。但这样推算需要知道两件事,原文只交代清楚了一件。

第一件:扩容时是不是已经接近上限?这有证据。 原文说,到 10 月服务已经吃紧,核心数翻倍前,团队连续两天因告警被叫起来(原文:“got paged two days straight”)。连续两天触发告警,说明当时已经没有多少余量。因此可以近似取 u ≈ 1,这 70 天也就近似对应 log₂(k) 个倍增时间。

第二件:吞吐究竟提高了多少?这个数字未知,而且很可能不到 2 倍。 核心数翻倍,只能说明一种资源翻倍。彼时的写入仍要经过单写者,也就是仍有串行处理的环节。只要部分工作必须串行执行,增加核心数就不能带来等比例的吞吐提升。这个区别会直接改变反推结果:

核心数翻倍后,实际吞吐达到对应的 2025 年末倍增时间
2.0x70 天
1.7x91 天
1.5x120 天
1.3x185 天

k 越低于 2,反推出来的倍增时间就越长。所以,能站得住的结论只是一个下限:如果核心数翻倍带来的实际吞吐最多为 2x,那么 2025 年末的倍增时间至少是 70 天。

再看另一组公开数字:六个月内增长到 25x,对应的平均倍增时间是 39.3 天,大约只有上述下限的一半。

这两个数字有差距,并不一定要靠「增长加速了」来解释。不过,增长加速确实是一种顺理成章的读法。它依赖一个原文没有给出的前提:统计 25x 的那六个月,晚于从 10 月到次年 3 月的修复过程。文章到 9 月才发表,这样理解有道理;但原文没有说明,所以这是一种读法,不是测量结果。这点必须说清,因为文章传播时,25x 已经常被当作可以套到任何地方的增长速度。

70 天、29 天、不到一天,这组越来越短的持续时间也与增长加速的读法一致,却不能独立证明它。三次临时措施处理的是不同资源,不能把它们当成同一条增长曲线上的三个采样点。

三次临时措施,三种资源

要理解为什么不能把三段时间连成一条曲线,就得回到每次故障本身。最初是处理器吃紧,接着是 listener 积压并触发告警,后来是大多数工作日到下午过半,进程就触及内存上限。对应的是处理器、队列和内存。

一个有状态、只运行单个实例的服务,会同时占用多种资源。原文并没有证明「每修好一处,就导致下一处出问题」。它能说明的是:团队对同一个组件先后做了三次处理,却一直没有解决它难以承受增长的根本原因——无法横向扩展。

到了每天重启这一步,前面的扩容模型已经不适用了。重启并没有抬高容量上限,只是清掉了积累的内存占用。能撑多久,要看内存多快再次填满,而不是负载多久翻倍;这次连一天都没撑住。

这不能证明增加内存就没用。它说明的是,到了这个阶段,团队已经没有可继续采用、又能用「容量增加到几倍」来描述的临时措施了。

重构改掉了什么,又没改掉什么

重构后,listener 工作进程不再保存状态,日志移到了共享内存存储中。consumer 每隔几秒把日志条目汇总到各项测试的历史记录里,再由 selector 查询。此前的单写者,最初是全局一个,2 月分片后是每个包一个;现在,listener 的处理路径上不再有这个环节。

这不意味着系统从此没有容量上限。共享存储、consumer 和 selector 都有自己的上限;负载到了一定程度,其中某一个仍会成为瓶颈。真正改变的是 listener 这一层:只要其他环节没有先卡住,再承受一轮负载翻倍,主要就靠增加工作进程,无须重新设计这一层。 改善的范围没有「无限扩展」那么大,但已经很有价值。

完成这次重构,只用了一名工程师三周。不过,不能据此认定前面的补丁都是错误决策。长期方案还在开发时,临时措施能维持服务运行;故障也是一个接一个出现,团队才逐渐看清原因。更不能拿 3 月才掌握的信息,去要求 10 月的决策者提前知道。

这些限制不妨碍我们问一个具体问题:如果长期方案需要三周,继续打能撑 70 天的补丁,打到第几次才值得转向重构? 很多团队迟早都会面对同样的选择。

Anthropic 的四条建议

原文最后给了四条建议。这里保留要点,用中文简述:

  • 无论自建还是采购,都先按两个季度内负载达到 25x 做规划。
  • 预算允许时,v0 设计就按当前估计规模的 10-20x 考虑。
  • 给服务加监控埋点,让 Claude 能读取指标;其中一项检查是,进入的任务数与完成的任务数是否匹配。
  • 从一开始就把状态移出进程。 除非能监测关键服务本身以及任何 canary 变更,否则不要只运行一个实例。

前两条故意用了不同的数字。按 25x 做规划,设计时先做到预算允许的较小规模,两者并不矛盾。需要心里有数的是:中间没覆盖的那部分规模,以后还得花时间补上。

换成自己的流水线,该怎么算

  1. 先算自己的倍增时间。 选同一个指标,用相同口径取两个时间点的数据,比如每天进入 CI 的任务数,或测试结果事件数。用后一个数除以前一个数,再取 log₂,最后用间隔天数除以这个结果。假设 180 天内,每日任务量从 400 增至 3,000,就是 log₂(7.5) = 2.9 次翻倍,倍增时间约为 62 天。这只是历史平均值,不是预测。
  2. 再确认扩容实际能带来多大吞吐。 更大的实例增加的是核心数;只有没有串行环节,核心数的倍数才等于吞吐的倍数。那 70 天的补丁,恰恰就容易在这里被误读。分成 N 片,也只有在没有热点分片、没有其他共享环节时,才能按 N× 计算。通过缓存或去重,把每项任务的工作量减半,才算得上实际的 2x 提升——前提仍然是测过。
  3. 把争取到的时间和重构所需时间放在一起比较。 用 D × log₂(k) 对比长期方案从开发到验证、逐步上线所需的时间。再单独算 D × log₂(1/u),看什么都不做时原本还能撑多久。如果补丁本身争取到的时间不足以覆盖长期方案,就该动手前知道,而不是拖到第四个月才发现。

规模没那么大,也会遇到同类问题

25x 不是给其他团队的增长预测。Anthropic 的情况很极端:约 80% 的合并代码由编程代理编写,测试数量达到原来的 10x,工程师人数却大致没变。

但这类问题并不要求代理编写的代码占到 80% 才会出现。代理加快了代码变更和测试的产出,下游各环节却仍按人工开发的速度配置。测试选择、runner 资源池、制品存储、审查队列受到的影响各不相同,不能统一乘上某个倍数;哪个环节最先到上限,哪个就先出问题。

因此,值得带回自己团队的结论是:代理增加的成本,可能落在一项原本没人算到它头上的预算里。 词元费用每月出账,明细很清楚。CI 的额外开销来得更晚,还记在另一个团队的账上。等这笔成本终于看得清,那支团队可能已经救火五个月了。

延伸阅读

来源

  1. Anthropic:编程代理正在给 CI 带来压力:Anthropic 如何扩展测试影响分析服务,作者 Sachin Malhotra,发表于 2026 年 9 月 14 日。本文引用的各项事实和数据均来自这篇文章:25x、10x、8x 和 80%;2025 年 10 月、2026 年 2 月、2026 年 3 月的修复时间;三次临时措施各自维持的时间和对应故障;首次扩容前连续两天触发告警;单写者的限制、分片方案和重构后的架构;一名工程师三周完成重构;以及四条建议。
  2. Anthropic:当人工智能开始构建自身。用于理解代码占比数据的统计口径。

文中的计算由我们完成,依据是同一个恒等关系:持续吞吐提高到原来的 k 倍,会新增 log₂(k) 个倍增时间;利用率 u 则单独决定原本还有的 log₂(1/u) 个倍增时间。39.3 天来自 182.6 天除以 log₂(25),只是一个平均值,原文没有给出这段统计窗口的起止日期。

2025 年末「至少 70 天」的下限有两个前提:扩容时利用率已接近上限,连续两天触发告警为此提供了依据;核心数翻倍带来的实际吞吐不超过 2x。若进一步认为两个时段之间增长加速了,还得再加一个前提:那六个月的统计窗口在时间上更靠后。原文没有说清,所以我们把这项假设明确列出,不将它当作已知事实。

FAQ

Anthropic 的 CI 到底出了什么问题?

2026 年 9 月 14 日,Anthropic 工程师 Sachin Malhotra 回顾了测试影响分析服务反复故障的经过。这个服务负责决定一次代码变更需要跑哪些测试。2025 年 10 月,服务连续两天触发告警,核心数翻倍后撑了 70 天;2026 年 2 月,按包分片,把单写者拆成每个包各有一个写者,撑了 29 天;2026 年 3 月,每天重启以清理内存,却连一天都没撑住。最后,一名工程师用三周围绕无状态工作进程完成了重构。

负载究竟增长了多少?

工程师人数仅小幅增加,CI 任务量在六个月内达到原来的 25x,测试数量达到 10x。另一组数字是,每季度交付的代码量约为 2021-2025 年间的 8x,约 80% 的合并代码由 Claude 编写。这些数字的比较基准和统计时段不同,不能相乘。原文也没给出那六个月的起止日期,不能把它与从 10 月到次年 3 月的修复过程精确对齐。

一次扩容能多争取多少可用时间?

负载的倍增时间为 D、实际吞吐提高到原来的 k 倍时,新增的可用时间为 D × log₂(k) 天,与扩容时的利用率无关。利用率 u 决定的是原本就有的 D × log₂(1/u) 天;扩容后的总可用时间为 D × log₂(k/u) 天。提前扩容会让总可用时间更长,但不会让这次扩容本身多争取到时间。

能从那 70 天反推出负载的倍增时间吗?

可以近似估算,但必须说明前提。连续触发告警,说明扩容时已接近容量上限。可当时写入仍要串行处理,核心数翻倍不等于吞吐翻倍:如果实际吞吐达到 2x,倍增时间就是 70 天;如果只有 1.5x,则约为 120 天。k 越低于 2,反推的倍增时间就越长。因此,在吞吐最多提高到 2x 的前提下,只能说 2025 年末的倍增时间至少为 70 天。相比之下,六个月增长到 25x,对应约 39 天。

这能证明增长在加速吗?

有这种可能,而且是顺理成章的读法,但还不能证明。它要求那六个月晚于从 10 月到次年 3 月的修复过程。文章在 9 月发表,使这个前提显得合理,原文却没有明确说明。补丁撑过的时间从 70 天缩到 29 天,再到不到一天,也与这种读法一致;但三次措施处理的资源不同,不能把它们当成同一条曲线上的三个采样点。这是一种读法,不是测量结果。

规模较小的团队该怎么借鉴 Anthropic 的建议?

Anthropic 建议,无论自建还是采购,都按两个季度内负载达到 25x 做规划;预算允许时,v0 设计就按当前估计规模的 10-20x 考虑。给服务加监控埋点,让 Claude 读取指标,同时核对进入和完成的任务数是否匹配。从一开始就把状态移出进程;除非能监测关键服务以及任何 canary 变更,否则不要只运行一个实例。25x 和 10-20x 有意不同:按前者规划,设计时先做到预算允许的较小规模。

对小团队来说,25x 不是自己的增长预测。更值得借鉴的是成本归属:代理加快了代码变更和测试的产出,下游却仍按人工开发的速度配置。增加的成本,可能落在一项原本没人算到代理头上的预算里。

这篇对你有帮助吗?

相关阅读


文章独立产出 · 编辑政策

继续阅读 →