목차
모노레포 CI의 병렬 빌드와 Turborepo 캐시 사전 점검 (2025-06)
원문 정보
- 원문: FE Core팀의 CI 속도전: 캐시 전략을 활용한 병렬 빌드
- 매체 · 게시일: 쏘카 기술 블로그 · 2025-06-11
- 저자: 아놀드 (쏘카 FE Core팀)
- 원문 내용을 바탕으로 재구성한 요약입니다.
병합할 때마다 전체를 검증해야 하는 모노레포
Turborepo 기반 모노레포에는 팀이 서로 다른 30여 개의 상용 프로젝트가 있습니다. 배포 일정이 제각각이라 main 병합이 잦고 pnpm-lock.yaml도 자주 바뀝니다. core 라이브러리 버전은 고정하고 caret 범위를 제한했지만, 하위 패키지의 caret 사용까지는 통제할 수 없었습니다. 의존성이 바뀌었을 수 있으니 main에 변경이 생기면 모든 프로젝트를 다시 빌드해 검증해야 합니다.
CI에는 이미 Kubernetes 위의 Turborepo 원격 캐시 서버와 dorny/paths-filter@v3를 통한 불필요한 빌드 제거가 있었습니다. 그래도 캐시를 못 맞추면 Next.js 앱 30여 개를 빌드하는 데 평균 20분이 넘게 걸렸습니다. 워크플로우가 겹치면 병합 대기가 30분을 넘었고, 32분을 넘긴 실행에서는 “The operation was canceled.” 오류로 강제 종료되는 일이 반복됐습니다. 빌드를 건너뛸 수는 없으니 빌드 시간 단축이 첫 과제였습니다.
Runner 증설과 Matrix 병렬화
먼저 Ubuntu Runner의 메모리와 코어 수를 올렸습니다. 평균 빌드 시간이 20분대에서 10분대로 줄었고 중단 오류도 사라졌습니다. 그래도 단일 프로세스로 30개 프로젝트를 빌드하면 10분 넘게 걸려서 GitHub Workflow의 Matrix를 도입했습니다.
한 job이 빌드 대상 패키지 목록을 스크립트로 만들면, 그 목록을 받은 build job이 패키지마다 pnpm build --filter 로 병렬 실행됩니다. fail-fast: false로 두어 하나가 실패해도 나머지는 계속 돌고, 프로젝트별 성공 여부가 status check로 따로 보입니다.
효과는 캐시 상태에 따라 갈렸습니다. 캐시를 못 맞출 때는 10분 27초에서 8분 6초로 22.5% 줄었지만, 캐시가 맞을 때는 1분 14초에서 6분 57초로 늘었습니다. 병렬화에 드는 오버헤드 때문이라고 원문은 설명합니다.
병렬화 전에 캐시 상태부터 확인하기
팀은 Matrix 앞에 turbo run build --dry-run을 두어 모든 패키지의 캐시 상태를 먼저 확인합니다. 전부 캐시되어 있으면 빌드 단계를 통째로 건너뛰고, 아니면 캐시가 없는 패키지만 Matrix 대상으로 삼습니다. 스크립트가 빌드할 패키지 수를 출력으로 내보내고, 그 값이 0이면 빌드 단계를 스킵하는 조건을 걸었습니다.
이 구조에서는 캐시를 못 맞출 때 5분 29초로 47.5% 줄었고, 캐시가 맞을 때도 1분 11초로 기존보다 3초 빨랐습니다. 병렬화로 늘었던 시간이 사라진 셈입니다.
브랜치 보호용 단일 검증 job
Matrix를 쓰면 빌드 결과가 프로젝트마다 별도의 status check로 남습니다. 브랜치 보호 정책을 하나의 status로 관리하려고, 모든 Matrix 빌드가 끝난 뒤 성공을 확인하는 verify-build job을 따로 두었습니다.
네 가지 전략을 합친 결과 캐시를 못 맞출 때 30분대였던 빌드가 5분대로 줄어, 최대 84%를 단축했습니다. 팀은 프로젝트 수가 늘어도 속도와 안정성이 떨어지지 않는 구조를 마련했다고 정리합니다.
단계별 빌드 시간 비교
| 단계 | 캐시 미적중 | 캐시 적중 |
|---|---|---|
| Runner 사양 변경 전 평균 | 20분대 | 해당 없음 |
| Runner 사양 변경 후 평균 | 10분대 | 해당 없음 |
| 전체 빌드(Matrix 이전 기준) | 10m27s | 1m14s |
| Matrix 적용 | 8m6s (-22.5%) | 6m57s (+463.5%) |
| Matrix와 dry-run 적용 | 5m29s (-47.5%) | 1m11s (-4%) |