📄
Google이 수십억 줄의 코드를 하나의 리포지토리에서 관리하는 이유
refactoring
monorepo
management
0. Introduce
- 목적
- 왜 Google은 수십억 줄의 코드를 하나의 리포지터리에서 관리하는가
- 왜 Google은 이러한 결정을 고수하는가
- 순서
- Google Repository Scale
- Google Systems and Workflows
- Advantages of a Monolithic Repository
- Costs associated with This Model
- Conclusion
1. Google Repository Scale
- 각각의 프로젝트를 서로 다른 리포지터리에서 관리하면 발생되는 상황
- 동일한 코드가 여러 리포지터리에 존재
- 각 코드가 독립적으로 고도화되어 이를 하나로 다시 합치기 어려움
- Google은 하나의 리포지터리에서 무엇을, 얼마나 관리하고 있을까? (Jan, 2015)
- 리포지터리 크기 : 86TB
- 파일 개수 : 1B
- 소스 파일 개수 : 9M
- 코드 라인 : 2B
- 커밋 개수 : 35M
- 일일 커밋 개수 : 45K (계속 증가하는 추세)
- 사람에 의한 커밋 : 15K
- 자동화 시스템에 의한 커밋 : 30K
- Thousands of Commits per Week
- 자동화 시스템에 의해 생성되는 것 : 설정 파일, 데이터 파일 등
- 모노리포 확장성 → 사람 뿐만 아니라 자동화 시스템에 대한 부분도 고민 필요
2. Google Systems and Workflows
- Google의 모노리포 워크플로우
- 모든 코드는 Upstream에 커밋되기 전 리뷰 과정(Human or Automated Tools)을 거침
- 이러한 구조 덕분에 많은 분석 & 테스팅 과정의 자동화가 가능
- Google은 Tree 구조로 디렉터리를 관리
- Tree 내 모든 디렉터리는 소유자(Owner)가 존재
- Owner : 프로젝트와 관련된, 디렉터리 내 변경사항을 승인하는 사람
- 이로 인해 코드 리뷰는 퀄리티와 스타일 관련해서 때때로 디테일하게 진행되곤 함
- Upstream 커밋 후 문제가 발생되면 자동으로 Rollback되도록 구성
- Google 내부의 소스 관리 시스템
- Piper
- 하나의 거대한 리포지터리로 소스 코드 관리
- Google 인프라 위에 구축되어 있음
- 전 세계 10개 데이터센터로 데이터 복사되어 안정성 & 접근성 갖춤
- CitC (Clients in the Cloud)
- etc.
- 모노리포를 위한 Trunk-Base Development
- 소스 코드는 Mainline에 커밋됨 → 모든 개발자가 접근 가능
- 개발을 위한 브랜치는 많이 필요하지 않음
- 브랜치가 길어지는 경우 이를 Merge 하는 데 어려움을 겪곤 하는데, 이를 피할 수 있음
- 새로운 기능이 추가(배포)될 때, 이에 대한 Cherry Pick이 릴리즈 브랜치에 머지
- 즉, 릴리즈 브랜치는 특정 버전의 Trunk 브랜치에 대한 스냅샷
- 플래그를 이용해 각 기능을 릴리즈에 포함시킬 것인지 지정 가능
→ 간단한 설정 변경만으로 배포 포함 여부 결정이 가능하다는 것
- 이에 대한 추가적인 이점은 아래에서 설명
3. Advantages of a Monorepo
- high-level list
- 모든 기능을 일관된 버전으로 배포 가능 (SSOT, one source of truth)
- 코드 재사용 가능
- 디펜던시 관리 단순화
- Atomic Changes
- 큰 단위의 리팩터링 & 코드베이스 최신화 가능
- 팀 간 협업이 쉬워짐
- 유연한 코드 소유권 & 팀 관리
- 코드 가시성 & 명확한 트리 구조 → 묵시적으로 팀 네임스페이스를 지정할 수 있음
- 일관된 버전 - SSOT, Single Source Of Truth
- 소스 코드가 최신 소스 코드인지 고민할 필요 없음
- 공유된 라이브러리를 포크할 필요 없음
- 여러 리포지터리에 분리되어 각각 개발된 소스 코드를 머지할 필요 없음
- 팀이나 프로젝트 사이에 인위적인 경계가 없음
- 점진적으로 코드베이스 리팩터링 가능
- 코드 재사용 가능
- 이미 개발된 기능을 재사용해서 빠르게 새로운 프로덕트 개발이 가능해짐
- 디펜던시 관리 단순화 - Diamond Dependency Problem
- 모듈 A는 모듈 B와 C를 사용 & 모듈 B와 C는 모듈 D를 사용
- 멀티리포를 이용한 경우(우측) - 서로 다른 버전의 디펜던시를 사용해버리게 되는 상황 가능
- 모노리포를 이용한 경우(좌측) - 통일된 디펜던시 버전
- 빌드 시, 동일한 디펜던시의 여러 버전을 사용한다면...
- 최종적으로 어떤 것을 선택해야 하는지 결정이 어려워짐
- 특정 디펜던시를 업데이트하면 관련된 디펜던시 모두에게 영향이 가도록 구성해야 함
- Atomic Changes
- 이전 버전과 호환되지 않는 대규모 변경을 쉽게 진행할 수 있게 됨
- 하나의 명령으로 굉장히 많은 숫자의 파일 변경 가능
- 빌드나 테스트 워크플로우의 크래시 없이 하나의 커밋으로 Class 또는 Function 이름 변경 가능
- 큰 단위의 리팩터링 & 코드베이스 최신화
- 하나의 코드베이스만이 존재하기에 쉽게 관련자 논의 & 테스팅 가능
- 모든 디펜던시도 같은 코드베이스에 존재하기에, 사용되지 않는 API 역시 고민 없이 제거 가능
- 에러 or 설계 실수 역시 코드베이스 전체에서 찾아 수정될 수 있음
4. Costs associated with This Model (Disadvantages)
- 참고로 모노리포 ≠ Monolithic Architecture(Software Design)
- Monolithic Repository : 하나의 거대한 코드베이스
- Monolithic Architecture : 하나의 거대한 서비스
- 모노리포 구성을 위한 비용
- 모노리포 구성을 위해 조사, 개발 등 시간이 필요
- 현재 코드베이스에 알맞은 개발 도구를 추가로 개발하는 경우도 존재
- 자칫 코드베이스가 복잡해지거나 빌드 타임이 길어질 수 있음 → 생산성에 영향
- 항상 코드 상태가 어떤지 염두해두고 있어야 함
- 복잡한 코드베이스
- 모노리포 사용 이유 = 코드 재사용
- 즉, 불필요한 디펜던시는 추가하지 말아야 함
- 각 모듈의 API를 잘 구성해야 빌드 & 테스트 & 유지보수 비용을 낮출 수 있음
- 코드 상태 관리에 대한 투자
- 아래와 같은 것들을 지원하는 툴이 필요
- 사용되지 않거나, 덜 사용되는 디펜던시 & 코드를 찾아 제거
- 큰 단위의 리팩터링 & 코드베이스 최신화를 지원
- Google은 기본적으로 모듈 API의 가시성(Visibility)을 ‘private’ 으로 구성
- 명시적으로 사용하기 적합할 때를 지정하기 위함
- 향후 더 이상 사용되지 않는 API를 명시하는 목적도 있음
- 코드베이스가 너무 커지기 전 위와 같은 매커니즘(툴)이 반드시 필요
5. Conclusion
- 모노리포는 투명한 엔지니어링 문화와 협업에서 잘 동작함
- Google은 모노리포의 확장성과 생산성을 위한 툴링에 막대한 투자를 진행함
- Google은 굉장히 크기가 커다란 코드베이스도 모노리포로 잘 관리할 수 있음을 보여줬음
- 그렇다고 모노리포가 정답이라는 말은 아님 (회사바이회사)