모노레포 성능 개선
모노레포는 하위 프로젝트를 포함하는 저장소입니다. GitLab 자체는 Git을 기반으로 합니다. Git은 공간을 덜 사용하기 위해 객체를 팩파일로 압축합니다. 대규모 모노레포는 소규모 저장소보다 더 많은 커밋, 파일, 브랜치, 태그를 가집니다.
모노레포는 하위 프로젝트를 포함하는 저장소입니다. 단일 애플리케이션에는 종종 상호 의존적인 프로젝트가 포함됩니다. 예를 들어 백엔드, 웹 프론트엔드, iOS 애플리케이션, Android 애플리케이션이 있습니다. 모노레포는 일반적이지만 성능 위험을 초래할 수 있습니다. 일반적인 문제:
- 대용량 바이너리 파일.
- 긴 기록을 가진 많은 파일.
- 많은 동시 클론 및 푸시.
- 수직 확장 제한.
- 네트워크 대역폭 제한.
- 디스크 대역폭 제한.
GitLab 자체는 Git을 기반으로 합니다. GitLab의 Git 저장소 서비스인 Gitaly는 모노레포와 관련된 성능 제약을 경험합니다. 저희가 배운 것이 자신의 모노레포를 더 잘 관리하는 데 도움이 될 수 있습니다.
- 어떤 저장소 특성이 성능에 영향을 미칠 수 있는지.
- 모노레포를 최적화하는 도구 및 단계.
모노레포를 위한 Gitaly 최적화#
Git은 공간을 덜 사용하기 위해 객체를 팩파일로 압축합니다. 클론, 페치 또는 푸시할 때 Git은 팩파일을 사용합니다. 팩파일은 디스크 공간과 네트워크 대역폭을 줄이지만 팩파일 생성에는 많은 CPU와 메모리가 필요합니다.
대규모 모노레포는 소규모 저장소보다 더 많은 커밋, 파일, 브랜치, 태그를 가집니다. 객체가 커지고 전송 시간이 길어질수록 팩파일 생성이 더 비용이 많이 들고 느려집니다. Git에서 git-pack-objects 프로세스가 가장 리소스 집약적인 작업입니다. 이 프로세스는:
- 커밋 기록과 파일을 분석합니다.
- 클라이언트에 다시 보낼 파일을 결정합니다.
- 팩파일을 만듭니다.
git clone 및 git fetch에서의 트래픽은 서버에서 git-pack-objects 프로세스를 시작합니다.
GitLab CI/CD와 같은 자동화된 지속적 통합 시스템은 이 트래픽의 많은 부분을 일으킬 수 있습니다.
많은 자동화된 CI/CD 트래픽은 많은 클론 및 페치 요청을 보내며 Gitaly 서버에 부담을 줄 수 있습니다.
Gitaly 서버의 부하를 줄이기 위해 다음 전략을 사용하세요.
Gitaly pack-objects 캐시 활성화#
클론과 페치에 대한 서버 부하를 줄이는 Gitaly pack-objects 캐시를 활성화합니다.
Git 클라이언트가 클론 또는 페치 요청을 보낼 때 git-pack-objects가 생성한 데이터를 재사용을 위해 캐시할 수 있습니다. 모노레포가 자주 클론되는 경우 Gitaly pack-objects 캐시를 활성화하면 서버 부하가 줄어듭니다. 활성화되면 각 클론 또는 페치 호출마다 응답 데이터를 재생성하는 대신 Gitaly는 인메모리 캐시를 유지합니다.
자세한 내용은 팩 객체 캐시를 참조하세요.
Git 번들 URI 구성#
CDN과 같은 낮은 지연 시간을 가진 서드파티 저장소에 Git 번들을 만들고 저장합니다. Git은 먼저 번들에서 패키지를 다운로드한 다음 Git 원격에서 나머지 객체와 참조를 페치합니다. 이 방법은 객체 데이터베이스를 더 빠르게 부트스트랩하고 Gitaly의 부하를 줄입니다.
- GitLab 서버에 대한 네트워크 연결이 좋지 않은 사용자의 클론 및 페치 속도를 높입니다.
- 번들을 사전 로드하여 CI/CD 작업을 실행하는 서버의 부하를 줄입니다.
자세한 내용은 번들 URI를 참조하세요.
Gitaly 협상 타임아웃 구성#
저장소를 페치하거나 아카이브하려고 할 때 다음과 같은 경우 fatal: the remote end hung up unexpectedly 오류가 발생할 수 있습니다:
- 대용량 저장소.
- 많은 저장소의 병렬 처리.
- 동일한 대형 저장소의 병렬 처리.
이 문제를 완화하려면 기본 협상 타임아웃 값을 늘립니다.
하드웨어 크기 적절히 조정#
모노레포는 일반적으로 많은 사용자를 가진 더 큰 조직을 위한 것입니다. 모노레포를 지원하기 위해 GitLab 환경은 GitLab 테스트 플랫폼 및 지원 팀이 제공하는 참조 아키텍처 중 하나와 일치해야 합니다. 이러한 아키텍처는 성능을 유지하면서 대규모로 GitLab을 배포하는 권장 방법입니다.
Git 참조 수 줄이기#
Git에서 참조는 특정 커밋을 가리키는 브랜치와 태그 이름입니다. Git은 참조를 저장소의 .git/refs 폴더에 느슨한 파일로 저장합니다. 저장소의 모든 참조를 보려면 git for-each-ref를 실행합니다.
저장소의 참조 수가 증가할수록 특정 참조를 찾는 데 필요한 검색 시간도 증가합니다. Git이 참조를 파싱할 때마다 검색 시간 증가로 인해 지연 시간이 증가합니다.
이 문제를 해결하기 위해 Git은 pack-refs를 사용하여 해당 저장소의 모든 참조를 포함하는 단일 .git/packed-refs 파일을 만듭니다. 이 방법은 참조에 필요한 저장 공간을 줄입니다. 단일 파일에서 검색이 디렉토리의 모든 파일을 검색하는 것보다 빠르기 때문에 검색 시간도 줄어듭니다.
Git은 느슨한 파일로 새로 만들어지거나 업데이트된 참조를 처리합니다. git pack-refs를 실행할 때까지 이 파일들은 정리되어 .git/packed-refs 파일에 추가되지 않습니다. Gitaly는 하우스키핑 중에 git pack-refs를 실행합니다. 이것이 많은 저장소에 도움이 되지만 쓰기가 많은 저장소는 여전히 다음 성능 문제를 겪습니다:
- 참조를 만들거나 업데이트하면 새로운 느슨한 파일이 생성됩니다.
- 참조를 삭제하려면 기존
packed-refs파일을 편집하여 기존 참조를 제거해야 합니다.
저장소를 페치하거나 클론할 때 Git은 모든 참조를 반복합니다. 서버는 각 참조의 내부 그래프 구조를 검토("탐색")하고 누락된 객체를 찾아 클라이언트에 보냅니다. 반복 및 탐색 프로세스는 CPU 집약적이며 지연 시간을 증가시킵니다. 이 지연 시간은 활동이 많은 저장소에서 도미노 효과를 일으킬 수 있습니다. 각 작업이 느려지고 각 작업이 이후 작업을 지연시킵니다.
모노레포에서 많은 수의 참조의 영향을 완화하려면:
-
오래된 브랜치를 정리하는 자동화된 프로세스를 만듭니다.
-
특정 참조를 클라이언트에 표시할 필요가 없다면
transfer.hideRefs구성 설정을 사용하여 숨깁니다. Gitaly는 서버상의 Git 구성을 무시하므로/etc/gitlab/gitlab.rb에서 Gitaly 구성 자체를 변경해야 합니다:gitaly['configuration'] = { # ... git: { # ... config: [ # ... { key: "transfer.hideRefs", value: "refs/namespace_to_hide" }, ], }, }
Git 2.42.0 이상에서는 다양한 Git 작업이 객체 그래프 탐색을 수행할 때 숨겨진 참조를 건너뛸 수 있습니다.
저장소 최적화 작업 예약#
Git 저장소의 객체 데이터베이스에 데이터가 저장되는 방식은 시간이 지남에 따라 비효율적이 될 수 있어 Git 작업이 느려집니다. Gitaly가 최대 지속 시간이 있는 일일 백그라운드 작업을 실행하도록 예약하여 이런 항목을 정리하고 성능을 개선할 수 있습니다.
모노레포를 위한 CI/CD 최적화#
모노레포에서 GitLab의 확장성을 유지하려면 CI/CD 작업이 저장소와 상호 작용하는 방식을 최적화하세요. 크고 긴 파이프라인은 모노레포의 일반적인 문제점입니다. 모노레포의 파이프라인 구성에서 변경 유형을 감지하는 빌드 규칙을 사용하여:
- 불필요한 작업을 건너뜁니다.
- 자식 파이프라인에서 관련 작업만 실행합니다.
CI/CD에서 동시 클론 줄이기#
예약된 파이프라인을 다른 시간에 실행하도록 분산하여 CI/CD 파이프라인 동시성을 줄이세요. 몇 분 차이도 도움이 될 수 있습니다.
CI/CD 부하는 파이프라인이 특정 시간에 예약되기 때문에 종종 동시에 발생합니다. 저장소에 대한 Git 요청은 이 시간 동안 급증할 수 있으며 CI/CD 프로세스와 사용자의 성능에 영향을 미칩니다.
CI/CD 프로세스에서 얕은 클론 및 필터 사용#
CI/CD 시스템의 git clone 및 git fetch 호출에서 다음 옵션으로 전송되는 데이터 양을 제한할 수 있습니다:
CI/CD에서의 얕은 클론#
--depth 필터는 소위 _얕은 클론_을 만듭니다.
GitLab과 GitLab Runner는 기본적으로
얕은 클론을 수행합니다.
클론 깊이는 GIT_DEPTH를 사용하여 GitLab CI/CD 파이프라인 구성에서 구성할 수 있습니다. 예를 들어:
variables:
GIT_DEPTH: 10
test:
script:
- ls -al
CI/CD에서의 부분 클론#
--filter 옵션을 사용하여 _부분 클론_을 만듭니다.
이 인수를 git-clone에 전달하려면 GIT_CLONE_EXTRA_FLAGS 변수를 설정합니다. 예를 들어, blob의 최대 크기를 1MB로 제한하려면 다음을 추가합니다:
variables:
GIT_CLONE_EXTRA_FLAGS: --filter=blob:limit=1m
경로와 객체 유형 필터링#
특정 유형의 객체나 특정 경로의 객체를 필터링하려면 git sparse-checkout 옵션을 사용합니다.
자세한 내용은 파일 경로별 필터링을 참조하세요.
CI/CD 작업에서 git fetch 사용#
저장소의 작업 복사본을 유지하는 것이 가능하다면 CI/CD 시스템에서 git clone 대신 git fetch를 사용합니다. git fetch는 서버의 작업이 적습니다:
git clone은 처음부터 전체 저장소를 요청합니다.git-pack-objects는 모든 브랜치와 태그를 처리하고 보내야 합니다.git fetch는 저장소에서 누락된 Git 참조만 요청합니다.git-pack-objects는 전체 Git 참조의 하위 집합만 처리합니다. 이 전략은 전송되는 총 데이터도 줄입니다.
기본적으로 GitLab은 대용량 저장소에 권장되는 fetch Git 전략을 사용합니다.
git clone 경로 설정#
모노레포가 포크 기반 워크플로우와 함께 사용되는 경우 GIT_CLONE_PATH를 설정하여 저장소를 클론하는 위치를 제어하는 것을 고려하세요.
Git은 포크를 별도의 워크트리가 있는 별도의 저장소로 저장합니다. GitLab Runner는 워크트리 사용을 최적화할 수 없습니다. 주어진 프로젝트에 대해서만 GitLab Runner 실행기를 구성하고 사용합니다. 프로세스를 더 효율적으로 만들려면 다른 프로젝트 간에 공유하지 마세요.
GIT_CLONE_PATH는 $CI_BUILDS_DIR에 설정된 디렉토리에 있어야 합니다. 디스크에서 임의의 경로를 선택할 수 없습니다.
CI/CD 작업에서 git clean 비활성화#
git clean 명령은 작업 트리에서 추적되지 않은 파일을 제거합니다. 대형 저장소에서는 많은 디스크 I/O를 사용합니다. 기존 시스템을 재사용하고 기존 워크트리를 재사용할 수 있다면 CI/CD 작업에서 비활성화하는 것을 고려하세요. 예를 들어, GIT_CLEAN_FLAGS: -ffdx -e .build/는 실행 사이에 워크트리에서 디렉토리를 삭제하지 않도록 할 수 있습니다. 이를 통해 증분 빌드 속도를 높일 수 있습니다.
CI/CD 작업에서 git clean을 비활성화하려면 GIT_CLEAN_FLAGS를 해당 작업에 대해 none으로 설정합니다.
기본적으로 GitLab은 다음을 보장합니다:
- 주어진 SHA에서 워크트리가 있음.
- 저장소가 깨끗함.
GIT_CLEAN_FLAGS에서 허용되는 정확한 매개변수에 대해서는 git clean에 대한 Git 문서를 참조하세요. 사용 가능한 매개변수는 Git 버전에 따라 다릅니다.
플래그로 git fetch 동작 변경#
CI/CD 작업에 필요하지 않은 데이터를 제외하도록 git fetch 동작을 변경합니다. 프로젝트에 많은 태그가 포함되어 있고 CI/CD 작업에 태그가 필요하지 않은 경우 GIT_FETCH_EXTRA_FLAGS를 사용하여 --no-tags를 설정합니다. 이 설정으로 페치가 더 빠르고 간결해집니다.
저장소에 태그가 많지 않더라도 --no-tags는 일부 경우에 성능을 향상시킬 수 있습니다.
자세한 내용은 이슈 746과 GIT_FETCH_EXTRA_FLAGS Git 문서를 참조하세요.
러너에 롱 폴링 사용#
러너는 새 CI/CD 작업을 위해 GitLab 인스턴스를 주기적으로 폴링합니다. 폴링 간격은 다음 두 가지에 따라 달라집니다:
check_interval설정.- 러너 구성 파일에서 구성된 러너 수.
서버가 많은 러너를 처리하는 경우 이 폴링은 더 긴 대기 시간과 높은 CPU 사용량과 같은 GitLab 인스턴스의 성능 문제를 일으킬 수 있습니다. 롱 폴링은 새 작업이 준비될 때까지 러너의 작업 요청을 보류합니다.
구성 지침은 롱 폴링을 참조하세요.
모노레포를 위한 Git 최적화#
모노레포에서 GitLab의 확장성을 유지하려면 저장소 자체를 최적화합니다.
개발에 얕은 클론 사용 피하기#
개발에는 얕은 클론을 사용하지 마세요. 얕은 클론은 변경 사항을 푸시하는 데 필요한 시간을 크게 늘립니다. 얕은 클론은 체크아웃 후 저장소 콘텐츠가 변경되지 않기 때문에 CI/CD 작업에서 잘 작동합니다.
로컬 개발에는 다음을 위해 대신 부분 클론을 사용합니다:
git clone --filter=blob:none으로 blob 필터링git clone --filter=tree:0으로 트리 필터링
자세한 내용은 클론 크기 줄이기를 참조하세요.
저장소 프로파일링으로 문제 찾기#
대형 저장소는 일반적으로 Git에서 성능 문제를 겪습니다. git-sizer 프로젝트는 저장소를 프로파일링하고 잠재적인 문제를 이해하는 데 도움을 줍니다. 이를 통해 성능 문제를 방지하는 완화 전략을 개발하는 데 도움을 받을 수 있습니다.
저장소 분석에는 모든 Git 참조가 있는지 확인하기 위해 전체 Git 미러 또는 베어 클론이 필요합니다.
git-sizer로 저장소를 프로파일링하려면:
-
다음 명령을 실행하여
git-sizer와 호환되는 베어 Git 형식으로 저장소를 클론합니다:git clone --mirror <git_repo_url> -
Git 저장소 디렉토리에서 모든 통계로
git-sizer를 실행합니다:git-sizer -v
처리 후 git-sizer의 출력은 이 예시처럼 보여야 합니다. 각 행에는 저장소의 해당 측면에 대한 우려 수준이 포함됩니다. 더 높은 우려 수준은 더 많은 별표로 표시됩니다. 극히 높은 우려 수준의 항목은 느낌표로 표시됩니다. 이 예시에서 몇 가지 항목의 우려 수준이 높습니다:
Processing blobs: 1652370
Processing trees: 3396199
Processing commits: 722647
Matching commits to trees: 722647
Processing annotated tags: 534
Processing references: 539
| Name | Value | Level of concern |
| ---------------------------- | --------- | ------------------------------ |
| Overall repository size | | |
| * Commits | | |
| * Count | 723 k | * |
| * Total size | 525 MiB | ** |
| * Trees | | |
| * Count | 3.40 M | ** |
| * Total size | 9.00 GiB | **** |
| * Total tree entries | 264 M | ***** |
| * Blobs | | |
| * Count | 1.65 M | * |
| * Total size | 55.8 GiB | ***** |
| * Annotated tags | | |
| * Count | 534 | |
| * References | | |
| * Count | 539 | |
| | | |
| Biggest objects | | |
| * Commits | | |
| * Maximum size [1] | 72.7 KiB | * |
| * Maximum parents [2] | 66 | ****** |
| * Trees | | |
| * Maximum entries [3] | 1.68 k | * |
| * Blobs | | |
| * Maximum size [4] | 13.5 MiB | * |
| | | |
| History structure | | |
| * Maximum history depth | 136 k | |
| * Maximum tag depth [5] | 1 | |
| | | |
| Biggest checkouts | | |
| * Number of directories [6] | 4.38 k | ** |
| * Maximum path depth [7] | 13 | * |
| * Maximum path length [8] | 134 B | * |
| * Number of files [9] | 62.3 k | * |
| * Total size of files [9] | 747 MiB | |
| * Number of symlinks [10] | 40 | |
| * Number of submodules | 0 | |
대용량 바이너리 파일에 Git LFS 사용#
바이너리 파일(패키지, 오디오, 비디오, 그래픽 등)을 Git Large File Storage(Git LFS) 객체로 저장합니다.
사용자가 파일을 Git에 커밋할 때 Git은 blob 객체 유형을 사용하여 콘텐츠를 저장하고 관리합니다.
Git은 대용량 바이너리 데이터를 효율적으로 처리하지 않으므로 대용량 blob은 Git에 문제가 됩니다. git-sizer가 10MB 이상의 blob을 보고하면 일반적으로 저장소에 대용량 바이너리 파일이 있음을 의미합니다. 대용량 바이너리 파일은 서버와 클라이언트 모두에게 문제를 일으킵니다:
- 서버의 경우: 텍스트 기반 소스 코드와 달리 바이너리 데이터는 종종 이미 압축되어 있습니다. Git은 바이너리 데이터를 더 압축할 수 없어 대용량 팩파일이 생성됩니다. 대용량 팩파일은 생성하고 전송하는 데 더 많은 CPU, 메모리, 대역폭이 필요합니다.
- 클라이언트의 경우: Git은 blob 콘텐츠를 팩파일(일반적으로
.git/objects/pack/)과 일반 파일(워크트리) 모두에 저장하므로 바이너리 파일은 텍스트 기반 소스 코드보다 훨씬 더 많은 공간이 필요합니다.
Git LFS는 객체를 외부(예: 객체 스토리지)에 저장합니다. Git 저장소에는 바이너리 파일 자체가 아닌 객체의 위치에 대한 포인터가 포함됩니다. 이를 통해 저장소 성능을 향상시킬 수 있습니다. 자세한 내용은 Git LFS 문서를 참조하세요.
