모노레포 vs 폴리레포 — 코드 저장소 전략과 트레이드오프
프로젝트가 여러 개인데, 하나의 저장소에 다 넣어야 할까요? 아니면 프로젝트마다 저장소를 따로 만들어야 할까요?
코드를 어떤 단위로 저장소에 담을 것인가는 생각보다 개발 경험과 팀 생산성에 큰 영향을 미칩니다. Google, Meta는 거대한 모노레포를, Netflix, Spotify는 폴리레포를 사용합니다. 어떤 전략이 우리 팀에 맞는지 정리해보겠습니다.
이게 뭔가요?
모노레포(Monorepo)
여러 프로젝트(패키지, 서비스, 라이브러리)를 하나의 Git 저장소 에 담는 전략입니다.
my-company/ # 하나의 저장소
├── apps/
│ ├── web/ # 프론트엔드 앱
│ ├── api/ # 백엔드 API
│ └── admin/ # 관리자 페이지
├── packages/
│ ├── shared-ui/ # 공유 UI 컴포넌트
│ ├── auth-utils/ # 인증 유틸리티
│ └── eslint-config/ # 공유 린트 설정
└── package.json
폴리레포(Polyrepo / Multi-repo)
프로젝트마다 별도의 Git 저장소 를 만드는 전략입니다.
my-company-web/ ← 별도 저장소
my-company-api/ ← 별도 저장소
my-company-admin/ ← 별도 저장소
shared-ui/ ← 별도 저장소 + npm 패키지로 배포
auth-utils/ ← 별도 저장소 + npm 패키지로 배포
"모노레포 ≠ 모놀리식"
이 둘을 혼동하면 안 됩니다:
- 모놀리식: 하나의 배포 단위 (아키텍처 개념)
- 모노레포: 하나의 저장소 (코드 관리 전략)
모노레포 안에서 각 프로젝트를 독립적으로 빌드하고 배포할 수 있습니다.
왜 필요한가요?
코드 저장소 전략은 다음에 직접적으로 영향을 줍니다:
- 코드 공유: 공통 라이브러리를 어떻게 배포하고 버전 관리하는가
- 의존성 관리: 패키지 버전 충돌을 어떻게 해결하는가
- CI/CD: 무엇이 변경됐을 때 무엇을 빌드/배포하는가
- 팀 협업: 다른 팀의 코드를 얼마나 쉽게 보고 기여할 수 있는가
어떻게 동작하나요?
모노레포의 구조와 운영
워크스페이스 설정:
// package.json (루트)
{
"name": "my-company",
"private": true,
"workspaces": [
"apps/*",
"packages/*"
]
}
// packages/shared-ui/src/Button.tsx
// 공유 컴포넌트 — apps/web과 apps/admin 모두 사용
export function Button({ children, variant }: ButtonProps) {
return (
<button className={`btn btn-${variant}`}>
{children}
</button>
);
}
// apps/web/src/pages/Home.tsx
// 같은 저장소 내 패키지를 바로 import
import { Button } from '@my-company/shared-ui';
export function Home() {
return <Button variant="primary">시작하기</Button>;
}
핵심 도구 — Turborepo / Nx:
모노레포가 커지면 "전체 빌드"는 너무 느립니다. 모노레포 도구는 이 문제를 해결합니다.
// turbo.json — Turborepo 설정
{
"tasks": {
"build": {
"dependsOn": ["^build"], // 의존하는 패키지를 먼저 빌드
"outputs": ["dist/**"]
},
"test": {
"dependsOn": ["build"]
},
"lint": {} // 의존성 없이 병렬 실행
}
}
# 변경된 패키지만 빌드 (캐시 활용)
npx turbo build --filter=...@my-company/web
# 결과:
# shared-ui: cache hit (변경 없음, 캐시 사용)
# web: building... (변경 있음, 실제 빌드)
핵심 기능:
- 태스크 캐싱: 변경되지 않은 패키지는 이전 빌드 결과를 재사용
- 의존성 그래프 기반 실행: 빌드 순서를 자동으로 결정
- 병렬 실행: 독립적인 태스크를 동시에 실행
- 원격 캐시: 팀원 간 빌드 캐시 공유 (CI 시간 대폭 단축)
폴리레포의 구조와 운영
공유 라이브러리 배포:
# shared-ui 저장소에서
npm version patch
npm publish # npm 레지스트리에 배포
# web 저장소에서
npm install @my-company/shared-ui@latest
버전 관리가 핵심:
// web 저장소의 package.json
{
"dependencies": {
"@my-company/shared-ui": "^2.3.0",
"@my-company/auth-utils": "^1.5.2"
}
}
- shared-ui에 브레이킹 체인지가 생기면 → 메이저 버전 올림 (3.0.0)
- web 저장소에서 새 버전을 업데이트하기 전까지는 영향 없음
- 하지만 그 사이에 "업데이트 부채"가 쌓임
비교
| 측면 | 모노레포 | 폴리레포 |
|---|---|---|
| 코드 공유 | 직접 import, 버전 관리 불필요 | npm 배포 + 버전 관리 필요 |
| 의존성 일관성 | 전체 통일 쉬움 (하나의 lock 파일) | 저장소마다 다른 버전 가능 |
| 원자적 변경 | 여러 패키지를 한 커밋으로 수정 | 여러 저장소에 PR을 각각 올려야 함 |
| CI/CD | 변경 감지 도구 필요 | 저장소별 독립 파이프라인 |
| 권한 관리 | 저장소 수준 세분화 어려움 | 저장소별 독립 권한 설정 |
| Git 성능 | 저장소가 커지면 clone/status 느려짐 | 각 저장소가 가벼움 |
| 온보딩 | 전체 구조를 한눈에 파악 가능 | 필요한 저장소만 clone |
원자적 변경(Atomic Change)이란?
모노레포의 가장 큰 장점 중 하나입니다:
상황: API 응답 형식 변경 (백엔드 + 프론트엔드)
모노레포:
하나의 PR에서 api/와 web/을 동시에 수정
→ 빌드가 성공하면 둘 다 호환이 보장됨
폴리레포:
api 저장소에 PR #1 (API 응답 변경)
web 저장소에 PR #2 (프론트 대응)
→ 둘 중 하나만 먼저 머지되면 일시적으로 깨질 수 있음
자주 헷갈리는 포인트
Google은 모노레포인데 우리도 써야 하나요?
Google은 자체 빌드 도구(Bazel), 자체 버전 관리 시스템(Piper), 전담 인프라 팀이 있습니다. 수십억 줄의 코드를 관리하기 위한 도구를 직접 만들었습니다.
일반 팀에서 모노레포를 쓰려면 Turborepo, Nx 같은 오픈소스 도구가 이 갭을 메워줍니다. 하지만 팀 규모가 작고 프로젝트가 2~3개라면, 모노레포 도구 없이도 충분할 수 있습니다.
모노레포에서 Git이 느려지지 않나요?
저장소가 커지면 git clone, git status 등이 느려질 수 있습니다. 해결책:
- Sparse Checkout: 필요한 디렉토리만 체크아웃
- Shallow Clone: 최근 히스토리만 clone (
git clone --depth 1) - Git LFS: 큰 바이너리 파일을 별도 관리
# Sparse Checkout — apps/web만 체크아웃
git sparse-checkout init
git sparse-checkout set apps/web packages/shared-ui
어떤 전략을 선택해야 하나요?
모노레포가 적합한 경우:
- 프로젝트 간 코드 공유가 빈번
- 여러 프로젝트를 동시에 수정하는 경우가 많음
- 일관된 도구/린트/테스트 설정을 유지하고 싶음
- 팀 규모: 1~30명 정도
폴리레포가 적합한 경우:
- 팀(조직)별로 독립적인 릴리스 주기
- 오픈소스 라이브러리를 공개 배포하는 경우
- 저장소별 엄격한 접근 권한 관리가 필요
- 프로젝트 간 기술 스택이 완전히 다름
하이브리드 전략도 있나요?
실제로 많은 팀이 하이브리드 방식을 사용합니다:
프론트엔드 모노레포/ ← 프론트엔드 앱 + 공유 UI
├── apps/web/
├── apps/admin/
└── packages/shared-ui/
backend-order-service/ ← 백엔드 서비스별 폴리레포
backend-payment-service/
backend-user-service/
프론트엔드처럼 코드 공유가 많은 영역은 모노레포로, 백엔드 서비스처럼 독립성이 중요한 영역은 폴리레포로 운영합니다.
정리
- 모노레포는 여러 프로젝트를 한 저장소에, 폴리레포는 프로젝트마다 별도 저장소에 관리
- 모노레포의 핵심 장점은 코드 공유 용이성과 원자적 변경
- 폴리레포의 핵심 장점은 독립적인 권한/릴리스/파이프라인 관리
- 모노레포 도구(Turborepo, Nx)가 캐싱과 의존성 관리를 자동화해줌
- "모노레포 ≠ 모놀리식" — 모노레포 안에서 독립 배포가 가능
- 하이브리드 전략(영역별로 다른 전략 적용)도 현실적인 선택지