
모노레포 vs 폴리레포, 언제 선택하고 무엇을 감수해야 할까
해당 블로그는 Santosh Yadav 원저자의 글 'The case for monorepos, and what they cost you'을 번역한 것입니다. 더 나은 이해를 위해서 약간의 의역이 반영되었습니다.
여러 팀이 같은 코드에 의존하는 상황을 생각해 봅시다. 디자인 시스템, 공용 API, 비즈니스 규칙 묶음을 여러 팀이 함께 쓴다면, 이 코드의 복사본을 저장소마다 따로 두고 동기화하는 일은 시간을 꽤 잡아먹습니다. 모노레포(monorepo)는 이 공유 코드를 한곳에 모읍니다. 그래서 수정은 한 번이면 끝나고 호환성을 깨는 변경은 3주 뒤 다른 팀 서비스에서 조용히 터지는 대신 리뷰 단계에서 바로 걸립니다.
다만 모든 팀이 같은 트리에 합류하는 순간, 잘못된 변경 하나는 모두의 문제가 됩니다. 그리고 코드 리뷰는 부주의한 PR 하나와 그 코드에 의존하는 모든 하류 소비자 사이를 지키는 마지막 방어선이 됩니다.
저는 CodeRabbit에서 개발자 애드보케이트(developer advocate)로 일하고 있습니다. 그전에는 여러 조직이 모노레포를 도입하고 이미 쓰고 있던 모노레포를 확장하도록 돕는 일을 수년간 해 왔습니다. Celonis에서 진행한 대규모 마이그레이션도 그중 하나입니다. 이 글에서는 모노레포가 언제 그만한 가치를 하는지, 모노레포를 실용적으로 만들어 주는 도구는 무엇인지, 그리고 일단 모노레포로 옮긴 뒤 CodeRabbit이 어떻게 이를 리뷰 가능한 상태로 유지하는지를 다룹니다.
모노레포, 폴리레포, 그리고 모놀리스

모노레포에서는 모든 프로젝트가 하나의 저장소 안에 있습니다. 프로젝트마다 서로 다른 기술을 써도 되고 코드를 공유할 수도, 공유하지 않을 수도 있습니다. Google, Meta, Microsoft가 이 방식을 씁니다. 오픈소스 도구들이 등장하면서 훨씬 작은 팀도 같은 구성을 현실적으로 쓸 수 있게 됐습니다. 이 글에서 계속 다시 꺼내 볼 마이그레이션 사례도 바로 이 모델을 따릅니다.

폴리레포(polyrepo)는 팀마다 자기 저장소를 하나씩 갖습니다. 프로젝트 100개가 든 저장소 하나가 아니라, 각각 독립적으로 관리되는 저장소 100개를 두는 셈입니다.
모놀리스(monolith)는 성격이 전혀 다른 개념입니다. 저장소를 어떻게 배치하느냐가 아니라 아키텍처를 가리키며 하나의 단위로 빌드하고 배포하고 테스트하는 애플리케이션을 뜻합니다. 모놀리스는 모노레포 안에서 돌릴 수도 있고 폴리레포에 나눠 담을 수도 있습니다. 이 단어는 코드가 어디에 있느냐가 아니라 소프트웨어가 어떻게 배포되느냐를 설명하기 때문입니다.
폴리레포는 팀이 서로 완전히 독립적이고 다른 팀을 건드리지 않고도 배포할 수 있을 때 제 역할을 합니다. 실패 양상은 분산 모놀리스(distributed monolith)입니다. 저장소는 나뉘어 있는데 결국 함께 배포해야만 하는 상태로, 모노레포의 조율 비용은 그대로 떠안으면서 이점은 하나도 얻지 못합니다.
작은 팀들이 수년간 모노레포를 꺼리게 만든 반론은 빌드 시간이었습니다. 모노레포는 변경이 닿는 모든 것을 다시 빌드합니다. 그래서 공유 로직 한 조각만 고쳐도, 그 변경을 실제로 쓰는 건 셋 중 하나뿐인데도 디자인 시스템과 웹 앱, 모바일 앱을 전부 다시 빌드해야 할 수 있습니다.
빌드 캐싱이 이 문제를 해결했습니다. Nx나 Turborepo 같은 무료 도구는 입력이 바뀌지 않은 프로젝트를 건너뜁니다. 그래서 빌드는 실제로 수정한 부분만 건드립니다. 캐시를 저장할 공간은 필요한데 GitHub Actions는 저장소당 10GB를 무료로 제공하므로, 그 이상을 쓰기 전까지는 S3 버킷이나 원격 캐시 서비스에 비용을 낼 필요가 없습니다. 예전에는 전담 플랫폼 팀이 있어야 풀 수 있던 빌드 속도 문제가 이제는 설정 하나로 정하는 문제가 됐습니다.
모노레포가 정답일 때
판단을 가르는 질문은 하나입니다. 여러분의 팀이 실제로 코드를 공유하는가입니다. 프로젝트가 서로 독립적이고 각자 배포한다면 폴리레포가 더 단순합니다. 여러 팀이 같은 디자인 시스템이나 비즈니스 로직, API를 함께 유지보수한다면 모든 것을 동기화된 상태로 지켜 주는 쪽은 모노레포입니다.
이 패턴은 대개 아무 문제 없어 보이게 시작합니다. 한 팀이 독립적인 제품을 하나 내놓습니다. 그 제품이 점점 커지면서 디자인 시스템이나 마이크로서비스를 다른 앱과 공유하기 시작하죠. 서류상으로는 모든 게 독립적입니다. 하지만 청구서는 나중에 날아옵니다. 같은 공유 코드의 라이브 버전 여러 개를 동시에 유지보수하게 되고 업그레이드할 때마다 그 전부를 빌드하고 배포해야 하는 순간이 오거든요.
모노레포는 여러 서비스에 걸친 원자적 변경(atomic change)이 필요할 때도 유리합니다. 모든 앱이 의존하는 마이크로서비스에 호환성을 깨는 변경을 넣는다고 해 봅시다. 폴리레포에서는 그 수정이 여러 저장소로 흩어지고 프로덕션이 깨진 뒤 되돌리는 일은 영향을 받는 모든 팀이 얽히는 조율 문제가 됩니다. 모노레포에서는 같은 변경이 단일 히스토리를 가진 커밋 하나로 들어옵니다. 되돌리기 한 번이면 모든 곳에서 원상 복구됩니다.
같은 논리가 대규모 리팩터링에도 적용되는데 바로 여기서 폴리레포는 발이 묶입니다. 여러분이 의존하는 변경을 다른 팀이 내놓을 때까지 아무것도 못 하고 기다리게 되죠. 시간이 지나면 이런 구조는 문화도 바꿉니다. 같은 유틸리티를 다섯 번씩 새로 만드는 대신 공유 코드를 재사용하도록 팀을 이끌거든요.
예전에 다니던 회사에서 저희 팀은 서로 긴밀하게 연결된 하나의 제품을 만들고 있었습니다. 그런데 폴리레포 아래에서는 스쿼드마다 공유 디자인 시스템 위에 자기 프런트엔드를 따로 만들고 있었습니다. Angular를 한 번 업그레이드하려면 팀마다 저장소를 하나씩 찾아다녀야 했습니다. 그야말로 고역이었죠.
2년 남짓에 걸쳐 저희는 모두를 Nx 모노레포 하나로 모았고 그 변화는 숫자로 드러났습니다. 릴리스는 가끔 하던 수준에서 일주일에 40개 앱 이상으로 늘었습니다. Angular 업그레이드와 새 도구 도입은 더 이상 '프로젝트'가 아니라 일상적인 일이 됐고 내부 라이브러리를 이용한 코드 공유가 드디어 원래 의도대로 작동하기 시작했습니다.
그렇다고 폴리레포가 틀렸다는 뜻은 아닙니다. 폴리레포는 다른 질문에 답하는 방식일 뿐이고 서로 무관한 프로젝트를 억지로 한 저장소에 몰아넣으면 그것대로 또 다른 문제가 생깁니다.
폴리레포가 더 나은 선택일 때
폴리레포에도 분명한 쓰임새가 있습니다. 그중 하나는 먼저 짚어 둘 만합니다. 모노레포는 여러 개를 운영할 수 있습니다. 그래서 "모든 것을 한 저장소에 두지 않는다"가 "프로젝트마다 저장소 하나씩"을 뜻하지는 않습니다.
팀들이 서로 코드를 공유하지 않고 각자의 주기로 배포하는 무관한 제품을 소유하고 있다면 따로 두세요. 이럴 때는 모노레포가 주는 이점이 없고 저장소를 분리해 두면 릴리스 시점과 고객에게 보이는 체인지로그를 깔끔하게 유지할 수 있습니다.
컴플라이언스 때문에 분리가 협상 불가능한 조건이 되기도 합니다. 정부 계약이나, 지정된 사람만 코드를 만질 수 있어야 한다고 요구하는 고객이라면 나머지 전부와 벽으로 분리된 저장소가 가장 잘 맞기 때문입니다. 오픈소스 공개도 흔한 경우입니다. 전용 공개 저장소를 두면 나머지 코드베이스를 끌고 들어가지 않고도 외부 기여자를 받아들일 수 있거든요.
CodeRabbit으로 모노레포를 건강하게 유지하기
결정을 내리고 모노레포로 옮겼다고 해 봅시다. 베팅의 값을 치러야 할 때가 바로 여기입니다. 모든 이점은 팀들을 같은 트리에 모은 데서 나왔고 바로 그 가까움 때문에 이제 부주의한 PR 하나가 하류의 모두에게 가닿습니다. 바쁜 모노레포에서 이를 잡아내는 리뷰어는 일주일에 수십 개의 PR을 처리하는 사람이고 사람은 무언가를 놓치기 마련입니다.
여기에 더 새로운 압박이 하나 얹힙니다. 풀 리퀘스트에 담긴 코드 가운데 팀의 누구도 직접 쓰지 않은 비중이 점점 커지고 있습니다. 코딩 에이전트가 그 코드를 만들었고 리뷰어는 이제 자기가 작성하지도 않았고 온전히 이해하지도 못할 수 있는 코드를 평가합니다. 그러면 질문이 "나라면 이렇게 짰을까"에서 더 어려운 쪽으로 바뀝니다. "이게 실제로 무슨 일을 하고 어디서 깨지는가"로요.
CodeRabbit이 AI가 작성한 PR과 사람이 작성한 PR을 분석해 보니 AI가 만든 변경은 평균적으로 약 1.7배 더 많은 문제를 안고 있었고 가독성 문제는 3배 이상 많았습니다. 모노레포에서는 이 코드가 모두의 공유 로직 바로 옆에 자리 잡습니다. 그래서 이해의 간극도 함께 짊어지는 문제가 됩니다.
CodeRabbit은 반복적인 1차 검토를 대신 맡습니다. 그래서 리뷰어는 아키텍처와 의도에 주의를 집중할 수 있습니다. 모노레포에서 CodeRabbit을 둘 만한 이유는 몇 가지입니다.
- 코드 그래프를 따라 변경을 추적하고 PR이 병합되기 전에 호환성을 깨는 API 변경을 그 영향을 받는 호출부와 대조해 표시합니다.
- 리뷰를 시작할 때마다 쉬운 말로 된 요약과 워크스루, 아키텍처 다이어그램을 함께 제시합니다. 그래서 낯선 변경을 마주한 리뷰어가 그 변경이 옳은지 판단하기 전에 무슨 일을 하는지부터 파악할 수 있습니다.
- 패키지 전반에서 스타일과 컨벤션을 일관되게 유지하고 빠진 문서를 짚어 줍니다. 공유 코드가 가장 빨리 썩는 지점이 바로 문서거든요.
- 50개가 넘는 린터와 SAST 스캐너를 인라인으로 실행하고 획일적인 체크리스트를 들이대는 대신 시간이 지나며 팀의 피드백에서 배웁니다.
이 가운데 무엇도 사람의 리뷰를 대체하지 않습니다. 사람을 더 빠르게 만들고 그냥 두면 서로 멀어졌을 팀들 사이에서도 기준을 지켜 줍니다.
이 맥락이 하나의 트리에만 갇혀 있는 것도 아닙니다. 폴리레포라면 Linked Repositories와 멀티 레포 분석(Multi-Repo Analysis)이 같은 추론을 관련 저장소들로 확장합니다. 그래서 API 스키마에서 어떤 필드의 이름이 바뀌면, 그 필드를 파싱하는 서비스가 다른 저장소에 있더라도 해당 서비스와 대조해 문제를 드러냅니다. 마이크로서비스나 공유 SDK, 프런트엔드와 백엔드가 분리된 팀에게 바로 이 점이 중요합니다.
관련해서 멀티 레포 분석으로 여러 저장소를 함께 읽는 법과 저장소 자동 연결을 함께 보시면 도움이 됩니다. RAG와 에이전트 방식을 비교한 에이전트형 코드 리뷰 vs RAG도 추천 드립니다.