AI编码使CI成为瓶颈,因此我们重新设计了CI流水线以跟上步伐
摘要
Linear重新设计了他们的CI流水线,以应对AI编码带来的瓶颈,在优化速度和成本的同时扩大测试覆盖范围。
暂无内容
查看缓存全文
缓存时间: 2026/09/21 21:46
# AI编程使CI成为瓶颈,因此我们重新设计了持续集成流程
来源:https://linear.app/now/ci-bottleneck-reworked
今年早些时候,我打开Linear时发现我们的CTO Tuomas指派了一个问题给我,标题是“CI成本过高”。他还希望我能让CI运行得更快。
智能编程工具让代码发布速度呈指数级增长,但验证变更的速度却未能同步跟上。每个代码变更请求仍需通过CI流水线,因此随着开发加速,CI反而成为瓶颈,推高基础设施成本,让开发者和智能工具等待更久才能获得反馈。
在Linear追求更高效CI的过程中,我们重点优化了两个指标:PR在CI中的等待时间以及运行器占用时长。尽管年初至今我们的测试用例数量几乎翻了四倍,但我们仍将PR等待时间从超过6分钟缩短至刚过5分钟,同时将每个测试的运行器耗时削减了约一半。
性能指标图表:一月至九月期间测试覆盖率提升(蓝线)与机器耗时降低(白线)趋势
性能指标图表:一月至九月期间测试覆盖率提升(蓝线)与机器耗时降低(白线)趋势
这是以一月首周为基准的测试套件性能表现。白线代表每个测试的机器耗时,在我们添加测试分片(缩短等待时间但增加机器耗时)时出现峰值,在代码检出卡顿问题期间再次出现波动。
总体而言,我们从四个方面改进了CI:
- 升级基础设施与工具链
- 优化关键路径任务
- 减少重复性配置
- 提升测试执行效率
Linear代码库以TypeScript为主,但多数优化方案适用于各类语言和工具链。
### 升级基础设施与工具链 (https://linear.app/now/ci-bottleneck-reworked#upgraded-infrastructure-and-tooling)
部分早期收益几乎不需要优化CI本身。通过将工作负载从GitHub Actions迁移至采用更快CPU、更高性能存储和更优缓存基础设施的第三方运行器,我们获得了运行相同流水线的更高速设备。对比切换前后的两天数据,任务平均运行速度提升34%,其中`tsc`等特定任务耗时降低52%。
此外,现代化工具链也带来显著收益。切换至原生TypeScript编译器`tsgo`后,每周`tsc`检查的中位时间缩短73%,足以让瓶颈完全脱离类型检查环节。
#### 无需类型检查器的代码分析 (https://linear.app/now/ci-bottleneck-reworked#lint-without-the-type-checker)
代码静态分析是早期优化重点。我们的一些自定义分析规则依赖TypeScript类型信息,无论是执行约束检查还是自动修复。这意味着每次运行分析都必须构建完整的类型图谱,使其成为CI中内存消耗最大的任务之一。
我们重写规则,改为基于抽象语法树进行静态分析,在不依赖类型信息的情况下识别类函数结构和模式守卫。这使得ESLint完全摆脱TypeScript依赖,API分析时间减少68%,完整仓库分析时间减少55%。内存占用也大幅下降。
移除类型依赖也为后续迁移至Oxlint (https://oxc.rs/docs/guide/usage/linter)创造了便利,因为纯语法操作的规则易于移植。Oxlint本身也减少了分析任务的CI运行器耗时。
### 优化关键路径任务 (https://linear.app/now/ci-bottleneck-reworked#optimize-the-jobs-that-gate-other-work)
随着底层基础设施和单项检查速度提升,我们开始从系统视角审视CI。这使我们注意到位于所有任务前端的小型任务。每次运行都始于检查PR变更了哪些路径,以及这些测试是否已在相同输入下通过。我们在任务级别设置门控检查,确保跳过的任务不会占用运行器,但这也将其直接置于关键路径上。所有八个API测试分片必须等待它们完成,即使微小延迟也会产生重大影响。
#### 仅获取每个任务所需内容 (https://linear.app/now/ci-bottleneck-reworked#fetch-only-what-each-needs)
多个工作流始于变更检测任务,用于决定后续执行内容;例如检查差异是否包含数据库迁移,并输出信号调度相关数据库CI检查。这些任务之前会检出完整工作目录,尽管只需其中小部分内容。我们限制了获取深度,将最慢的检测任务耗时从94秒降至20秒,并对不需要工作目录的任务完全移除检出步骤,使其耗时从27秒降至7秒。对于提交推送和合并队列事件(需要进行路径差异比对),我们发现稀疏、无blob的检出配合有限历史记录已足够使用,额外节省约11秒。
性能分布直方图:蓝色柱状(优化后8秒中位数)与灰色柱状(优化前26秒中位数),展示优化后的加载时间缩减
性能分布直方图:蓝色柱状(优化后8秒中位数)与灰色柱状(优化前26秒中位数),展示优化后的加载时间缩减
变更检测任务中位时间从26秒降至8秒,P90值从31秒降至12秒,最慢运行时间从138秒降至37秒。
#### 增强检出流程稳定性 (https://linear.app/now/ci-bottleneck-reworked#make-checkout-more-resilient)
更换底层运行器基础设施后,我们发现任务中的检出时间(使用`actions/checkout`)变长且偶尔会挂起。由于第三方运行器位于GitHub网络外部,依赖直连IP访问GitHub。服务商将挂起问题追溯至该链路的间歇性性能下降。多个工作流以检出开头,获取延迟可能拖慢整个CI运行。
为增强网络不稳定性下的韧性,我们用自定义的复合动作替代了`actions/checkout`,该动作采用退避重试机制,并设置`GIT_HTTP_LOW_SPEED_LIMIT`和`GIT_HTTP_LOW_SPEED_TIME`,使停滞连接在约30秒后中止而非挂起,同时启用检出缓存(在持久化磁盘上维护git镜像)。这大幅减少了关键路径任务因等待检出完成而闲置的情况。
#### 最小化关键路径内容 (https://linear.app/now/ci-bottleneck-reworked#minimize-what's-on-the-critical-path)
并非所有关键路径任务都必须存在。我们之前将缓存标记写入合并前的最终检查阶段,这意味着即使测试通过,PR仍可能滞留于合并队列。我们将该操作移至测试分片完成后且不设门控的任务中,为每个API PR和合并队列条目节省42秒。
这些变更共同作用,使API PR在缓存未命中时所需检查时间减少约一分钟,同时降低了运行器启动开销。
### 减少重复配置 (https://linear.app/now/ci-bottleneck-reworked#reduce-repeated-setup)
随后我们转向减少每个任务的重复配置成本,包括启动运行器、安装软件包和配置构建依赖。这意味着仅执行数秒有效工作的任务可能消耗数分钟基础设施时间。以下是我们的应对措施:
#### 在CI镜像中预装共享依赖 (https://linear.app/now/ci-bottleneck-reworked#preinstall-shared-dependencies-in-the-ci-image)
每个API测试分片每次运行都要用apt安装相同的Postgres客户端,耗时7-8秒。我们将其整合至包含Node和客户端的轻量级CI基础镜像中,使每个分片可直接在就绪环境中启动。后续发现下载原生构建头文件偶尔会导致卡顿,我们也将其加入镜像,缩短了尾部延迟。
#### 仅安装每个任务所需依赖 (https://linear.app/now/ci-bottleneck-reworked#install-only-the-dependencies-each-job-needs)
Linear代码库是采用pnpm工作区的单体仓库。我们的API测试工作流之前安装整个工作区,实际只需API包及其依赖。将安装范围限制为API包后,pnpm install耗时从44-73秒缩短至16-18秒。我们对API相关任务也应用了相同模式,这些任务原本安装完整仓库并上传依赖缓存,但后续运行几乎无法命中缓存。
#### 当重建快于缓存时选择直接重建 (https://linear.app/now/ci-bottleneck-reworked#don't-cache-when-it's-faster-to-rebuild)
我们测试了`node_modules`缓存效果,发现直接重建更快。缓存键依赖频繁更新的锁文件,即使缓存命中仍需约28秒恢复,而筛选安装仅需约7.5秒。缓存增加了保存时间和不稳定性,却未带来明显收益。
这三项变更共同将每个分片配置时间缩减约44%,从110-140秒降至67-73秒。
GitHub Actions工作流对比:左侧运行(4分57秒)与右侧运行(3分14秒)的test-api任务逐阶段耗时分解,展示流水线各环节性能提升
GitHub Actions工作流对比:左侧运行(4分57秒)与右侧运行(3分14秒)的test-api任务逐阶段耗时分解,展示流水线各环节性能提升
测试分片P95时长对比
此外,我们还消除了其他形式的重复配置:
#### 避免重放未变更配置 (https://linear.app/now/ci-bottleneck-reworked#avoid-replaying-unchanged-setup)
部分配置仅在输入变更时才需重复执行。例如我们的API容器每次运行都会重放完整数据库迁移历史,即使PR未修改Schema。对此类情况,我们改用生成的Schema快照和引导文件替代,将每个容器的数据库配置时间从约12秒缩短至1-2秒。
#### 合并短时检查至更少任务 (https://linear.app/now/ci-bottleneck-reworked#batch-short-checks-into-fewer-jobs)
七个独立检查原本各自启动运行器、检出仓库、安装依赖,仅执行数秒有效工作。我们将它们整合至两个任务中,并在任务内并发执行七项任务。这使重复配置开销从七次降至两次。基于六月使用数据,此变更每月节省约87,000运行器分钟,相当于总CI使用量的11.8%。
CI/CD流水线批处理优化前后对比:展示测试工作流各步骤执行时间与依赖关系,所有任务均标记为成功完成
CI/CD流水线批处理优化前后对比:展示测试工作流各步骤执行时间与依赖关系,所有任务均标记为成功完成
### 提升测试执行效率 (https://linear.app/now/ci-bottleneck-reworked#make-test-execution-more-efficient)
随着每个测试分片固定成本下降,我们得以更积极地并行化API测试套件。它是工作流中规模最大且执行频率最高的部分,其改进对合并时间有倍增效应。
#### 按照测试运行器视角平衡负载 (https://linear.app/now/ci-bottleneck-reworked#balance-work-the-way-the-test-runner-sees-it)
我们用于TypeScript测试套件的Vitest (https://vitest.dev/) 按文件而非单个测试时长分配任务。这意味着少数异常庞大的测试文件可能主导某个分片,有效拖慢整个套件的完成进度,即使其他分片早已完成。
我们在保持测试结构的同时将大文件拆分为更小、更专注的文件,随后评估不同的分片和运行器配置。今年早些时候我们已从三个分片增至四个;增至八个使关键任务在初始基准测试中速度提升约19%,成本降低19%。变更一周后,最慢分片从5.25分钟降至4.33分钟。
Vitest通常隔离每个测试文件,对我们而言意味着每个测试分片都要重建实体、GraphQL和装饰器图谱。我们引入了采用`isolate: false`的可选Vitest项目,允许安全文件在每个工作器内共享模块注册表。
模块状态缓存优化:之前(每文件独立状态)与之后(单一共享状态),减少跨工作器进程的重复构建
模块状态缓存优化:之前(每文件独立状态)与之后(单一共享状态),减少跨工作器进程的重复构建
这是我们单次最大性能改进,在当前规模下价值约17%的月度成本节约。最慢分片从约300-379秒降至约195秒,同时API分片总运行器耗时从约32.8分钟降至22分钟。
这也成为正确性风险最高的优化。我们通过每个文件的可选注释明确资格要求,并为共享状态添加必要的清理逻辑。少数使用模拟计时器或共享状态的文件因无法安全处理,仍保留在隔离项目中。由于智能工具现在编写了我们大部分测试,我们更新了相关智能工具技能以适配此性能优化项,使生成的测试默认遵循相同约束。
#### 分片受配置开销限制 (https://linear.app/now/ci-bottleneck-reworked#sharding-is-limited-by-setup-overhead)
只有当分片固定成本足够低时,增加分片才划算,因为分片数量加倍也会使工作流配置时间加倍。我们之前提到的配置优化使八个分片变得可行。若按每个分片110-140秒计算,八个分片仅配置就要占用15-19分钟运行器时间,超过测试本身耗时。现在配置时间约40秒,八个分片的总配置时间少于之前四个分片,同时测试并行度提升两倍。
测试分片优化:从4分片(8.3分钟配置时间)增至8分片(7.5分钟配置时间),实现更好的测试负载并行化
测试分片优化:从4分片(8.3分钟配置时间)增至8分片(7.5分钟配置时间),实现更好的测试负载并行化
配置优化前,测试任务使用4个分片,配置耗时8.3分钟。优化后,我们可运行8个分片,配置时间7.5分钟。
### 在系统中产生复合效应的改进 (https://linear.app/now/ci-bottleneck-reworked#improvements-that-compound-across-a-system)
若非今年初我们刻意改进CI,如今的测试套件将耗时约11分钟,接近当前开发者等待时间的两倍。工作并未止步于此。显然我们的代码库将持续增长;目前每周新增约2000个测试。在此过程中保持CI快速运行将是持续性工作,许多优化经验将用于解决新瓶颈。
CI/CD流水线各环节性能改进分析:展示从工具链、门控任务、配置到整体指标的耗时缩减(-12%至-89%)
CI/CD流水线各环节性能改进分析:展示从工具链、门控任务、配置到整体指标的耗时缩减(-12%至-89%)
相似文章
AI编码工具已强大到能真正交付产品,但对学习而言却成问题
一位开发者反思AI编码工具如何提升生产力,但可能阻碍深度学习——用户交付了不完全理解的代码,引发关于技能发展的疑问。
Agentic Code Review(15分钟阅读)
分析AI编码代理如何将瓶颈从编写代码转移到审查代码,数据显示代码变更量增加861%,缺陷率上升,使得代码审查成为软件工程中最具杠杆效应的技能。
AI 改变软件重写的经济性
文章认为,AI 的编码效率取决于代码库的一致性,这使得重写在经济上可行,以使代码库与 AI 的优势对齐,从而提高输出质量和速度。
AI编码助手没有智能问题,它们有的是运行时纪律问题。以下是我如何强制执行它。
一位开发者介绍了agent-rigor,这是一个开源框架,它将运行时纪律和传统SDLC机制强制应用于AI编码助手,以防止常见的代理失败,如范围蔓延和修复-前向循环。
大规模编排AI代码审查
Cloudflare构建了一个CI原生的AI代码审查编排系统,使用多达七个由协调器管理的专业代理,在数千个合并请求中提供结构化、准确的审查。