Git의 모노레포(Monorepo)
모노레포란 무엇입니까?
정의는 다양하지만 Atlassian에서는 모노레포를 다음과 같이 정의합니다.
- 둘 이상의 논리적 프로젝트(예: iOS 클라이언트와 웹 애플리케이션)가 포함된 리포지토리
- 이러한 프로젝트는 관련이 없거나, 느슨하게 연결되어 있거나, 다른 수단으로(예: 종속성 관리 도구를 통해) 연결할 수 있음
- 리포지토리는 여러 면에서 큽니다.
- 커밋의 수
- 브랜치 및/또는 태그의 수
- 추적된 파일의 수
- 추적되는 콘텐츠의 크기(리포지토리의 .git 디렉터리를 보고 측정)
Facebook에는 모노레포의 한 가지 예가 있습니다.
수십만 개의 파일에 대해 매주 수천 번 커밋을 하는 Facebook의 메인 소스 리포지토리는 엄청나며 2013년에 1,700만 줄의 코드와 44,000개의 파일을 체크인했던 Linux 커널보다 몇 배나 더 큽니다.
관련 자료
전체 Git 리포지토리를 이동하는 방법
솔루션 보기
Bitbucket Cloud에서 Git에 대해 알아보기
성능 테스트를 수행할 때 Facebook에서 사용한 테스트 리포지토리는 다음과 같습니다.
- 4백만 건의 커밋
- 선형 기록
- 최대 130만 개의 파일
- 약 15GB의 .git 디렉터리 크기
- 191MB의 색인 파일 크기
개념적인 어려움
Git의 모노레포에서 관련 없는 프로젝트를 관리할 때는 개념적인 어려움이 많습니다.
우선 Git은 이루어지는 커밋마다 트리 전체의 상태를 추적합니다. 단일 프로젝트나 관련 있는 프로젝트에서는 괜찮지만 관련 없는 프로젝트가 많은 리포지토리에서는 다루기 힘들어집니다. 간단히 설명하자면 트리에서 관련 없는 부분의 커밋은 개발자와 관련된 하위 트리에 영향을 줍니다. 커밋이 많아 트리의 기록이 늘어나는 경우 문제가 더욱 커집니다. 브랜치 팁은 항상 변경되기 때문에 변경 사항을 푸시하려면 로컬에서 자주 병합하거나 다시 지정(rebase)해야 합니다.
Git에서 태그는 트리 전체를 가리키는 특정 커밋의 별칭입니다. 하지만 모노레포의 컨텍스트에서는 태그의 유용성이 낮아집니다. 모노레포에 지속적으로 배포되는 웹 애플리케이션을 개발하고 있는 경우 버전이 지정된 iOS 클라이언트의 릴리스 태그는 어떤 관련이 있는지 스스로에게 질문해 보세요.
성능 문제
이러한 개념적 어려움 외에도 모노레포 설정에 영향을 미칠 수 있는 수많은 성능 문제가 있습니다.
커밋의 수
관련 없는 프로젝트를 단일 리포지토리에서 대규모로 관리하는 것은 커밋 수준에서 번거로울 수 있습니다. 시간이 지남에 따라 커밋이 많아지고 성장 속도가 크게 증가할 수 있습니다(Facebook에서는 “일주일에 수천 개의 커밋”이 이루어진다고 함). Git은 프로젝트의 기록을 나타내는 데 방향성 비순환 그래프(DAG)를 사용하므로 특히 번거로워집니다. 커밋의 수가 많으면 그래프의 모든 명령은 기록이 많아질수록 느려질 수 있습니다.
Some examples of this include investigating a repository's history via git log or annotating changes on a file by using git blame. With git blame if your repository has a large number of commits, Git would have to walk a lot of unrelated commits in order to calculate the blame information. Other examples would be answering any kind of reachability question (e.g. is commit A reachable from commit B). Add together many unrelated modules found in a monorepo and the performance issues compound.
참조의 수
모노레포에 참조(예: 브랜치 또는 태그)가 많으면 여러 면에서 성능에 영향을 미칩니다.
Ref advertisements contain every ref in your monorepo. As ref advertisements are the first phase in any remote git operation, this affects operations like git clone, git fetch
or git push. With a large number of refs, performance takes a hit when performing these operations. You can see the ref advertisement by using git ls-remote
with a repository URL. For example, git ls-remote git://git.kernel.org/ pub/scm/linux/kernel/git/torvalds/linux.git
will list all the references in the Linux Kernel repository.
If refs are loosely stored listing branches would be slow. After a git gc refs are packed in a single file and even listing over 20,000 refs is fast (~0.06 seconds).
Any operation that needs to traverse a repository's commit history and consider each ref (e.g. git branch --contains SHA1
) will be slow in a monorepo. In a repository with 21708 refs, listing the refs that contain an old commit (that is reachable from almost all refs) took:
사용자 시간(초): 146.44*
*페이지 캐시와 기본 스토리지 계층에 따라 달라집니다.
추적된 파일의 수
색인 또는 디렉터리 캐시(.git/index
)는 리포지토리의 모든 파일을 추적합니다. Git은 이 색인을 사용하여 모든 파일마다 stat(1)
을 실행하고 파일 수정 정보를 색인에 포함된 정보와 비교하여 파일이 변경되었는지 확인합니다.
따라서 추적되는 파일의 수는 여러 작업의 성능*에 영향을 줍니다.
git status
가 느릴 수 있습니다(모든 파일의 통계를 만들며 색인 파일이 큼)git commit
도 느릴 수 있습니다(모든 파일의 통계를 만듦)
*페이지 캐시와 기본 스토리지 계층에 따라 달라지며 파일이 수만 또는 수십만 개에 달할 정도로 많은 경우에만 두드러지게 나타납니다.
대용량 파일
하나의 하위 트리/프로젝트에 있는 대용량 파일은 전체 리포지토리의 성능에 영향을 줍니다. 예를 들어, 모노레포의 iOS 클라이언트 프로젝트에 추가된 대규모 미디어 자산은 개발자(또는 빌드 에이전트)가 관련 없는 프로젝트를 진행하고 있을 때에도 복제됩니다.
복합적인 영향
파일의 수, 변경의 빈도, 크기 등의 이러한 문제가 결합되면 성능에 더 큰 영향을 줍니다.
- 브랜치/태그 사이를 전환해도 트리 전체가 업데이트되며 이는 하위 트리 컨텍스트(예: 작업 중인 하위 트리)에서 가장 유용합니다. 영향을 받는 파일의 수로 인해 프로세스가 느려지거나 임시 해결책이 필요할 수 있습니다. 예를 들어,
git checkout ref-28642-31335 -- templates
는 주어진 브랜치와 일치하도록./templates
디렉터리를 업데이트하지만HEAD
는 업데이트하지 않습니다. 이는 업데이트된 파일을 색인에서 수정된 것으로 표시하는 부작용이 있습니다. - 전송되기 전에 모든 정보가 팩 파일에 압축되기 때문에 복제 및 가져오기가 느려지고 서버에서 리소스가 집약적이게 됩니다.
- 가비지 컬렉션이 느리고 기본적으로 푸시에 따라 트리거됩니다(가비지 컬렉션이 필요한 경우).
- 팩 파일을 (다시) 만드는 모든 작업에서 리소스 사용량이 높습니다. 예:
git upload-pack, git gc.
완화 전략
Git이 모놀리식 리포지토리처럼 특별한 사용 사례를 지원한다면 좋겠지만 Git이 큰 성공을 거두고 인기를 얻도록 해준 Git의 설계 목표는 설계 의도와 다른 방식으로 사용하려는 욕구와 상충되는 경우가 있습니다. 대다수 팀에게 좋은 소식은 대규모 모놀리식 리포지토리는 일반적이지 않은 경향이 있다는 것입니다. 따라서 여러분의 상황에는 해당하지 않을 가능성이 높습니다.
그렇지만 대규모 리포지토리로 작업할 때 도움이 될 수 있는 다양한 완화 전략이 있습니다. 기록이 길거나 바이너리 자산이 큰 리포지토리에 대해서는 제 동료인 Nicola Paolucci가 몇 가지 해결 방법을 설명합니다.
참조 제거
리포지토리에 수만 개의 참조가 있다면 더 이상 필요하지 않는 참조를 제거하는 것을 고려해야 합니다. DAG는 변경 사항이 어떻게 발전했는지에 대한 기록을 보관하고 병합 커밋은 상위 항목을 가리키므로 브랜치가 더 이상 존재하지 않더라도 브랜치에서 수행된 작업을 추적할 수 있습니다.
브랜치 기반 워크플로에서는 장기적으로 보관하려는 브랜치의 수가 적어야 합니다. 병합 후 수명이 짧은 기능 브랜치를 삭제하는 것을 두려워하지 마세요.
프로덕션과 같은 main 브랜치에 병합된 모든 브랜치를 제거하는 것을 고려해 보세요. main 브랜치에서 커밋에 연결할 수 있고 병합 커밋을 통해 브랜치를 병합했다면 변경 사항이 어떻게 발전했는지에 대한 기록을 계속하여 추적할 수 있습니다. 기본 병합 커밋 메시지에는 브랜치 이름이 포함되는 경우가 많아 필요한 경우 이 정보를 보관할 수 있습니다.
대량의 파일 처리
리포지토리에 파일이 많은 경우(수만에서 수십만 개) 버퍼 캐시로 사용할 수 있는 충분한 메모리가 있는 고속 로컬 스토리지를 사용하면 도움이 될 수 있습니다. 이 부분은 예를 들어 Facebook에서 Mercurial에 구현한 변경 사항과 비슷하게 클라이언트에 더 큰 변경이 필요한 부분입니다.
Facebook의 접근 방식에서는 모든 파일을 반복해서 변경되었는지 확인하는 대신 파일 시스템 알림을 사용하여 파일 변경 사항을 기록했습니다. 비슷한 접근 방식(Watchman도 사용)이 Git에 대해서도 논의되었지만 아직 결정된 것은 없습니다.
Git LFS(대용량 파일 스토리지) 사용
이 섹션은 2016년 1월 20일에 업데이트되었습니다
비디오나 그래픽과 같은 대용량 파일이 포함된 프로젝트의 경우 리포지토리의 크기나 전반적인 성능에 미치는 영향을 제한하는 한 가지 옵션은 Git LFS입니다. Git LFS는 대용량 개체를 리포지토리에 바로 저장하는 대신 개체에 대한 참조가 포함된 동일한 이름의 작은 자리 표시자 파일을 저장하며 이 파일은 특수한 대용량 개체 스토리지에 저장됩니다. Git LFS는 Git의 네이티브 푸시, 풀, 체크아웃, 가져오기 작업에 연결하여 작업 트리에서 이러한 개체의 전송과 대체를 투명하게 처리합니다. 따라서 리포지토리의 크기가 커져도 문제 없이 평소처럼 리포지토리에서 대용량 파일 작업을 할 수 있습니다.
Bitbucket Server 4.3 이상 버전에는 완벽하게 호환되는 Git LFS v1.0+ 구현이 포함되어 있으며, LFS가 추적하는 대용량 이미지 자산을 Bitbucket UI 내에서 직접 미리 보고 차이점을 확인할 수 있습니다.
LFS 프로젝트에 적극적으로 기여하는 Atlassian의 제 동료인 Steve Streeting이 최근에 이 프로젝트에 대해 글을 작성했습니다.
경계를 파악하고 리포지토리를 분할
가장 근본적인 해결 방법은 더 작고 집중적인 Git 리포지토리로 모노레포를 분할하는 것입니다. 단일 리포지토리에서 모든 변경 사항을 추적하는 대신 릴리스 주기가 비슷한 모듈이나 구성 요소를 식별하여 구성 요소의 경계를 파악해 보세요. 명확한 하위 구성 요소를 가려낼 수 있는 테스트 방법은 리포지토리에서 태그를 사용하여 태그가 소스 트리의 다른 부분에도 적합한지 여부를 확인하는 것입니다.
Git이 모노레포를 원활하게 지원한다면 좋겠지만 모노레포라는 개념은 애초에 Git의 큰 성공과 인기를 얻게 한 요소와 약간 상충됩니다. 하지만 모노레포가 있다고 해서 Git의 기능을 포기해야 한다는 뜻은 아니며 대부분의 경우 발생하는 모든 문제에 대한 실행 가능한 해결책이 있습니다.
이 문서 공유
다음 토픽
여러분께 도움을 드릴 자료를 추천합니다.
이러한 리소스에 책갈피를 지정하여 DevOps 팀의 유형에 대해 알아보거나 Atlassian에서 DevOps에 대한 지속적인 업데이트를 확인하세요.