~/yomxxx/posts/2026-09-23-ai-coding-ci-bottleneck-rework-long-form.mdx
Long-form

$ cat AI 编码把 CI 逼成瓶颈:Linear 的一次系统级重构复盘

-- 6 min read ·

今年早些时候,Linear 的 CTO 给工程师 Mufeez Amjad 派了一个单行标题的 issue:「CI 成本很高」,顺便把 CI 也做快一点。9 月 21 日,他把整个优化过程写了出来。这篇文章之所以值得逐条复盘,是因为它精准命中了 AI 编码时代所有工程团队正在撞上的同一堵墙:产出的加速远快于验证的加速,瓶颈从写代码转移到了验代码。 Linear 的解法不是堆机器,而是一次覆盖基础设施、门禁结构、环境准备、执行效率四个层面的系统级重构。

瓶颈的形状:先看清问题

Agent 让产出代码的速度指数级上升,但验证节奏没跟上——每个 PR 都得过 CI,开发越快,CI 越堵,成本越高,人和 agent 一起等反馈。Linear 的量化背景:自年初以来测试套件规模接近翻了四番,目前每周新增约 2000 个测试。他们优化的目标变量有两个:PR 在 CI 上的等待时长、runner 时间的消耗量。最终成绩:等待从 6 分多钟降到 5 分出头,每测试 runner 时间约减半。

值得强调的是他们的对照估算:如果年初不做这轮优化,今天这套测试要跑约 11 分钟。也就是说,这轮重构的真实收益不是「快了 1 分钟」,而是在测试规模翻四番的情况下守住了等待时间。这是系统级优化和个人小打小闹的本质区别。

路线一:基础设施与工具链

最早的收益几乎不需要动 CI 逻辑本身。把工作负载从 GitHub Actions 迁到第三方 runner(更快 CPU、更强存储、更好缓存)后,做前后各一天的同负载对比:任务平均快 34%,其中 tsc 一项降了 52%。

工具链现代化的收益更可观。换用原生 TypeScript 编译器 tsgo,把周中位 tsc 检查时间砍掉 73%,直接把瓶颈从类型检查上挪走。lint 侧的做法更具启发性:少数自定义 lint 规则依赖类型信息,导致每次 lint 都要构建完整类型图。他们把这些规则重写为基于 AST 的纯语法分析,让 ESLint 完全摆脱 TypeScript——API lint 降 68%,全仓 lint 降 55%,内存占用大降。去类型化还有一个连带好处:后续迁移到 Oxlint 时,纯语法规则几乎可以无损平移。

路线二:优化卡在门口的门禁任务

CI 作为一个系统看,最要命的是那几个「所有任务都在等它」的小任务:路径变更检测、输入相同则跳过的判断。它们被放在关键路径上,八个 API 测试 shard 全都得等它们结束。

三个针对性动作:其一是按需拉取——变更检测类任务原本要 checkout 完整工作树,限制 fetch depth 后最慢的门禁从 94 秒降到 20 秒,完全不需要工作树的任务直接去掉 checkout(27 秒降到 7 秒),需要 diff 的场景用 sparse、blobless 的浅 checkout 再省 11 秒。变更检测中位耗时从 26 秒到 8 秒,p90 从 31 秒到 12 秒,最慢一次从 138 秒到 37 秒。其二是checkout 韧性——第三方 runner 在 GitHub 网络之外,直连链路间歇性劣化会卡死关键路径;换成自带退避重试、设置低速超时(约 30 秒主动失败而非无限挂起)、使用持久磁盘上的 git 镜像缓存的合成 action。其三是关键路径瘦身——把「写缓存标记」这类动作从合并前最后检查挪到测试 shard 结束后的非门禁任务里,每条 API PR 和合并队列条目省 42 秒。

路线三:消灭重复 setup

每个 job 都要启动 runner、装依赖、准备构建环境,几秒钟的有用工作可能消耗几分钟的基础设施。Linear 的四板斧:

  • 预装进基础镜像:每个测试 shard 都在 apt 装 Postgres 客户端(7 到 8 秒),直接做进含 Node 的 CI 基础镜像,顺手把原生编译头也装进去,消除偶发下载挂起的长尾。
  • 按需安装:pnpm workspace 的 API 测试原本装全仓依赖,限定只装 API 包及其依赖后,pnpm install 从 44 到 73 秒降到 16 到 18 秒。
  • 该重建就重建:缓存 node_modules 实测不如重建——见上方 FAQ 展开。
  • 跳过未变化的 setup:API 容器每次重放完整数据库迁移历史,改为 schema 未变时直接加载预生成的快照文件,数据库准备从约 12 秒降到 1 到 2 秒。

另外,七条各自启动 runner、checkout、装依赖、然后只干几秒活儿的短检查,合并成两个 job 内部并发执行。按 6 月用量估算,这一项每月省约 87000 个 runner 分钟,占总 CI 用量的 11.8%。三项 setup 优化叠加后,单 shard 准备时间从 110 到 140 秒降到 67 到 73 秒,降幅约 44%。

路线四:测试执行效率

setup 便宜了,激进并行才划算。Vitest 按文件而非按耗时分配任务,几个超大测试文件就能拖死整个 shard。他们把大文件拆小,shard 从 4 个加到 8 个,关键任务快了约 19% 也便宜了约 19%,一周后最慢 shard 从 5.25 分钟降到 4.33 分钟。

单条最大的优化是共享模块状态:给安全文件开一个 isolate: false 的 opt-in 项目,同 worker 内的文件共用模块注册表,省掉每个文件重建实体、GraphQL、装饰器图的开销。收益约 17% 的月度节省,最慢 shard 从 300 到 379 秒降到约 195 秒,API shard 总 runner 时间从每次约 32.8 分钟降到 22 分钟。但这也是正确性风险最高的一步:每个入选文件要显式 opt-in、补齐共享状态的 teardown,用 fake timer 或共享状态改不干净的文件留在隔离项目里。还有一个细节体现了时代的变化——现在团队里大部分测试是 agent 写的,所以他们直接更新了 agent skill,让生成的测试默认遵守这套性能 opt-in 约束。

最后一步棋点透了整个系统:sharding 的上限是 setup 开销。shard 翻倍,setup 也翻倍——在每 shard 110 到 140 秒的年代,8 个 shard 光 setup 就要 15 到 19 分钟,比测试本身还贵;setup 压到 40 秒后,8 shard 的总 setup 比原来 4 shard 还少,并行翻倍才是净赚。先降固定成本,再加并行度,这个顺序在所有「水平扩展」场景里都成立。

写在最后:给 AI 编码团队的三条迁移建议

Linear 的做法未必能逐条照搬,但三个判断可以立刻带走。第一,把 CI 当系统而不是当任务列表:先测量关键路径,再决定动哪里,他们的多数收益来自「挪出关键路径」而不是「跑得更快」。第二,固定成本是并行的天花板:任何想加并发的地方,先看每单位工作携带多少一次性开销。第三,也是这个时代特有的——agent 写代码的速度只会继续涨,测试量会继续涨,CI 优化不是一次性项目,而是与产出增长赛跑的持续工程。Linear 现在每周还在新增约 2000 个测试,下一个瓶颈已经在路上了。

Frequently asked questions

为什么说 AI 编码让 CI 成为瓶颈?
AI 工具让产出代码的速度指数级提升,但每个 PR 仍然必须过 CI 这一关,验证环节的吞吐没有同步提升,于是整条流水线的节奏被 CI 拖住:开发者等反馈更久、基础设施成本更高。Linear 的观察是,团队今年新增约每周 2000 个测试,测试套件规模接近年初的 4 倍——产出提速的必然结果就是验证压力指数上涨。
Linear 拿到的整体收益有多大?
在测试套件增长近 4 倍的前提下,PR 在 CI 上的等待时间从 6 分多钟降到 5 分多钟,每条测试消耗的 runner 时间大约减半。他们估算如果年初不做这轮优化,今天的测试套件跑完要 11 分钟左右,接近现在等待时间的两倍。另外仅合并七条短检查这一项,每月就省下约 87000 个 runner 分钟,占总用量 11.8%。
单条最大的优化是哪一个?
让安全文件共享模块状态:给 Vitest 开一个 isolate 为 false 的 opt-in 项目,让相互独立的文件共用模块注册表,省掉每个文件重建实体、GraphQL 和装饰器图的开销。这一项占月度节省的 17%,最慢 shard 从 300 到 379 秒降到约 195 秒。但也是正确性风险最高的改动,必须配合显式 opt-in 注释和共享状态的 teardown 才能安全落地。
为什么 Linear 放弃了缓存 node_modules?
实测发现重建反而更快。缓存键依赖频繁变化的 lockfile,即使命中也要约 28 秒恢复,而按包过滤后的安装只要约 7.5 秒。缓存还额外增加了保存时间和结果波动。结论是:缓存不是免费的,只有「命中后恢复成本明显低于重建成本」时才值得保留,否则不如删掉,还能减少一层失效排查的心智负担。
shard 数量是不是越多越好?
不是,shard 的并行收益受每 shard 固定开销限制。shard 翻倍,setup 开销也跟着翻倍:在每 shard setup 110 到 140 秒的年代,8 个 shard 光装环境就要 15 到 19 分钟,超过测试本身。Linear 先把 setup 压到约 40 秒,才让 8 shard 的总 setup 时间低于原来 4 shard 的水平,并行度翻倍变成净赚。先降 setup,再加并行,顺序不能反。
// next.txt ›

Some outbound links in this post are affiliate links — see disclosure.