← 전체 글로 돌아가기

CI/CD

GitHub Actions 배포가 성공해도 웹이 예전 버전일 때 확인한 캐시 비교

CI 초록불만으로 배포 완료를 판단하지 않고 빌드 캐시, CDN, 브라우저 캐시를 나눠 검증한 비교 글입니다.

초록불인데 변경 사항이 안 보였다

문구 하나를 고친 뒤 GitHub Actions는 성공으로 끝났지만, 휴대폰에서는 옛 문구가 계속 보였다. 서버에서 git rev-parse HEAD를 확인하니 최신 커밋이었다. 이때 “배포가 안 됐다”고 뭉뚱그리지 않고, 캐시가 생길 수 있는 위치를 세 구간으로 나눴다.

구간확인 방법의심할 값
빌드 산출물CI 로그와 커밋 SHA잘못된 checkout, 재사용된 산출물
원본 서버curl로 직접 요청프로세스, reverse proxy
사용자 경로응답 헤더와 새로고침CDN, 브라우저 캐시

먼저 산출물의 기준을 고정했다

Actions에서 checkout 단계 뒤에 SHA를 출력하도록 했다. 캐시 키는 브랜치 이름만 쓰지 않고 lockfile과 런타임 버전을 포함했다.

- uses: actions/checkout@v4
- name: Print revision
  run: git rev-parse HEAD
- name: Cache dependencies
  uses: actions/cache@v4
  with:
    path: ~/.npm
    key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}

문제의 실행에서는 SHA가 맞았고 빌드 결과에도 새 문구가 있었다. 따라서 CI 캐시를 지우는 것으로 끝내지 않았다.

원본과 공개 주소를 따로 요청했다

Nginx가 있는 서버의 로컬 포트와 공개 도메인에 각각 요청해 헤더를 비교했다.

curl -sS -D - http://127.0.0.1:3000/ -o /tmp/local.html
curl -sS -D - https://blog.example.com/ -o /tmp/public.html
sha256sum /tmp/local.html /tmp/public.html

원본 HTML은 새 문구였지만 공개 응답에 age가 높게 남아 있었다. CDN 캐시가 원인이었다. HTML은 변경 즉시 반영되어야 해서 s-maxage를 짧게 잡고, 정적 자산에는 해시 파일명을 쓰는 쪽으로 정책을 나눴다.

무엇을 캐시할지 결정하는 표

  • HTML: 짧은 공유 캐시 또는 배포 후 purge
  • 해시가 붙은 JS/CSS: 긴 캐시
  • 관리자 응답: private, no-store
  • 인증이 섞인 응답: 공개 CDN 캐시 금지

이번 일의 완료 조건도 바꿨다. Actions 성공은 빌드 단계의 성공일 뿐이고, 공개 URL의 응답 본문과 헤더까지 확인해야 배포 완료로 기록한다. 브라우저에서 한 번 더 새로고침하는 것보다, 각 경계를 직접 요청하는 편이 훨씬 빨랐다.