Monorepo는 AI 시대에 어디에 서게 될까?

2026. 8. 17. · 11분 · DongHyeon Yu

Monorepo는 AI 시대에 어디에 서게 될까?

모노레포는 고통에서 나왔습니다. 프로젝트마다 저장소를 따로 두면 코드가 깔끔하게 나뉠 것 같지만, 실제로는 같이 바뀌어야 할 것들이 따로 바뀌기 시작합니다. 공유 라이브러리의 버전이 저장소마다 어긋나고, 기능 하나가 PR 서너 개로 흩어지고, 전체를 관통하는 리팩토링은 아무도 손대지 못하게 됩니다. 그래서 구글 같은 회사들은 아예 코드 전체를 한 트리에 뒀고, 그 방식이 Lerna와 Turborepo 같은 도구를 타고 보통 크기의 팀에게도 내려왔습니다. 같이 바뀔 것은 같이 둔다. 모노레포가 태어난 이유는 그 한 줄입니다.

저도 그래서 씁니다. 이 블로그와 데이터베이스 스키마, 지인을 위해 만든 앱까지 한 저장소 안에 있고, Turborepo가 그 전체를 굴립니다. 그런데 요즘 코드를 쓰는 건 대부분 제가 아니라 에이전트입니다. 그러다 보니 예전에는 없던 질문이 생겼습니다. AI가 코드를 쓰는 시대에, 모노레포는 어떤 자리에 서게 될까.

먼저 좋았던 장면부터 꺼내 보겠습니다. 며칠 전 이 블로그에 로케일별 커버 이미지를 넣으면서 데이터베이스 컬럼을 옮겼습니다. 스키마 마이그레이션과 그걸 읽는 앱 코드가 같은 저장소에 있으니, 변경이 PR 하나에 담기고 CI 하나가 둘을 함께 검증했습니다. 저장소가 갈라져 있었다면 두 PR의 배포 순서라는 문제가 하나 더 생겼겠죠. 규칙도 마찬가지입니다. 코딩 컨벤션을 적은 파일이 저장소 루트에 한 벌, 앱마다 한 벌 있는데, 에이전트가 어느 구석에서 일하든 같은 규칙을 읽습니다.

나빴던 장면도 있습니다. 어느 날 CI 요금이 계정 지출 한도를 쳤습니다. 세어 보니 커밋 하나가 main까지 가는 길에 같은 검사를 네 번 받고 있었습니다. 이건 모노레포의 죄라기보다 필터를 안 건 제 미숙이지만, 트리가 하나면 그런 실수가 이웃 프로젝트의 검사 요금까지 곱해져 청구된다는 건 알아 둘 만합니다. 환경변수도 한 번 데였습니다. 배포 설정에 값이 분명히 있는데 앱에 도달하지 않아서 한참을 찾았는데, 모노레포 도구가 넘겨주는 목록에 그 이름이 빠져 있었던 겁니다. 관리할 배관이 하나 더 있는 셈이고, 배관의 고장은 대체로 조용합니다.

가장 크게 데인 건 에이전트 여럿을 한 저장소에서 굴릴 때였습니다. 에이전트 여럿이 같은 체크아웃 위에서 일하고 있었는데, 한 명이 브랜치를 바꾸면 다른 에이전트 발밑의 코드가 통째로 바뀝니다. 측정하던 대상이 측정 중에 바뀌는데 도구는 아무 경고도 주지 않았습니다. 같은 사고를 여섯 번 겪고 나서야 규칙이 아니라 구조로 풀었습니다. 에이전트마다 워크트리를 따로 파서, 각자 자기 트리만 밟게 한 겁니다.

이 사고가 제 생각을 바꿔 놓았습니다. 여섯 번째에 깨달은 건 에이전트가 문제가 아니라 경계가 문제라는 것이었거든요. 사람 여럿이 한 체크아웃을 공유하면 서로 말을 하니까 부딪히기 전에 비킵니다. 에이전트는 말없이 빠르게 일하니까, 경계가 구조로 그어져 있지 않으면 반드시 밟습니다.

모노레포 논쟁 자체는 오래됐습니다. 해커뉴스에는 Monorepos: Please don't라는 글과 Just use a monorepo라는 글이 2019년과 2023년에 올라와 각각 수백 개의 댓글을 모았습니다. 제목이 정반대인데 둘 다 설득력이 있었고, 그 사이 몇 년이 지나도록 결론은 나지 않았습니다. 저는 AI가 이 저울에 새 추를 올렸다고 봅니다. 다만 그 추는 모노레포 쪽도 폴리레포 쪽도 아닙니다. 진짜 변수는 AI를 얼마나 잘 쪼개서 쓰느냐거든요.

못 쪼개면 어떻게 되는지는 그려 보면 됩니다. 에이전트에게 저장소를 통째로 쥐여 주면, 트리가 클수록 읽어야 할 것이 늘고 건드릴 수 있는 범위도 넓어집니다. 컨텍스트 창은 유한한데 트리는 계속 자라니까, 어느 시점부터 에이전트는 거대한 덩어리를 들고 허우적대게 됩니다. 이 상태라면 저장소를 물리적으로 갈라 둔 폴리레포가 낫습니다. 갈라진 경계만큼은 확실히 못 밟으니까요.

다만 가르는 쪽도 공짜가 아닙니다. 모노레포 도구를 만드는 Nx는 이걸 Memento Problem이라고 부르더군요. 에이전트가 저장소 경계를 넘을 때마다 기억이 초기화되고, 같은 설명을 반복해야 한다는 겁니다. 도구 판매자의 글이니 걸러 읽어야겠지만, 여러 프로젝트에 걸치는 변경이 전체 커밋의 20%쯤 된다는 관찰은 제 경험과도 맞습니다. 그 20%가 폴리레포에서는 매번 저장소 수만큼의 PR과 배포 순서 문제로 쪼개집니다.

물론 처방이 없는 건 아닙니다. 이 완화의 계보는 깁니다. 의존성에 새 버전이 나오면 저장소마다 알려주고 범프 PR까지 만들어 주는 봇들이 한 시절을 풍미했죠. 갈라진 저장소 사이에서 버전을 이어주는 게 정확히 그 봇들의 일이었습니다. 다만 그 광경을 떠올려 보면, 패키지 하나가 오를 때 소비하는 저장소 수만큼 같은 PR이 만들어지고 각각 CI가 돌았습니다. 완화 장치가 그렇게까지 발달했다는 것 자체가 그 고통이 실재했다는 증거이기도 합니다.

에이전트 시대의 처방도 비슷한 모양입니다. 이슈 트래커는 어디서나 쓰는 도구지만, 저장소가 갈라져 있으면 거기에 역할이 하나 더 얹힙니다. 저장소 사이의 대화 수단이 되는 거죠. 저도 그렇게 씁니다. 이 블로그 저장소에서 발견한 문제를 제품 저장소의 이슈로 등재하면서, 코드 위치와 측정값과 선택지를 이슈 본문에 다시 적어 넘겼습니다. 다음 세션은 그 이슈를 읽고 시작하면 되니까 기억이 이어지긴 합니다. 다만 이건 완화이지 소거가 아닙니다. 그 이슈를 쓰는 일 자체가 비용이고, 산문은 컴파일되지 않아서 낡아도 아무도 모릅니다. 모노레포에서 같은 역할을 하는 건 코드 그 자체라, 어긋나면 타입체커가 기계로 잡아냅니다.

반대로 잘 쪼개면 계산이 뒤집힙니다. 에이전트가 필요한 만큼만 들고 일하게 만들 수 있다면, 경계를 물리적으로 가르지 않고도 폴리레포의 격리를 얻으면서 모노레포의 맥락은 그대로 남습니다. 위의 커버 이미지 작업이 그 예입니다. 에이전트는 앱 폴더와 스키마 폴더만 읽었지만, 둘을 한 PR로 묶을 수 있었던 건 트리가 하나였기 때문입니다. 폴리레포의 쪼개기는 한 번 가르면 되돌리기 어렵지만, 이쪽의 쪼개기는 작업마다 조정할 수 있습니다.

여기까지는 논리입니다. 궁금해서 직접 재봤습니다. 공유 타입과 그것을 소비하는 패키지들로 작은 구성물을 만들어 한 벌은 모노레포로, 한 벌은 독립 저장소들로 두고, 같은 과제를 에이전트(Claude Code, 같은 모델)에게 시켰습니다. 그리고 세 가지를 쟀습니다. 기본 격차, 트리 크기의 효과, 변경 폭의 효과.

먼저 기본 격차. 타입에 필드를 추가하고 api 계산과 web 표시까지 세 패키지를 관통하는 변경에서, 모노레포는 1세션 $0.68, 폴리레포는 3세션 합계 $1.11이 들었습니다. 흥미로운 건 출력 토큰이 거의 같았다는 점입니다. 실제로 한 일은 같은데, 폴리레포 쪽이 읽은 컨텍스트가 1.4배였습니다. 세션마다 저장소 구조를 처음부터 파악하고, 앞 저장소의 변경을 diff로 다시 설명받아야 했으니까요. Memento 비용이 숫자로 잡힌 셈입니다. 한 패키지 안에서 끝나는 로컬 변경에서는 둘이 박빙이었습니다($0.29 vs $0.33, 노이즈 범위로 봅니다).

다음은 트리 크기. 과제는 그대로 두고 무관한 이웃 패키지를 아홉 개 더해 3패키지를 12패키지로 만들었습니다. 역전이 나오는지 보고 싶었거든요. 안 나왔습니다. 모노레포가 40% 저렴한 비율이 두 규모에서 똑같이 재현됐고, 12패키지 트리에서도 에이전트는 정확히 관련된 3개 파일만 만졌습니다. 턴당 컨텍스트 비용도 트리 크기와 무관하게 비슷했습니다(39k vs 36k 토큰). 트리가 4배가 되는 효과는 실행 사이의 자연스러운 변동에 묻힐 만큼 작았습니다.

마지막으로 변경의 폭. 이번에는 공유 타입의 Money를 number에서 객체로 바꾸는 파괴적 변경을 넣었습니다. 12개 패키지 전부가 그 타입을 쓰고 있어서, 전부 고쳐야 컴파일이 통과합니다. 모노레포는 1세션 $0.92로 12개를 돌았고, 폴리레포는 12세션 합계 $4.22가 들었습니다. 4.6배입니다. 기전이 숫자에 그대로 보입니다. 폴리레포 세션들은 실제 수정이 한두 줄인 레포에서도 비용이 세션당 $0.35 언저리로 균일했습니다. 일의 크기가 아니라 세션의 고정비가 지배하는 겁니다. 저장소를 파악하고, diff를 읽고, 검증을 다시 돌리는 그 비용이 관통하는 저장소 수만큼 선형으로 쌓입니다. 모노레포는 그 고정비를 한 번만 냅니다. 정리하면, 격차를 만드는 건 트리의 크기가 아니라 변경이 관통하는 폭이었습니다. 3개를 관통하면 1.6배, 12개를 관통하면 4.6배.

물론 한계가 있는 실험입니다. 각 조건 한두 번씩이고, 12패키지는 여전히 작은 트리이고, 폴리레포 세션 사이의 diff 전달을 스크립트가 대신해 줬습니다(현실에서는 사람이 하거나 누락되니, 실제 폴리레포 비용은 이보다 클 겁니다). 폴리레포의 소비자 세션들은 서로 독립이라 병렬로 돌리면 시간은 모노레포와 비슷해집니다. 다만 비용은 그대로입니다. 고정비는 세션 수를 따라가니까요. 그리고 수백 패키지에서 에이전트가 덩어리에 삼켜지는 상황은 이 실험 밖입니다. 그래도 하나는 분명해졌습니다. 이 규모에서 에이전트는 트리를 통째로 들지 않았습니다. 필요한 곳을 찾아 그것만 만졌습니다.

사실 이 쪼개기는 새로운 발상이 아닙니다. 모노레포를 크게 굴려 본 사람들에게는 오래된 원리가 하나 있습니다. 저장소에 대한 연산은 O(repo)가 아니라 O(change)여야 한다는 원리입니다. 빌드와 테스트가 저장소 크기가 아니라 변경 크기에 비례해야 한다는 뜻입니다. 에이전트의 컨텍스트가 그 목록에 하나 더 올라온 것뿐입니다. 에이전트가 드는 것도 저장소가 아니라 변경이어야 하니까요.

그리고 그 방향의 도구는 이미 도처에 있습니다. Turborepo는 의존성 그래프를 보고 안 바뀐 앱의 빌드를 건너뛰고, CI는 경로 필터로 관련 없는 검사를 거릅니다. 워크트리는 에이전트마다 트리를 격리하고, 규칙 파일은 루트와 앱 단위로 계층을 이룹니다. 전부 한 방향을 가리키고 있습니다. 경계가 저장소라는 물리 단위에서 컨텍스트라는 논리 단위로 옮겨가는 중입니다.

저도 모든 것을 한 트리에 넣지는 않았습니다. 제품과 SDK와 이 블로그는 저장소가 다릅니다. 소비하는 쪽과 만드는 쪽이 섞이면 에이전트가 특별 취급 코드를 쓰기 시작할 것 같았거든요. 그러니까 이 경계도 결국 같은 판단이었습니다. 어디까지를 한 컨텍스트로 볼 것인가.

그래서 모노레포는 AI 시대에 어디에 서게 될까요. 저는 쪼개는 능력에 따라 갈린다고 봅니다. 쪼개지 못하면 모노레포는 에이전트를 삼키는 덩어리가 될 거고, 쪼갤 수 있다면 폴리레포보다 효율적인 구조가 될 겁니다. 그리고 실험이 보여준 게 하나 있다면, 그 능력의 절반은 에이전트가 이미 갖고 나온다는 겁니다. 작은 트리에서 에이전트는 알아서 필요한 것만 들었습니다. 나머지 절반, 그러니까 트리가 커져도 그게 유지되게 하는 경계와 규칙과 격리가 사람 몫으로 남아 있을 뿐입니다. 저장소를 가를지 말지보다, 에이전트에게 얼마를 들려 보낼지를 먼저 정해야 하는 때가 온 것 같습니다.