中位数 2.2 分钟,均值 9.9 分钟:AI 时代的等待时间去了哪
About 16 min read
读 CircleCI 2026 的近 2800 万条流水线、Incredibuild 的千级开发者抽样,以及那个被引用了十几年、却查不到出处的「23 分钟」。
Contents · 7 sections · jump to a heading
2026 年 2 月 18 日,CircleCI 发了第七份 State of Software Delivery。样本是 2025 年 9 月前 28 天里的近 2800 万条 workflow,来自 149 个国家的 22000 多个组织。
同一份报告里有两个方向相反的数字。全平台日均吞吐同比涨了 59%;主分支成功率跌到 70.8%,是五年最低。
第三个数字更值得停下来看:workflow 的中位时长是 2.2 分钟,比上一年快了 18%;而均值是 9.9 分钟。
流水线本身在变快,人花在等它上面的时间却在变长。这两件事同时为真。
这篇文章想回答三个可以被验证的问题:
- 流水线到底快不快?
- 那十几分钟被谁吃掉了?
- 被引用了十几年的「23 分钟才能回到心流」,站不站得住?
- 我没有那 2800 万条流水线的原始数据。 所有数字都是 CircleCI 自己发布的,我只是把它们放在一起核对。我做的那点算术会标出来是算术。
- 我没有复现 Incredibuild 的抽样。 他们的样本量、问卷设计和统计口径以发布的报告为准,我只引用报告里印出来的数。
- 我查不到「23 分 15 秒」的同行评审来源。 这一节会把我找到的东西和没找到的东西分开写,不合并成一句结论。
- 下面那份 YAML 没有在你们的 CI 上跑过。 它在本机做过语法检查,跑没跑通,取决于你们的锁文件和测试框架。
CircleCI 这份报告里,我认为最该被记住的是这三个。
| 指标 | 2025 年 | 同比 |
|---|---|---|
| workflow 中位时长 | 2.2 分钟 | 快 18%(2024 年为 2.7 分钟) |
| workflow 均值 | 9.9 分钟 | 快 10%(2024 年为 11 分钟) |
| 主分支中位 MTTR | 72 分钟 | 慢 13% |
第一个和第二个数字之间的差,就是这篇文章的全部内容。中位数 2.2 分钟意味着一半的流水线在两分半内结束;均值 9.9 分钟意味着剩下那一半里,有一批跑得极慢。均值被中位数甩开 4.5 倍,这不是正态分布,是一条长尾。
长尾从哪里来,报告自己给了线索:feature 分支上的 MTTR 从 2024 年的 64 分钟涨到了接近 80 分钟,涨幅 25%。 报告给的解释是 feature 分支上的测试套件反馈通常不完整。翻译一下:主干上跑得快的那些检查,在分支上没有跑,所以问题要等到更晚才暴露,暴露之后的修复窗口更长。
再看吞吐。全平台日均吞吐涨 59%,但这个涨幅高度集中:
- 前 5% 的团队:6.8 次/天 → 13.4 次/天,接近翻倍
- 前 10%:+47%
- 前 25%:+25%
- 中位数团队:+4%
- 后 25%:无可测量的增长
报告用了一个说法:AI 不是在制造性能差距,是在暴露它。这句话有点公关味,但数字本身是硬的——代码产出速度的提升是普遍的,把它变成可交付变更的能力是集中的。
把一次流水线拆开,时间花在四类地方,性质只有两种。
content/_code/ci-speedup/pipeline.yml
1name: ci23on:4pull_request:5push:6branches: [main]78concurrency:9group: ci-${{ github.ref }}10cancel-in-progress: true # ← 同一分支推第二次,直接掐掉上一轮,不排队
上面这段是「并发排队」那一类的最便宜的解法:cancel-in-progress。同一分支上推了第二次,第一次的任务直接取消,不再排队等机器。四行配置,省掉的是纯粹的空等。
剩下三类,性质不同:
冷启动与装包,属于「重复劳动」。 每次运行从公网重新拉一遍依赖,磁盘和网络 I/O 吃掉了时间,而这些依赖上一次运行刚刚拉过。这类时间的特征是它可以被缓存掉,且缓存掉没有信息损失。
串行测试,属于「并行度不足」。 单线程跑一条测试套件,CPU 只用一个核。这类时间的特征是可以靠分片摊平,但摊平有固定开销。
Flaky 重跑,属于「假信号」。 一条用例因为动画时序或网络抖动飘红,整轮重跑。这类时间最贵,因为它不只吃掉等待,还吃掉信任——红灯多了之后,团队开始习惯性地点 Re-run,真正的问题会被淹没在噪音里。
三类对应的动作写在 pipeline.yml 里,分片那一段长这样:
content/_code/ci-speedup/pipeline.yml
1test:2needs: scope3if: needs.scope.outputs.affected == '1'4runs-on: ubuntu-latest5strategy:6fail-fast: false7matrix:8shard: ${{ fromJSON(needs.scope.outputs.shards) }}9steps:10- uses: actions/checkout@v41112- uses: pnpm/action-setup@v413with:14version: 91516- uses: actions/setup-node@v417with:18node-version: 221920- name: 复用 pnpm store21uses: actions/cache@v422with:23path: ~/.local/share/pnpm/store24key: pnpm-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }} # ← 锁文件没变就命中2526- run: pnpm install --frozen-lockfile2728- name: 跑第 ${{ matrix.shard }} 片29run: pnpm test:run --shard=${{ matrix.shard }}/4
注意 hashFiles('pnpm-lock.yaml') 这个 key。缓存命中与否完全由锁文件决定,不掺时间戳、不掺分支名。缓存 key 的写法决定了缓存是资产还是负债。
我最初写这篇的时候,开头准备直接引用那个数字:被打断后平均需要 23 分 15 秒才能回到原来的任务,所以等 CI 十分钟的代价是 23 分钟。
我去找了原始出处。找不到。
公开可追溯的源头是 2006 年 Gallup 对 Gloria Mark 的一次采访。她在采访里提到了这个时长,它没有作为正式的研究发现发表过,也就没有经过同行评审。
有人把常被引作来源的几篇论文逐篇查了一遍,结论是没有一篇包含这个数字。其中被引用最多的是《The Cost of Interrupted Work: More Speed and Stress》,而这篇的结论方向和流行说法相反:
- 有中断时,原任务耗时 20.31 到 20.60 分钟
- 无中断时,原任务耗时 22.77 分钟
- 也就是说,被打断的人更快完成了任务,代价是压力水平显著更高
Mark 自己在 2005 年的研究给出的平均恢复时间是 25 分 26 秒,返回原任务前平均插入 2.26 个任务。她 2008 年那篇 CHI 论文的摘要写的是同一件事:被中断的任务完成时间更短,质量没有差异。
有人统计过 23 篇引用「23 分 15 秒」的博客:9 篇错误引用了论文,2 篇引对了,9 篇追溯到那次采访,2 篇引的是《华尔街日报》的转述,还有 1 篇引用了在被引论文里根本找不到的话。
所以这个数字的正确用法是:不要当测量值用。
代价在于,砍掉它之后,「等 CI 很贵」这个结论就少了一根支柱。它剩下的支柱是报告里的实测数字:中位 MTTR 72 分钟、主分支成功率 70.8%、500 次/天改动的团队在 60 分钟恢复时间下等于损失 12 个全职工程师的工作量。这些数字有明确的样本和口径,比一个查不到出处的 23 分钟好用。
收益是,论证变干净了。原来的链条是「被打断要 23 分钟 → 所以等 CI 很贵」,中间靠一个来源不明的数字撑着。现在的链条是「流水线失败后中位要 72 分钟才回到绿色 → 所以恢复环节才是瓶颈」,每一步都能查到出处。
前面几节都在讲哪里能省时间。这一节讲代价,因为省时间从来没有免费的版本。
缓存依赖。 省下的是每次运行重复下载依赖的时间,通常能砍掉最大的一块固定开销。代价是缓存会掩盖「干净环境」下的问题:本地能过、CI 也能过,但换一台没有缓存的机器就炸。另外缓存 key 一旦写错(比如掺进了 github.run_id),命中率会静默归零,你以为在省时间,实际一次都没命中。上线缓存之后要去看命中率,不是看配置写没写。
测试分片。 四片并发理论上把 10 分钟摊成 2.5 分钟。代价有三个:分片有固定开销,机器越多开销占比越高,超过某个片数总时长反而回升;同一用例在不同分片上的时序不同,flaky 会变多而不是变少;分片数写死在 workflow 里,测试量涨了要记得回来改。上面那份 YAML 把分片数写成一个 outputs,就是让改动只发生在一处。
变更感知。 只跑被改动文件影响到的测试,在大型仓库里省下的时间最多。代价是它需要一张真实的依赖图,而依赖图会过期。图错了的后果是不对称的:多跑几分钟只是慢,漏跑一条是直接上生产。这条的默认值应该是保守的——图不确定时,按全量跑。
调整测试金字塔。 把脆而慢的 UI 级测试砍到只留一条核心路径,逻辑交给毫秒级的接口契约测试。代价是丢掉真实渲染路径的覆盖:API 契约测试能告诉你接口返回了正确的 200,不能告诉你按钮被一个弹窗盖住了。冒烟 E2E 的价值不在覆盖率,在它挂掉时你会得到一张截图和一份 trace。
pipeline.yml 里最后那一段就是为这件事写的:
content/_code/ci-speedup/pipeline.yml
1- run: pnpm exec playwright test e2e/smoke.spec.ts --trace=retain-on-failure2- uses: actions/upload-artifact@v43if: failure() # ← 只在红灯时留证,绿灯不占存储4with:5name: playwright-trace6path: test-results/7retention-days: 7
--trace=retain-on-failure 加 if: failure() 的组合,是这套改动里性价比最高的一处:绿灯时零开销,红灯时你手上有一份可以直接回放的证据。没有它,冒烟测试就只是一盏灯。
这份报告的数据采集窗口是 2025 年 9 月的前 28 天,明年这些数字都会变。CircleCI 是卖 CI 的公司,它的报告同时是一份营销材料,读的时候要把这层关系算进去。
但有两件事不随版本变化。
第一,中位数和均值之间的差距,永远比中位数本身更值得看。 中位 2.2 分钟、均值 9.9 分钟这个组合告诉你,优化对象不是「把平均流水线做快」,而是「把那条长尾剪掉」。长尾通常来自少数几个又慢又脆的任务,不是来自所有任务的均匀分布。
第二,等待的成本不在算力账单上,在恢复窗口上。 中位 MTTR 72 分钟意味着一次失败之后,从红灯到绿灯的平均成本是 72 分钟,而不是那 2.2 分钟。想缩短它,动作不在「让测试跑得更快」,在「让失败更难发生、发生之后更容易定位」。
剩下的距离,全在测试设计的那一侧。
- CircleCI, 2026 State of Software Delivery,2026-02-18。样本:2025 年 9 月前 28 天,近 2800 万条 workflow,149 个国家的 22000 多个组织。本文引用的中位时长 2.2 分钟、均值 9.9 分钟、中位 MTTR 72 分钟、主分支成功率 70.8%、吞吐 59% 及分位数分布均出自此报告。
- Incredibuild, 2025 Build Time Survey Report。94% 的工程团队认为长构建时间是障碍,团队每天累计 32 小时花在运行构建上,44% 的组织已在开发中使用 AI。完整报告 PDF 见 2025-Build-Time-Survey-Report.pdf。
- DORA, 2025 State of AI-assisted Software Development。该报告的结论是 AI 主要起放大器作用,放大组织既有的强项与弱项。
- Gloria Mark, CHI 2008 论文原文。摘要结论:被打断的任务完成时间更短,质量无差异。
- 「23 分 15 秒」的出处考证:Interruptions cost 23 minutes 15 seconds, right?。本文关于引用错误分布的统计出自该文。
- GitHub Actions 依赖缓存文档:Caching dependencies to speed up workflows
- Playwright 测试分片文档:Test sharding
- 示例流水线:
content/_code/ci-speedup/pipeline.yml。仓库为私有,行号供本机核对。