为什么你的 GitHub Actions CI 如此缓慢(以及如何加速)

为什么你的 GitHub Actions CI 如此缓慢(以及如何加速)

解决工作流程中隐蔽性能损耗的简单方法

无人察觉的问题

当工作流程失败时,GitHub 会给你发送邮件。但当你的 CI 耗时比预期多出一倍时?你什么通知也收不到。每次拉取请求(pull request)、每次提交(commit)都在运行缓慢的构建,一次又一次地重新编译相同的依赖项——这是藏在眼皮子底下的浪费。

截至 2026 年 6 月 30 日,CI 性能比以往任何时候都更加重要。开发团队更加庞大,部署频率更快,缓慢反馈循环带来的成本在整个团队中不断累积。如果你的 CI 耗时 15 分钟而不是 5 分钟,且你的团队每周提交 40 个 PR,你们每周将共同浪费 8 小时在等待机器上。这相当于整整一个人一整周的空闲等待时间。然而,大多数团队从未看到过对此问题发出警报的仪表板。

为什么会发生这种情况

GitHub Actions 设置简单,这也是它广泛普及的原因之一。但这种简单性意味着看似合理的默认设置往往隐藏着巨大的性能陷阱。你创建了一个工作流程,它运转正常,除非有明显报错,否则你不会再留意它。与此同时,该工作流程可能正在做许多不必要的重复工作:每次运行都重新安装软件包、上传庞大的缓存构建产物(artifacts)、因工作流程矩阵(workflow matrix)中的拼写错误而运行两次测试,或者使用需要极长时间才能启动的旧镜像。

常见的罪魁祸首很容易排查和修复。让我们一一梳理。

常见的罪魁祸首

每次都重新构建依赖项

最容易犯的错误之一就是没有缓存依赖项。如果你的工作流程在每次运行时都安装相同的 npm 软件包、Go 模块或 Python 库,那么你每天都在为完全相同的文件多次支付网络传输、解包和验证的成本。

大多数语言都内置了缓存策略。对于 Node.js,GitHub Actions 提供了一个缓存 action,用于存储 node_modules 或两次运行之间的 lock 文件。对于 Python,你可以缓存 pip wheels。Java 拥有 Maven 和 Gradle 缓存。如果你没有使用它们,你的 CI 就在做冗余工作。

缓慢或臃肿的镜像

GitHub Actions 工作流程在容器中运行。你选择的镜像决定了每次运行仅启动所需的基准时间。使用旧的或体积过大的基础镜像(例如包含你不需要的构建工具的完整 Ubuntu),每次运行都会消耗几分钟时间。

尽可能切换到精简镜像。如果你需要特定工具,可以构建一次自定义 Docker 镜像,将其推送到像 GitHub Container Registry 这样的镜像仓库并重复使用。首次运行构建镜像需要较长时间,但后续运行可以即时拉取。

无意中运行了两次任务

工作流程矩阵(workflow matrices)功能强大但容易配置错误。一个常见的错误是为不同的 Node 版本或操作系统设置了矩阵,但随后发现实际上只想运行一次。每一次重复运行都会消耗 CPU、存储空间和时间。

检查你的工作流程 YAML。如果发现有矩阵在做不必要的重复工作,请将其扁平化或添加仅在特定分支上运行的条件。

庞大或不必要的构建产物(artifacts)

如果你的工作流程在每次运行后都上传庞大的构建产物或日志,GitHub 将花费时间对其进行存储和压缩。如果你在下游阶段实际上并没有使用这些构建产物,那么它们就是纯粹的浪费。

明确你要上传的内容。你需要整个构建目录,还是只需要最终的二进制文件?你需要成功运行的日志,还是仅在失败时需要?设置保留策略(retention policy),以免旧的构建产物堆积。

可以并行运行的串行步骤

如果你的工作流程依次运行 lint、测试、构建,那么你在部分时间内仅使用了一个 CPU 核心。默认情况下,GitHub Actions 可以并行运行多个任务(jobs)。如果你有独立的步骤,请将它们拆分为单独的任务。

一个简单的例子:lint 代码检查速度很快且不需要完整的构建。将其作为独立的任务运行。如果失败了,你无需等待测试即可立即获知。如果通过了,测试和构建可以同时并行运行。

如何衡量迟缓程度

在优化之前,你需要一个基准。GitHub 在 Actions 选项卡中提供了工作流程时间线。打开最近的一次运行,查看每个任务和步骤的持续时间。你几乎总能发现至少一个比预想中慢得多的步骤。

记录下这些数字。在做出更改后,回来进行对比。看到“CI 从 12 分钟减少到 5 分钟”令人满意,并且证明优化起了作用。

分步优化清单

步骤 1:启用依赖项缓存

为你的语言添加一个缓存步骤。对于 Node.js,将以下内容添加到你的工作流程中:

- uses: actions/setup-node@v4
  with:
    node-version: '20'
    cache: 'npm'

其中的 cache: 'npm' 行告诉 GitHub 在两次运行之间自动保存和恢复你的 node_modules 。其他语言也有类似的选项——请查看适用于你的语言的官方 action。

步骤 2:审计你的基础镜像

如果你的工作流程使用的是 runs-on: ubuntu-latest,请检查该镜像包含的内容。如果你只需要 Node.js,请考虑切换到官方 Node 镜像(docker://node:20)或 GitHub 的轻量级版本。如果你需要自定义设置,可以构建并推送一次精简镜像。

步骤 3:将缓慢的任务拆分为并行任务

检查你的工作流程 YAML。如果你有不相互依赖的顺序任务,请拆分它们。例如:

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm run lint

  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm run test

现在 lint 和测试可以同时运行,而不是先进行 lint 再进行测试。

步骤 4:移除冗余的构建产物

检查你的工作流程中是否有 actions/upload-artifact。如果其他任务未使用该构建产物,且不需要手动下载,请将其删除。如果你保留构建产物是为了调试,请设置较短的保留时间:

- uses: actions/upload-artifact@v4
  if: failure()
  with:
    name: logs-on-failure
    retention-days: 7

这仅在失败时上传日志,并在一周后自动删除。

步骤 5:检查矩阵重复

在你的工作流程中搜索 matrix: 并审查每一个。如果矩阵实际上没有改变你想要的行为,请将其删除。使每次构建都运行两次的矩阵纯粹是累赘。

步骤 6:审查步骤顺序

在一个任务内,每个步骤按顺序运行。确保顺序合理。如果某个步骤很慢但其结果在很久之后才用到,考虑将其移到靠近被调用的位置。如果某个步骤很快且互相独立,请将其移到最前面,以便能够及早报错失败。

持续监控

完成优化后,不要一放了之。CI 工作流程会发生偏移——依赖项变大、添加了新步骤、镜像发生变更。设置每月提醒以抽查最慢的工作流程。如果它呈现变慢趋势,请在其引发危机之前进行调查。

一些团队将 GitHub Actions 指标导出到监控仪表板。这是较为高级的做法,但如果你有许多工作流程,通过自动显现偏移趋势,收益将远超成本。

结论

缓慢的 CI 是对团队开发速度征收的一种隐形税,绝不会出现在账单上。它在每周数以百计的运行中悄悄累积。修复方法并不困难——缓存、并行任务、消除浪费——但它们需要首先发现问题。本周花一个小时检查你的工作流程。你很可能会在每次运行中找出 5 到 10 分钟浪费的时间。这是送给未来的你的一份礼物。

优势

  • 更快的反馈循环能让开发者保持通畅并专注于工作状态。
  • 随着运行完成得更快,降低了云端计算成本。
  • 及早发现问题(例如,拆分后的任务能更早报错失败)。
  • 更好的开发者体验;相比等待 15 分钟,大家更愿意等待 3 分钟。
  • 微小的改进在每周几十次的运行中累积,能节省大量时间。
  • 容易衡量;仪表板数据已经在 GitHub 中可用。

劣势

  • 需要审计和维护;工作流程如果不进行审查就无法保持优化状态。
  • 过度的缓存可能会掩盖依赖项缺陷,直到进入生产环境才暴露。
  • 过度拆分任务会增加复杂性;任务越多意味着需要调试的内容越多。
  • 精简镜像有时缺乏后续所需的工具,需要重新调整。
  • 构建产物清理策略存在丢失调试所需日志的风险。
  • 只有在拥有足够的并发配额时,并行化才有用;GitHub 的免费层额度有限。

注意事项

本文中的所有名称、配置值和占位符(例如 node:20, ubuntu-latest, app.example.com)仅用于示例目的。在将任何工作流程部署到生产环境之前,请在预发布分支上对其进行充分测试。你的具体配置、语言版本和基础架构可能会有所不同。在一个项目中效果良好的缓存策略可能不适合另一个项目。务必在实际环境中验证性能提升,并监控副作用(例如过期缓存导致依赖项陈旧)。风险自负。

常见问题

  • GitHub Actions 上每个仓库的最大缓存容量是多少?
  • 如何知道 GitHub Actions 缓存是否真的在我的工作流程中发挥作用?
  • 我可以使用自定义 Docker 镜像来加速 CI 吗?
  • 为什么创建缓存后的首次运行耗时更长,而不是更短?
  • 如何在不增加并发配额使用的情况下在 GitHub Actions 中并行化任务?
  • 缓存 node_modules 更好还是仅缓存 lock 文件更好?
  • 缓慢的 CI 运行会导致 GitHub 自动取消我的工作流程吗?
  • 我应该多久审查并更新一次 GitHub Actions 工作流程?

标签

#github #actions #ci #cicd #devops #performance #optimization #workflows #automation #testing

Free field guide

Prompt-Injection Defense Checklist

The controls that actually reduce the blast radius when your app feeds untrusted text to an LLM. Enter your email — you'll get the PDF instantly, plus new posts on AI, security & Linux.