pnpm store 캐시로 PR 검증 시간 줄이기

PR을 올리면 타입 검사, 린트, 테스트, 빌드가 각각 별도의 GitHub Actions 작업으로 실행돼요.

네 작업은 같은 pnpm-lock.yaml을 사용하지만 서로 다른 러너에서 시작해요. 그래서 각 작업이 같은 패키지를 다시 내려받고 있었고, 실제 검사보다 pnpm install을 기다리는 시간이 길 때도 있었어요.

이 시간을 줄이려고 pnpm store를 캐시하고, develop에 반영된 직후에는 pnpm fetch로 다음 캐시를 미리 채웠어요.

pnpm store가 뭘까

pnpm store는 내려받은 패키지 파일을 모아두는 공용 저장소예요.

예를 들어 여러 프로젝트에서 같은 버전의 React를 사용해도 React 파일을 프로젝트마다 새로 저장하지 않아요. pnpm은 패키지 내용을 기준으로 store에 한 번 보관하고, 각 프로젝트의 node_modules에서는 그 파일을 연결해서 사용해요.

패키지 레지스트리
       │ 다운로드
       ▼
pnpm store
       │ 연결
       ▼
프로젝트의 node_modules

현재 사용 중인 store 위치는 아래 명령으로 확인할 수 있어요.

pnpm store path

CI에서는 이 경로를 캐시해두면 다음 실행에서 같은 패키지를 다시 다운로드하지 않아도 돼요.

다만 store와 node_modules는 달라요. store 캐시를 복원해도 각 작업에서 pnpm install은 실행해야 해요. 패키지를 내려받는 대신 store에 있는 파일로 node_modules를 빠르게 구성하는 거예요.

lockfile을 기준으로 store 캐시하기

어떤 버전의 패키지가 필요한지는 pnpm-lock.yaml에 기록돼요. 그래서 lockfile의 해시를 캐시 키로 사용했어요.

- name: Find pnpm store path
  id: pnpm-store
  run: echo "path=$(pnpm store path)" >> "$GITHUB_OUTPUT"

- name: Cache pnpm store
  uses: actions/cache@v6
  with:
    path: ${{ steps.pnpm-store.outputs.path }}
    key: pnpm-store-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
    restore-keys: |
      pnpm-store-${{ runner.os }}-

lockfile이 같으면 타입 검사, 린트, 테스트, 빌드가 같은 store 캐시를 복원해요. 의존성이 바뀌어 lockfile 해시가 달라지면 새로운 캐시를 만들어요.

캐시를 복원한 뒤에는 평소처럼 install을 실행하면 돼요.

pnpm install --prefer-offline --frozen-lockfile

--prefer-offline은 store에 있는 패키지를 우선 사용하고, 없는 패키지만 레지스트리에서 받아요. 캐시가 비어 있어도 install이 실패하지 않아서 PR 검증 작업에서 사용하기 편해요.

pnpm fetch로 캐시 미리 채우기

lockfile이 바뀐 직후에는 아직 새 캐시가 없어요. 네 검증 작업이 동시에 시작되면 네 작업 모두 새 의존성을 받게 돼요.

이때 pnpm fetch를 사용할 수 있어요.

pnpm fetch --frozen-lockfile

pnpm fetch는 lockfile을 보고 필요한 패키지를 store에 내려받아요. node_modules를 만들거나 테스트를 실행하지는 않아요. 말 그대로 나중에 사용할 패키지만 미리 가져와요.

그래서 develop에 lockfile 변경이 반영되면 store 캐시를 복원한 뒤 pnpm fetch를 실행했어요.

on:
  push:
    branches: [develop]
    paths:
      - pnpm-lock.yaml

steps:
  # pnpm 설정과 store 캐시 복원

  - name: Fetch dependencies
    run: pnpm fetch --frozen-lockfile

이 작업이 끝나면 새 lockfile에 맞는 store 캐시가 생겨요. 이후 develop을 대상으로 열리는 PR은 미리 채워진 캐시를 복원할 수 있어요.

물론 lockfile을 처음 바꾼 PR의 첫 실행까지 빨라지는 것은 아니에요. 아직 그 캐시가 만들어지기 전이기 때문이에요. 대신 merge 이후 실행되는 작업과 다음 PR부터 같은 패키지를 반복해서 내려받는 일을 줄일 수 있어요.

마무리

이번에 캐시한 것은 node_modules가 아니라 pnpm store예요.

store는 내려받은 패키지 파일을 모아두는 곳이고, pnpm install은 그 파일로 프로젝트의 node_modules를 만들어요. pnpm fetch는 install보다 먼저 store만 채울 때 사용할 수 있어요.

각 검증 작업의 install 과정은 남아 있지만, 같은 패키지를 네 번씩 네트워크에서 받는 시간은 줄었어요. CI 작업이 여러 개라면 각 작업이 반복하는 다운로드부터 캐시할 수 있는지 확인해보는 것도 좋아요.

참고