왜 첫 빌드는 항상 느린가
CI 파이프라인에서 캐시는 대부분 "빌드가 끝난 뒤 저장"하는 방식으로 동작한다. 문제는 캐시가 처음 만들어지기 전, 즉 캐시 키가 바뀐 직후의 첫 실행이다. 이때는 의존성 다운로드, 컴파일러 툴체인, 베이스 이미지 레이어를 전부 새로 받아야 하므로 빌드 시간이 평소의 2~4배로 튀어 오른다.
이 현상은 package-lock.json이나 go.sum이 바뀌는 주간 의존성 업데이트, 새 브랜치 생성, 러너 교체 직후에 집중적으로 나타난다. 개발자가 PR을 열고 결과를 기다리는 순간에 하필 캐시 미스가 겹치면 체감 대기 시간이 크게 늘어난다.
워밍업 잡의 기본 아이디어
핵심은 "캐시가 필요해지는 시점"과 "캐시를 만드는 시점"을 분리하는 것이다. 사람이 기다리지 않는 시간(야간, 혹은 main 머지 직후)에 별도의 워밍업 잡을 돌려 캐시를 미리 채워 둔다. 이후 실제 PR 빌드는 이미 준비된 캐시를 복원만 하면 되므로 첫 빌드도 빠르게 끝난다.
중요한 전제는 워밍업 잡과 실제 빌드가 동일한 캐시 키를 공유해야 한다는 점이다. 키가 어긋나면 워밍업은 그저 CI 자원만 낭비한다.
GitHub Actions 워밍업 예시
스케줄과 main push에서 캐시만 채우는 잡을 분리한다. 실제 PR 워크플로는 같은 키로 캐시를 복원한다.
name: cache-warm
on:
schedule:
- cron: "0 18 * * *" # 매일 새벽(UTC 18시)
push:
branches: [main]
jobs:
warm:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/cache@v4
with:
path: ~/.npm
key: npm-${{ hashFiles('package-lock.json') }}
# 캐시를 채우는 것이 목적이므로 install까지만 수행
- run: npm ci --prefer-offline
PR 워크플로에서는 동일한 key와 함께 restore-keys로 부분 적중을 허용해 두면, 락파일이 살짝 바뀌어도 이전 캐시를 발판으로 다운로드량을 줄일 수 있다.
Docker 레이어와 툴체인 워밍업
언어 의존성뿐 아니라 도커 빌드 캐시, 컴파일 캐시(sccache, ccache)도 워밍업 대상이다. registry 캐시를 쓰면 워밍업 잡이 만든 레이어를 다른 러너가 그대로 재사용할 수 있다.
#!/usr/bin/env bash
set -euo pipefail
CACHE_REF="registry.example.com/app:buildcache"
docker buildx build \
--cache-to "type=registry,ref=${CACHE_REF},mode=max" \
--cache-from "type=registry,ref=${CACHE_REF}" \
--target builder \
--load -t app:warm .
echo "warmed layers pushed to ${CACHE_REF}"
mode=max는 중간 레이어까지 저장하므로 히트율이 높아지지만 캐시 용량도 커진다. builder 스테이지만 대상으로 삼아 불필요한 런타임 레이어 저장을 피하는 것이 요령이다.
어디까지 캐시할지 선택하기
모든 것을 캐시하면 복원 시간과 저장 용량이 도리어 병목이 된다. 다운로드 비용이 크고 잘 안 바뀌는 것부터 우선순위를 둔다.
| 대상 | 워밍업 효과 | 주의점 |
|---|---|---|
| 패키지 의존성(npm/pip/go) | 높음 | 락파일 키 정확히 일치 |
| Docker 베이스/빌더 레이어 | 높음 | registry 용량 관리 |
| 컴파일 캐시(ccache 등) | 중간 | 키가 넓으면 오염 위험 |
| 테스트 산출물 | 낮음 | 신선도 문제로 비추천 |
운영 시 주의점
첫째, 캐시 오염을 막아야 한다. 워밍업 잡이 잘못된 상태를 저장하면 이후 모든 빌드가 그 오염을 상속한다. 워밍업은 항상 검증된 브랜치(main) 기준으로만 저장하고, PR 빌드에서는 원칙적으로 캐시를 쓰기만 하고 덮어쓰지 않게 분리한다.
둘째, 만료와 용량이다. 대부분의 CI 캐시는 크기 상한과 미사용 만료 정책이 있다. 워밍업을 너무 자주 돌리면 오래된 유효 캐시가 밀려나 오히려 히트율이 떨어진다. 스케줄 주기는 의존성 변경 빈도에 맞춰 하루 1~2회 정도로 잡는다.
셋째, 효과를 반드시 측정한다. 워밍업 전후의 "캐시 미스 상황에서의 첫 빌드 시간"과 캐시 히트율을 지표로 남겨야 한다. 측정 없이 워밍업만 늘리면 절약한 시간보다 워밍업에 쓴 러너 비용이 더 커질 수 있다.