ondelva

← Archive

모노레포와 MSA, 헷갈리는 용어 정리

모노레포·멀티레포는 코드를 어디에 두느냐, 모놀리스·MSA는 무엇을 따로 배포하느냐의 문제다. 둘의 차이와 장단점, 섞어 쓰는 방법을 정리했다.

egg4분 읽기

모노레포 이야기를 하다 보면 MSA가 같이 따라 나오는 경우가 많다. 둘 다 뭔가를 나누거나 합치는 이야기라 그런 것 같은데 정작 나누는 대상은 다르다.

모노레포는 코드를 어디에 두느냐의 문제고 MSA는 서비스를 어떤 단위로 배포하느냐의 문제다. 그래서 저장소 하나에 서비스 수십 개를 돌리는 회사도 있고 저장소는 여러 개인데 배포는 한 덩어리인 팀도 있다.

헷갈리기 쉬운 용어 몇 가지까지 같이 놓고 정리해본다.

저장소 이야기 - 모노레포와 멀티레포

웹 프론트와 API 서버, 공통 라이브러리처럼 여러 프로젝트를 저장소 하나에 넣고 폴더로만 나누는 게 모노레포다. 모아두면 여러 곳을 한 번에 고칠 수 있다.

공통 라이브러리 함수 하나를 바꾸면 그걸 쓰는 곳까지 같은 커밋에서 고친다. 저장소가 따로였다면 라이브러리 버전을 올려 배포한 다음 쓰는 쪽마다 PR을 하나씩 올렸을 일이다.

의존성 버전이나 lint·CI 설정도 한 벌이면 끝이다. CI는 저장소가 커질수록 문제가 된다. 한 줄 고칠 때마다 전체를 빌드하고 테스트해서는 금방 못 버틴다.

그래서 모노레포는 보통 도구와 같이 간다. 바뀐 프로젝트와 거기에 의존하는 것만 골라 빌드해주는 도구인데 JavaScript 쪽이면 Turborepo나 Nx가 흔하다. 저장소 전체가 한 권한 단위라 “이 폴더는 이 팀만” 같은 통제가 까다롭다는 점도 알고 가야 한다.

가장 자주 인용되는 건 Google이다. 사내 코드 대부분을 저장소 하나에 두고 있다고 2016년 논문으로 밝혔다. 물론 git이 아니라 직접 만든 시스템이고 도구에 들인 투자가 전제라 아무래도 그대로 따라 하기는 어렵다.

반대로 프로젝트마다 저장소를 따로 두면 멀티레포(폴리레포라고도 한다)다. GitHub에서 새 프로젝트를 만들면 자연스럽게 이렇게 되니 사실 가장 흔한 형태다.

저장소마다 권한과 CI, 릴리스 주기를 따로 가져갈 수 있고 저장소 하나하나가 작아서 들여다보기 편하다.

불편한 점은 모노레포 장점을 뒤집어 놓은 그대로다. 공통 코드는 패키지로 배포해야 하고 쓰는 쪽마다 버전이 흩어진다. 여러 저장소를 같이 고쳐야 하면 PR 여러 개의 머지 순서를 맞추는 일이 생긴다.

헷갈리기 쉬운 게 하나 있는데, 모노레포라고 앱까지 한 덩어리로 배포하는 건 아니다.

배포 이야기 - 모놀리스, 모듈러 모놀리스, MSA

모놀리스는 애플리케이션 전체를 하나로 빌드해서 하나로 배포하는 방식이다. 서버를 여러 대 띄워도 같은 결과물을 복제한 것이고 데이터베이스도 보통 하나다.

처음엔 이게 제일 편하다. 기능끼리는 함수 호출이면 되고 트랜잭션 하나로 데이터가 맞춰진다. 로컬에서 띄워 디버깅하기도 쉽다.

커지면 빌드와 배포가 점점 느려지고 한 기능의 버그가 전체 장애로 번진다. 팀이 여럿이면 배포 하나에 모두가 줄을 서게 된다.

배포는 하나로 두고 내부만 모듈로 엄격하게 나누면 모듈러 모놀리스가 된다. 모듈끼리는 정해둔 인터페이스로만 부르고 다른 모듈의 테이블은 직접 건드리지 않는다. Shopify가 거대한 Rails 앱을 이렇게 정리한 과정을 글로 남겼다.

경계는 약속일 뿐이라 급하면 import 한 줄로 넘어가 버린다. 의존 방향을 검사하는 규칙을 CI에 걸어두지 않으면 금방 그냥 모놀리스로 돌아가는 것 같다.

MSA(마이크로서비스 아키텍처)라는 개념의 출발점은 James Lewis님과 Martin Fowler님이 2014년에 쓴 Microservices다. 기능을 작은 서비스로 쪼개서 각자 따로 배포하고 서비스끼리는 HTTP나 메시지 큐 같은 네트워크로 통신한다. 데이터베이스도 서비스마다 따로 둔다.

이렇게 쪼개면 팀마다 자기 서비스를 자기 일정에 배포하고 트래픽이 몰리는 서비스만 따로 늘린다. 기술 문제보다는 팀이 많아졌을 때 서로 발목을 잡지 않으려고 고르는 방식에 가까운 듯 싶다.

대신 분산 시스템의 문제를 전부 떠안는다. 네트워크 호출은 실패하고 서비스를 넘나드는 트랜잭션은 없다. 요청 하나가 어디서 느려졌는지 찾으려면 추적 도구부터 깔아야 한다.

서비스 수만큼 배포 파이프라인과 모니터링도 늘어난다. 작은 팀이라면 얻는 것보다 이 운영 비용이 더 크게 느껴질 것 같다.

조금 오래된 말로 SOA(서비스 지향 아키텍처)도 있다. 서비스로 나눈다는 발상은 같지만 2000년대 기업 시스템 통합에서 나온 말이라 중앙 미들웨어(ESB)가 메시지를 중계하는 구조가 많았고 나누는 단위도 더 컸다. MSA는 그 중앙을 걷어내고 서비스끼리 직접 가볍게 통신하는 쪽이다.

섞어 쓰는 네 가지

두 축이 따로 노니 조합은 네 가지가 나온다.

모노레포 + 모놀리스. 새로 시작하는 팀은 대개 여기서 출발하는 것 같다. 저장소 하나, 배포 하나. 따로 고민할 게 없고, 커지면 모듈러 모놀리스로 경계를 긋는 쪽으로 넘어간다.

모노레포 + MSA. 코드는 한 곳에 두고 배포만 서비스별로 한다. 서비스끼리 주고받는 API 형식을 바꿀 때 양쪽을 같은 커밋에서 고칠 수 있다는 게 크다.

바뀐 서비스만 골라 배포하는 파이프라인이 없으면 한 줄 고칠 때마다 전부 다시 배포된다. 같은 커밋이라고 같은 순간에 배포되는 것도 아니라서 API 하위 호환은 여전히 챙겨야 한다.

멀티레포 + MSA. 서비스 하나에 저장소 하나씩 두면 팀과 저장소와 서비스 경계가 딱 맞아서 책임이 가장 깔끔하다. 대신 인증이나 로깅 같은 공통 코드는 패키지로 나눠 쓰게 되니, 보안 패치 하나에도 저장소마다 버전을 올려야 한다.

멀티레포 + 모놀리스. 드물다. 앱은 한 덩어리인데 일부 모듈만 다른 저장소의 패키지로 떼어낸 형태다. 외부에 공개하는 SDK처럼 이유가 있으면 모를까, 아니면 불편함만 남는 경우가 많은 것 같다.

Segment는 반대로 돌아간 사례다. 서비스마다 저장소를 나눴다가 서비스가 100개를 넘자 감당을 못 하고 저장소도 서비스도 하나로 다시 합쳤다. 두 축을 한꺼번에 되돌린 경우라 그 과정을 남긴 Goodbye Microservices는 같이 읽어볼 만하다.

결론. 팀 구조부터 보는 게 맞는 것 같다

어떤 조합을 고를지는 기술보다 팀 모양이 먼저 정하는 것 같다. 시스템이 그걸 만드는 조직의 소통 구조를 닮는다는 콘웨이의 법칙 이야기다.

지금 새로 시작한다면 일단 모놀리스 하나로 가고, 따로 배포해야 할 이유가 분명한 모듈이 생겼을 때 그것만 떼어내겠다. Martin Fowler님의 MonolithFirst도 같은 이야기를 한다.

저장소는 같이 고치는 일이 얼마나 잦은지를 보고 정하면 될 듯 싶다.

사실 이 블로그와 영문 사이트도 이름과 디자인 토큰을 같이 쓰면서 저장소는 따로다. 공유하는 게 아직 몇 줄뿐이라 버틸 만한데, 늘어나면 그땐 이 글부터 다시 읽어봐야겠다.

Filed under 개발

ShareXFacebookLinkedIn