왜 의존성 설치가 병목이 되는가

CI 파이프라인의 러너는 대부분 매 실행마다 깨끗한 환경에서 시작한다. 그래서 npm install, pip install, go mod download 같은 명령이 매번 수백 개의 패키지를 네트워크에서 다시 받아온다. 코드 빌드나 테스트는 몇 초인데 의존성 설치에만 2~5분이 걸리는 경우가 흔하다. 하루 수백 번 돌아가는 파이프라인이라면 이 낭비는 러너 비용과 개발자 대기 시간으로 그대로 이어진다.

핵심은 의존성이 거의 바뀌지 않는다는 점이다. lock 파일이 동일하면 설치 결과도 동일하다. 그렇다면 이 결과물을 저장해 두고 재사용하지 않을 이유가 없다.

캐시할 대상을 정확히 고르기

흔한 실수는 node_modulesvenv 디렉터리 자체를 캐시하는 것이다. 이 방식은 네이티브 바이너리가 러너 OS/아키텍처에 종속되거나, 부분 설치 상태가 그대로 굳어버리는 문제가 있다. 더 안전한 대상은 패키지 매니저의 전역 다운로드 캐시다. 다운로드는 건너뛰되 설치(링크/컴파일)는 다시 수행하므로 무결성이 보장된다.

도구캐시 대상 경로키에 쓸 lock 파일
npm~/.npmpackage-lock.json
pip~/.cache/piprequirements.txt
Go~/go/pkg/modgo.sum
Maven~/.m2/repositorypom.xml

캐시 키 설계가 전부다

캐시의 성패는 키(key) 설계에서 갈린다. 키는 lock 파일의 해시를 포함해야 한다. 의존성이 바뀌면 해시가 바뀌어 새 캐시를 만들고, 그대로면 기존 캐시를 정확히 맞춘다. 여기에 OS와 언어 버전을 접두어로 붙여 환경 간 오염을 막는다. 완전 일치가 없을 때를 대비한 restore-keys(부분 일치 폴백)를 함께 두면, lock이 조금 바뀌어도 이전 캐시 위에 증분 설치만 하게 되어 효과가 크다.

# GitHub Actions 예시: pip 다운로드 캐시
- name: Cache pip
  uses: actions/cache@v4
  with:
    path: ~/.cache/pip
    key: ${{ runner.os }}-py3.11-${{ hashFiles('requirements.txt') }}
    restore-keys: |
      ${{ runner.os }}-py3.11-
- run: pip install -r requirements.txt

도커 빌드에서의 레이어 캐시

컨테이너 이미지를 빌드한다면 별도의 캐시 액션보다 Dockerfile 레이어 순서가 더 중요하다. lock 파일만 먼저 복사해 설치 레이어를 만들고, 소스 코드는 그다음에 복사한다. 이렇게 하면 소스만 바뀌었을 때 설치 레이어가 캐시에서 재사용된다.

FROM node:20-slim
WORKDIR /app

# 1) lock 파일만 먼저 복사 → 이 레이어는 의존성이 바뀔 때만 무효화
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm \
    npm ci

# 2) 소스는 나중에 복사 → 코드 변경이 설치 레이어에 영향 없음
COPY . .
RUN npm run build

--mount=type=cache(BuildKit)는 레이어가 무효화되어도 npm 다운로드 캐시를 유지하므로, lock이 바뀐 경우에도 변경분만 새로 받는다.

주의점: 캐시 무효화와 오염

캐시는 잘못 쓰면 디버깅이 가장 어려운 버그의 원천이 된다. 반드시 지킬 것들을 정리한다.

  • lock 파일 없는 키 금지. 날짜나 커밋 해시를 키에 넣으면 매번 미스가 나거나, 반대로 영원히 갱신되지 않는다.
  • 결정적 설치 명령 사용. npm install 대신 npm ci, pip은 해시 고정된 lock을 쓴다. 설치 명령 자체가 비결정적이면 캐시 신뢰도가 무너진다.
  • OS·아키텍처 분리. 네이티브 모듈(예: bcrypt, psycopg)은 러너가 바뀌면 재빌드가 필요하므로 키에 아키텍처를 포함한다.
  • 이상 시 우회 경로. 손상된 캐시로 인한 실패를 대비해 캐시를 무시하고 클린 설치하는 수동 트리거를 하나 남겨둔다.

효과 측정과 유지관리

캐시를 넣었다면 반드시 캐시 적중률(hit rate)과 설치 단계 소요 시간을 로그로 남겨 추적한다. 적중률이 낮다면 키에 불안정한 값이 섞였을 가능성이 높다. 또 대부분의 CI 서비스는 캐시에 용량 상한과 만료 정책이 있으므로, 오래된 lock 조합이 쌓여 한도를 넘기면 정작 필요한 캐시가 밀려난다. 주기적으로 사용하지 않는 캐시를 정리하고, restore-keys 접두어를 좁게 유지하는 것이 장기적으로 적중률을 지키는 방법이다. 캐시는 한 번 설정하고 끝나는 것이 아니라, lock 전략과 함께 관리해야 하는 파이프라인의 일부다.