Published on

GitHub Actions 캐시가 안먹을 때 9가지 원인

Authors

서론

CI가 느려지는 가장 흔한 이유는 “캐시가 안 먹는다”입니다. GitHub Actions는 actions/cache(또는 setup-node/setup-python의 내장 캐시)를 통해 의존성 디렉터리나 빌드 산출물을 저장·복원할 수 있지만, 실제 운영에서는 Cache not found for input keys가 반복되거나, 분명 restore는 됐는데도 설치/빌드가 다시 도는 경우가 잦습니다.

이 글에서는 GitHub Actions 캐시가 안 먹는 9가지 원인을 “증상 → 진단 포인트 → 해결” 순서로 정리합니다. 마지막에는 바로 붙여 넣어 쓸 수 있는 권장 캐시 패턴 예제도 제공합니다. (캐시 무효화 사고 패턴은 Docker에서도 비슷하게 발생하니, 빌드 캐시 관점은 Docker 빌드 캐시가 무효화되는 원인 7가지도 함께 참고하면 좋습니다.)


1) 캐시 key가 매번 바뀐다(너무 자주 invalidation)

대표 증상

  • 항상 Cache not found for input keys가 뜬다.
  • 매 실행마다 새로운 key가 찍힌다.

흔한 원인

  • key에 github.run_id, github.run_number, github.sha 등을 넣어 실행마다 다른 값이 포함됨
  • hashFiles('**/*')처럼 범위가 너무 넓어 사소한 파일 변경에도 hash가 바뀜

해결

  • key는 “의존성 결정 요소”만 포함: OS, 런타임 버전, lockfile hash 정도
  • 변경이 잦은 파일(README, 테스트 스냅샷, 빌드 아티팩트)을 hash 범위에서 제외
- uses: actions/cache@v4
  with:
    path: |
      ~/.npm
      node_modules
    key: npm-${{ runner.os }}-node20-${{ hashFiles('package-lock.json') }}
    restore-keys: |
      npm-${{ runner.os }}-node20-
      npm-${{ runner.os }}-

2) restore-keys가 없거나 너무 구체적이다(부분 일치 복원 실패)

대표 증상

  • lockfile이 조금만 바뀌면 캐시가 완전히 미스
  • “거의 같은 환경”인데도 매번 처음부터 설치

진단 포인트

  • restore-keys가 비어 있거나, key와 동일한 수준으로 구체적

해결

  • restore-keys는 점진적으로 덜 구체적인 prefix를 제공
  • 예: npm-OS-nodeVer-lockHashnpm-OS-nodeVer-npm-OS-
restore-keys: |
  npm-${{ runner.os }}-node20-
  npm-${{ runner.os }}-

3) 캐시 path가 잘못됐거나(상대/절대/홈), 실제로는 비어 있다

대표 증상

  • Cache saved successfully인데 다음 실행에서 효과가 없다
  • restore는 되는데 설치 시간이 그대로

진단 포인트

  • 캐시 대상 디렉터리가 실제로 생성되는지 확인
  • ls -al로 워크플로 내에서 경로 존재/용량 확인

해결

  • 러너 OS별 경로 차이를 고려
  • 패키지 매니저의 “다운로드 캐시”와 “설치 결과물”을 구분
- name: Inspect cache dirs
  run: |
    echo "$HOME"
    ls -al ~/.npm || true
    ls -al node_modules || true

- uses: actions/cache@v4
  with:
    path: |
      ~/.npm
    key: npm-${{ runner.os }}-${{ hashFiles('package-lock.json') }}

> 팁: Node는 ~/.npm(tarball 캐시)만 캐시해도 npm ci가 빨라지는 경우가 많고, node_modules까지 캐시하면 플랫폼/네이티브 모듈 이슈가 늘 수 있습니다.


4) 캐시 restore 시점이 늦다(설치/빌드 이후에 복원)

대표 증상

  • 로그에 restore가 보이긴 하지만, 이미 의존성 설치가 끝난 뒤
  • 캐시가 “먹은 것처럼 보이는데” 시간이 안 줄어듦

진단 포인트

  • actions/cache step이 npm ci, pip install, gradle build보다 아래에 위치

해결

  • 설치/빌드 전에 restore가 이뤄지도록 step 순서를 조정
steps:
  - uses: actions/checkout@v4

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

  - run: pip install -r requirements.txt

5) 권한/이벤트 타입 문제: PR from fork에서는 저장이 안 된다

대표 증상

  • 내부 브랜치 PR에서는 캐시가 쌓이는데, 포크 PR에서는 매번 미스
  • save cache 단계가 스킵되거나 권한 관련 메시지

원인

  • 보안상 포크 PR은 기본적으로 GITHUB_TOKEN 권한이 제한됨
  • 캐시 저장이 막히거나, 접근 범위가 달라짐

해결

  • 포크 PR에서는 “restore만” 기대하고, 저장은 push/workflow_dispatch에서 수행
  • 필요한 경우 pull_request_target을 신중히 사용(코드 인젝션 위험)
- uses: actions/cache@v4
  if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false
  with:
    path: ~/.npm
    key: npm-${{ runner.os }}-${{ hashFiles('package-lock.json') }}

6) 캐시 스코프(브랜치/키 네임스페이스) 착각

대표 증상

  • main에서 만든 캐시가 feature 브랜치에서 안 보인다(혹은 반대)
  • 같은 key를 쓰는데도 서로 못 찾음

설명

GitHub Actions 캐시는 **키뿐 아니라 스코프(브랜치/기본 브랜치/PR 등)**의 영향을 받습니다. 보통은 restore-keys로 완화되지만, “어디에서 생성된 캐시를 어디에서 재사용할지” 전략이 없으면 체감상 캐시가 계속 미스처럼 보입니다.

해결

  • 기본 브랜치(main)에서 캐시를 잘 쌓고, 다른 브랜치에서는 restore-keys로 최대한 끌어오기
  • 또는 브랜치명을 key에 넣되, 공통 prefix를 restore-keys로 제공
key: npm-${{ runner.os }}-${{ github.ref_name }}-${{ hashFiles('package-lock.json') }}
restore-keys: |
  npm-${{ runner.os }}-main-
  npm-${{ runner.os }}-

7) 캐시 크기/파일 수가 너무 커서 저장·복원이 비효율적이거나 실패

대표 증상

  • 캐시 저장/복원 자체가 오래 걸려서 전체 시간이 줄지 않는다
  • 간헐적으로 캐시 저장이 실패하거나 타임아웃

진단 포인트

  • 캐시 step이 수 분 이상 걸림
  • node_modules, .gradle, target, dist 등 거대한 디렉터리를 통째로 캐시

해결

  • “다운로드 캐시” 위주로 캐시(예: npm/pip/gradle/cargo registry)
  • 빌드 산출물은 캐시가 아니라 아티팩트로 분리(필요 시)
  • 캐시 대상에서 불필요한 디렉터리 제외
# Gradle 예시: 의존성 캐시 위주
- uses: actions/cache@v4
  with:
    path: |
      ~/.gradle/caches
      ~/.gradle/wrapper
    key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
    restore-keys: |
      gradle-${{ runner.os }}-

8) setup-* 내장 캐시와 actions/cache를 중복 사용해 충돌/헷갈림

대표 증상

  • setup-node에서 cache: 'npm'을 켰는데도 별도 actions/cache를 또 사용
  • 어떤 캐시가 적용되는지 로그가 혼란스럽고, 기대한 경로가 캐시되지 않음

해결

  • 한 가지 방식으로 단순화
    • Node: actions/setup-node의 내장 캐시 사용 권장
    • Python: actions/setup-python + cache: 'pip'
    • 그 외 커스텀 경로는 actions/cache로 명시
- uses: actions/setup-node@v4
  with:
    node-version: 20
    cache: 'npm'
    cache-dependency-path: package-lock.json

- run: npm ci

9) lockfile/의존성 결정 파일이 워크스페이스/모노레포에서 누락된다

대표 증상

  • 모노레포에서 일부 패키지만 변경했는데도 캐시가 전혀 재사용되지 않음
  • 반대로, 캐시가 재사용되는데 의존성 불일치로 빌드가 깨짐

진단 포인트

  • hashFiles()에 모노레포의 모든 lockfile이 포함되는지
  • pnpm/yarn workspaces의 경우 실제 의존성 결정 파일이 무엇인지 확인

해결

  • 모노레포 구조에 맞춰 해시 대상을 정확히 지정
  • pnpm은 pnpm-lock.yaml, yarn berry는 .yarn/cache 전략 등 도구별 베스트 프랙티스 적용
# 모노레포(여러 패키지)에서 모든 lockfile을 반영
key: node-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
  node-${{ runner.os }}-

실전 권장 템플릿: “진단 로그 + 안전한 캐시 키”

아래 템플릿은 캐시가 안 먹을 때 원인을 빠르게 좁히기 위해 키/복원/디렉터리 상태를 함께 출력합니다.

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

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

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

      - name: Compute cache key inputs
        run: |
          echo "ref=${GITHUB_REF}"
          echo "ref_name=${GITHUB_REF_NAME}"
          echo "sha=${GITHUB_SHA}"
          echo "lock_hash=${{ hashFiles('package-lock.json') }}"

      - uses: actions/cache@v4
        with:
          path: ~/.npm
          key: npm-${{ runner.os }}-node20-${{ hashFiles('package-lock.json') }}
          restore-keys: |
            npm-${{ runner.os }}-node20-
            npm-${{ runner.os }}-

      - name: Inspect npm cache dir
        run: |
          ls -al ~/.npm || true
          du -sh ~/.npm || true

      - run: npm ci
      - run: npm test

마무리: “캐시 미스”는 대부분 설계/스코프/경로 문제다

GitHub Actions 캐시는 마법이 아니라 키 설계(변경 빈도), 복원 범위(restore-keys), 경로 정확성, **이벤트 권한(PR/fork)**에 의해 거의 결정됩니다. 캐시가 안 먹는 상황을 만났다면,

  1. key가 너무 자주 바뀌는지, 2) restore-keys가 있는지, 3) 실제 캐시 경로가 맞는지, 4) step 순서가 올바른지부터 확인하세요.

추가로, CI/배포 파이프라인에서 권한 이슈로 예상치 못한 실패가 난다면 OIDC 쪽도 함께 점검해야 합니다. AWS 배포에서 토큰/권한이 꼬이는 케이스는 GitHub Actions OIDC AWS 배포 InvalidIdentityToken 해결에 정리해두었습니다.