GitHub Actions 缓存失效的 4 种真实场景和对应解法

GitHub Actions 缓存失效解决指南

每次提交代码,GitHub Actions 都要重新安装一遍依赖?同一个项目,别人的 CI 跑 1 分钟,你的跑 8 分钟?问题大概率出在缓存策略上。本文总结 4 种最常见的缓存失效场景,配合可运行的配置文件和踩坑记录,帮你把 CI 时间从 8 分钟压到 90 秒。

场景一:不同操作系统 runner 的缓存路径差异

Ubuntu、macOS、Windows 三个系统,pip 缓存路径完全不同:

  • Ubuntu:~/.cache/pip
  • macOS:~/Library/Caches/pip
  • Windows:C:\Users\runneradmin\AppData\Local\pip\Cache

如果你在 Ubuntu 的 runner 里缓存了 ~/.cache/pip,把 key 写死在配置里,到了 macOS 的 runner 根本找不到这个路径,缓存形同虚设。

解法:统一用 actions/cache 的内置 key 生成逻辑,让它自动处理路径差异。

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Cache pip packages
        uses: actions/cache@v4
        with:
          path: ~/.cache/pip
          key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}
          restore-keys: |
            ${{ runner.os }}-pip-

注意这里的 key 包含了 ${{ runner.os }},确保不同 OS 的缓存相互独立。restore-keys 用前缀匹配,即使精确 key 没命中,也能找到最接近的缓存。

场景二:restore-keys 和 save-only 模式混用导致的缓存未保存

actions/cache 的 save-only 模式是 v4 新增的,默认是 true。这意味着如果你用 restore-keys 匹配到旧缓存,系统会自动恢复,但不会自动保存新缓存——除非显式设置 save: always

踩坑场景:requirements.txt 每次都有微小改动(无关紧要的注释变更),导致 cache key 变化。restore-keys 找到了旧缓存,恢复成功,但新的依赖缓存没有保存,CI 速度没有任何提升。

# ❌ 错误配置:只恢复,不保存
- uses: actions/cache@v4
  with:
    path: ~/.cache/pip
    key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}
    restore-keys: |
      ${{ runner.os }}-pip-

# ✅ 正确配置:显式保存
- uses: actions/cache@v4
  with:
    path: ~/.cache/pip
    key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}
    restore-keys: |
      ${{ runner.os }}-pip-
    save: always

场景三:actions/cache v4 的 key 格式变化导致历史缓存全部失效

v3 和 v4 的 hashFiles 行为不同:

  • v3:只 hash 文件内容的前 1MB
  • v4:hash 完整文件内容

如果你从 v3 升级到 v4,所有基于 hashFiles('requirements.txt') 的 key 都会变化——即使文件内容完全一样。这意味着升级后第一个 CI run 一定会 miss 掉所有旧缓存。

更隐蔽的坑:requirements.txt 如果包含时间戳或随机版本号(有些 CI 模板会这样做),每次生成的 hash 都不同,缓存永远无法命中。

# ✅ 正确:确保 requirements.txt 内容稳定
# 先运行 pip-compile 锁定版本
- name: Generate requirements.txt
  run: |
    pip install pip-tools
    pip-compile --generate-hashes -o requirements.txt pyproject.toml

# 然后再用 hashFiles 缓存
- uses: actions/cache@v4
  with:
    path: ~/.cache/pip
    key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}
    restore-keys: |
      ${{ runner.os }}-pip-
    save: always

场景四:没有 lock 文件,依赖版本漂移导致缓存无效

最常见的问题:requirements.txt 里写的是 requests>=2.28,每次 CI 跑的版本可能都不一样。hashFiles 只管文件内容,不管实际装的是哪个版本——所以即使缓存命中了,实际安装的包版本可能和上次不同。

用 pip-compile 生成锁文件,版本完全固定:

# pyproject.toml
[tool.pip-tools]
generate-hashes = true
output-file = "requirements.txt"

# requirements.in
requests>=2.28
pytest>=7.0
# .github/workflows/ci.yml
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.12'

      - name: Cache pip packages
        uses: actions/cache@v4
        with:
          path: ~/.cache/pip
          key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}
          restore-keys: |
            ${{ runner.os }}-pip-
          save: always

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt

      - name: Run tests
        run: pytest tests/

运行 pip-compile --generate-hashes -o requirements.txt 后,requirements.txt 里每个包都带上了 hash:

requests==2.31.0     --hash=sha256:58cd2187c01e...     --hash=sha256:def21d6b4...
pytest==7.4.3     --hash=sha256:6b8e1a4...     --hash=sha256:c0d3578...

完整配置模板

把以上所有最佳实践整合到一个可复制的模板:

name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ['3.10', '3.11', '3.12']

    steps:
      - uses: actions/checkout@v4

      - name: Setup Python ${{ matrix.python-version }}
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}

      - name: Cache pip packages
        uses: actions/cache@v4
        with:
          path: ~/.cache/pip
          key: ${{ runner.os }}-py${{ matrix.python-version }}-pip-${{ hashFiles('requirements.txt') }}
          restore-keys: |
            ${{ runner.os }}-py${{ matrix.python-version }}-pip-

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt

      - name: Run tests
        run: pytest -v tests/

效果对比

用 pip-compile + actions/cache v4 优化后,同一个项目的 CI 耗时对比:

场景 优化前 优化后
首次运行(无缓存) 8 分 12 秒 8 分 05 秒
依赖无变化 7 分 48 秒 1 分 23 秒
requirements.txt 微调 7 分 51 秒 1 分 45 秒

关键提升在于第二、三行:缓存命中时,pip 直接从本地缓存安装,跳过了下载和编译过程。

现在你可以做什么

  1. 检查你的 GitHub Actions 配置里是否用了 actions/cache@v4
  2. 确认 key 里是否包含 runner.os
  3. 运行 pip-compile 生成锁文件,替换掉 requirements.txt
  4. 加上 save: always,避免 restore-only 模式不保存的问题

如果你的项目还在用 v3 的缓存配置,建议尽快升级到 v4,并同步检查 lock 文件的策略。升级后第一个 PR 会慢一些(缓存 miss),但后续会快很多。

© 版权声明
THE END
喜欢就支持一下吧
点赞14 分享