开源 CI 基础设施的性能优化:GitHub Actions Runner 的缓存与并行策略

开源 CI 基础设施的性能优化:GitHub Actions Runner 的缓存与并行策略 开源 CI 基础设施的性能优化GitHub Actions Runner 的缓存与并行策略一、CI 跑了 45 分钟我改了一行注释一个开源项目在 PR 提交后CI 需要运行 45 分钟才能完成全部检查。其中 28 分钟花在重复下载依赖上——每次 PR 都重新npm install、重新下载 Docker 镜像、重新编译。开发者的修改可能只是一行注释但 CI 的体验是完全重新构建。对于开源项目CI 有两类成本时间成本PR 提交到合并的等待时间和资金成本GitHub Actions 的计费分钟数。优化 CI 不是简单地加缓存——不同语言的依赖管理、Docker 构建层、test splitting 都有不同的最优策略。二、CI 缓存的分层策略2.1 依赖缓存Node.js 示例name: CI on: pull_request: push: branches: [main] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 - uses: actions/setup-nodev4 with: node-version: 20 # 关键缓存 node_modules 和 npm 缓存 - name: Cache dependencies uses: actions/cachev4 id: npm-cache with: path: | node_modules ~/.npm key: npm-${{ runner.os }}-${{ hashFiles(package-lock.json) }} restore-keys: | npm-${{ runner.os }}- - name: Install dependencies if: steps.npm-cache.outputs.cache-hit ! true run: npm cirestore-keys是精巧的设计当确切 key 未命中时会尝试模糊匹配前缀最近的缓存。即使package-lock.json变了如新增了一个依赖仍然可以复用之前大部分的node_modulesnpm ci只安装增量部分——安装时间从 120s 降到 8s。2.2 Docker BuildKit 缓存- name: Set up Docker Buildx uses: docker/setup-buildx-actionv3 - name: Build and push uses: docker/build-push-actionv5 with: context: . push: false cache-from: typegha # 从 GitHub Actions Cache 读取 cache-to: typegha,modemax # 保存所有中间层到 CacheDocker BuildKit 的typegha缓存将每一层包括RUN apt-get install的中间层存储到 GitHub Actions Cache。下一次构建时只有变化的层之后的层需要重新构建。对 Dockerfile 做了良好分层优化的项目缓存命中率可达 90%。2.3 Go 模块缓存- uses: actions/setup-gov5 with: go-version: 1.22 cache: true # 自动缓存 go.sum 校验的模块Go 的setup-goaction 内置了缓存策略——基于go.sum的哈希自动缓存GOMODCACHE。开发者和代码无需任何额外配置。三、Test Splitting并行化测试当单机运行测试需要 20 分钟时拆分为多个 job 并行运行jobs: # 策略一按测试文件拆分适合大文件 test-split: strategy: fail-fast: false matrix: shard: [1, 2, 3, 4] # 4 个分片 runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 - run: | # 获取所有测试文件并按分片取模分配 FILES$(find src -name *.test.ts | sort) MY_FILES$(echo $FILES | awk NR % 4 ${{ matrix.shard }} - 1) jest $MY_FILES # 策略二按测试类型拆分适合不同特征的测试 unit-tests: runs-on: ubuntu-latest steps: - run: npm run test:unit integration-tests: runs-on: ubuntu-latest services: postgres: image: postgres:16 steps: - run: npm run test:integration拆分的开销每个分片需要独立的 checkout 缓存恢复 依赖安装。分片数不是越多越好——当单个分片的执行时间接近预热时间约 30-60s时继续拆分反而增加总时间。四、边界与权衡缓存大小的限制GitHub Actions Cache 的总大小限制为 10GBFree Plan 为 10GBPro 更高超出后旧缓存会被自动淘汰。大型 monorepo 需要精简缓存路径不缓存可以快速重新生成的文件如dist/。缓存恢复的 Full GC 问题actions/cachev4在未找到精确匹配时会遍历所有restore-keys直到找到可用缓存。如果项目有上千个缓存条目恢复过程本身可能耗时 30-60s。定期清理旧的缓存 key通过只保留最近 N 天的缓存可以缓解。Docker 镜像的 CI 成本在 CI 中构建 Docker 镜像是作业中最耗时的步骤之一。如果大部分 PR 不需要验证 Docker 构建只在 merge 到 main 时才触发构建。开源项目的免费额度GitHub Actions 对公开仓库免费且不限量。对私有仓库免费额度 2000 分钟/月。需要权衡自建 Runner 的成本EC2 t3.medium ≈ $0.04/hrvs GitHub Actions 的额度消耗。五、总结CI 性能优化的核心策略是缓存不该重复的操作 并行化可拆分的任务。依赖缓存Node.js/Golang/Python/Docker是最低成本的优化通常将 CI 时间减半。Test Splitting 将串行测试变为并行分片需要测试框架支持。实施步骤先审查 CI 的执行时间分布用 GH Actions 的 Job Summary→ 优先缓存最耗时的步骤通常是依赖安装和 Docker 构建→ 确认缓存命中率稳定 80% 后再做 Test Splitting。CI 优化是持续的过程——每季度审视一次 CI 的执行情况新的依赖和测试会逐渐增加 CI 时间。