GitLab에서 Git 객체 중복 제거가 작동하는 방식
GitLab 사용자가 프로젝트 포크를 하면,
GitLab은 포크 시점의 원본 프로젝트 복사본인 새로운 프로젝트와 이를 연결하는 Git 저장소를 생성합니다.
대규모 프로젝트가 자주 포크되면 Git 저장소 저장 디스크 사용량이 급격히 증가할 수 있습니다.
이 문제를 해결하기 위해, 우리는 GitLab에서 포크를 위한 Git 객체 중복 제거 기능을 추가하고 있습니다.
이 문서에서는 GitLab이 Git 객체 중복 제거를 어떻게 구현하는지 설명합니다.
풀 저장소
Git alternates 이해하기
Git 레벨에서 우리는 Git alternates를 사용하여 중복 제거를 달성합니다.
Git alternates는 저장소가 동일한 기계에 있는 다른 저장소에서 객체를 빌려올 수 있게 하는 메커니즘입니다.
저장소 A가 저장소 B에서 빌리도록 설정하려면:
-
A.git/objects/info/alternates
라는 특별한 파일에서B.git/objects
에 해당하는 경로를 써서 alternates 링크를 설정합니다. -
저장소 A에서
git repack
을 실행하여 저장소 A에 있는 객체 중 저장소 B에도 존재하는 모든 객체를 제거합니다.
repack 후, 저장소 A는 더 이상 독립적인 상태가 아니더라도, 여전히 자신의 refs와 구성을 포함합니다.
A에 있는 객체 중 B에 존재하지 않는 객체는 A에 남습니다. 이 구성이 작동하려면 객체는 저장소 B에서 삭제되지 않아야 합니다.
저장소 A가 필요할 수 있기 때문입니다.
경고:
객체 풀 저장소에서 git prune
또는 git gc
를 실행하지 마십시오. 이로 인해 객체 풀에 의존하는 일반 저장소에서 데이터 손실이 발생할 수 있습니다.
위험은 git prune
에 있으며, git gc
는 git prune
을 호출합니다.
문제는 풀 저장소에서 실행되는 git prune
이 객체가 더 이상 필요하지 않은지 신뢰할 수 없게 결정할 수 없다는 점입니다.
GitLab의 Git alternates: 풀 저장소
GitLab은 특별한 풀 저장소를 생성하여 이 객체 빌리기를 조직하며, 이는 사용자에게 숨겨져 있습니다.
그런 다음 Git alternates를 사용하여 프로젝트 저장소 모음이 단일 풀 저장소에서 빌릴 수 있도록 합니다.
이러한 프로젝트 저장소 모음을 풀이라고 부릅니다.
풀은 단일 풀로부터 빌리는 저장소의 별 모양 네트워크를 형성하며, 이는 사용자가 프로젝트를 포크할 때 형성되는 포크 네트워크와 비슷하지만 동일하지는 않습니다.
Git 레벨에서 풀 저장소는 Gitaly RPC 호출을 사용하여 생성 및 관리됩니다.
일반 저장소와 마찬가지로, 어떤 풀 저장소가 존재하는지 및 어떤 저장소가 이를 빌리는지는 Rails 애플리케이션 레벨에서 SQL로 결정됩니다.
결론적으로, Git 레벨에서 GitLab 프로젝트 저장소 모음 전반에 걸쳐 효과적인 객체 중복 제거를 위해 세 가지가 필요합니다:
-
풀 저장소가 존재해야 합니다.
-
참여하는 프로젝트 저장소는 각각의
objects/info/alternates
파일을 통해 풀 저장소와 연결되어 있어야 합니다. -
풀 저장소는 참여하는 프로젝트 저장소에 공통된 Git 객체 데이터를 포함해야 합니다.
중복 제거 비율
GitLab에서 Git 객체 중복 제거의 효과는 풀 저장소와 각 참여자의 겹치는 정도에 따라 달라집니다.
소스 프로젝트에서 가비지 수집이 실행될 때마다 소스 프로젝트의 Git 객체가 풀 저장소로 마이그레이션됩니다.
가비지 수집이 실행될 때마다, 다른 구성원 프로젝트는 풀에 추가된 새로운 객체로부터 혜택을 받게 됩니다.
SQL 모델
GitLab의 프로젝트 리포지토리는 자체 SQL 테이블을 가지고 있지 않습니다.
그들은 projects
테이블의 열로 간접 식별됩니다.
다시 말해, 프로젝트 리포지토리를 조회하는 유일한 방법은 먼저 그 프로젝트를 조회하고,
그런 다음 project.repository
를 호출하는 것입니다.
풀 리포지토리와 함께 우리는 새로운 시작을 했습니다.
이들은 자체 pool_repositories
SQL 테이블에 존재합니다.
이 두 테이블 간의 관계는 다음과 같습니다:
-
Project
는 최대 하나의PoolRepository
에 속합니다 (project.pool_repository
) -
위와 같은 자동적인 결과로 인해,
PoolRepository
는 여러 개의Project
를 가집니다. -
PoolRepository
는 정확히 하나의 “소스Project
“를 가집니다 (pool.source_project
)
가정
-
풀에 있는 모든 리포지토리는 동일한 Gitaly 스토리지 샤드에 있어야 합니다.
Git의 대체 메커니즘은 여러 리포지토리 간의 직접 디스크 접근에 의존하며,
우리는 Gitaly 스토리지 샤드 내에서만 직접 디스크 접근이 가능하다고 가정할 수 있습니다.
-
풀에서 멤버 프로젝트를 제거하는 유일한 두 가지 방법은 (1) 프로젝트를 삭제하거나 (2) 프로젝트를 다른 Gitaly 스토리지 샤드로 이동하는 것입니다.
풀 및 풀 멤버십 생성
-
풀이 생성될 때, 반드시 소스 프로젝트가 있어야 합니다.
풀 리포지토리의 초기 내용은 소스 프로젝트 리포지토리의 Git 클론입니다.
-
풀을 생성하는 경우는 기존의 적격한 (비공개, 해시된 스토리지, 비포크) GitLab 프로젝트가 포크되고
이 프로젝트가 아직 풀 리포지토리에 속하지 않을 때입니다.
포크 부모 프로젝트는 새 풀의 소스 프로젝트가 되며, 포크 부모 및 포크 자식 프로젝트는
새 풀의 멤버가 됩니다.
-
프로젝트 A가 풀의 소스 프로젝트가 되면,
A의 모든 미래 적격 포크가 풀 멤버가 됩니다.
-
포크 소스가 자체가 포크인 경우, 결과 리포지토리는
리포지토리에 가입하지 않거나 새 풀 리포지토리가 생성되지 않습니다.
예를 들어:
포크 A가 풀 리포지토리의 일부분이라고 가정할 때, 포크 A에서 생성된 모든 포크는
포크 A가 속한 풀 리포지토리의 일부분이 아닙니다.
B가 A의 포크이고, A가 객체 풀에 속하지 않는다고 가정합니다.
이제 C가 B의 포크로 생성됩니다. C는 풀 리포지토리의 일부분이 아닙니다.
결과
-
풀에 참여하는 일반적인 프로젝트가 다른 Gitaly 스토리지 샤드로 이동하는 경우,
그 “PoolRepository에 속함” 관계가 끊어집니다.
샤드 간 리포지토리 이동 방식 때문에,
우리는 새로운 스토리지 샤드에서 프로젝트의 리포지토리에 대한 독립적인 복사본을 얻습니다.
-
풀의 소스 프로젝트가 다른 Gitaly 스토리지 샤드로 이동하거나 삭제되면,
“소스 프로젝트” 관계는 끊어지지 않습니다.
그러나 풀은 소스가 동일한 Gitaly 샤드에 있지 않으면 소스에서 가져오지 않습니다.
SQL 풀 관계와 Gitaly 간의 일관성
Gitaly와 관련하여 SQL 풀 관계는 Gitaly 서버의 상태에 대해 두 가지 유형의 주장을 합니다: 풀 리포지토리 존재와
리포지토리와 풀 간의 대체 연결 존재.
풀 존재
GitLab이 풀 리포지토리가 존재한다고 생각하지만 (즉, SQL에 따라 존재하지만),
Gitaly 서버에는 존재하지 않는 경우,
Gitaly에 의해 즉시 생성됩니다.
풀 관계 존재
여기서 잘못될 수 있는 세 가지가 있습니다.
1. SQL이 레포지토리 A가 풀 P에 속한다고 말하지만 Gitaly는 A에 대체 객체가 없다고 말함
이 경우 디스크 공간 절약을 놓치지만 A 자체의 모든 RPC는 정상적으로 작동합니다.
다음번에 A에서 가비지 수집이 실행되면, 대체 연결이 Gitaly에서 설정됩니다.
이것은 GitLab Rails의 Projects::GitDeduplicationService
에 의해 수행됩니다.
2. SQL이 레포지토리 A가 풀 P1에 속한다고 말하지만 Gitaly는 A가 풀 P2에 대체 객체가 있다고 말함
이 경우 Projects::GitDeduplicationService
가 예외를 발생시킵니다.
3. SQL이 레포지토리 A가 어떤 풀에도 속하지 않는다고 말하지만 Gitaly는 A가 P에 속한다고 말함
이 경우 Projects::GitDeduplicationService
가
레포지토리 A를 DisconnectGitAlternates RPC를 사용하여 “다시 중복”하려고 시도합니다.
Git 객체 중복 제거 및 GitLab Geo
Geo 기본에서 풀 레포지토리 레코드가 SQL에 생성되면, 이는 결국 Geo 보조에서 이벤트를 트리거합니다.
Geo 보조는 이후 Gitaly에서 풀 레포지토리를 생성합니다.
이로 인해 “최종적으로 일관된” 상황이 발생하는데, 각 풀 참가자가 동기화됨에 따라 Geo는 결국 보조의 Gitaly에서 가비지 수집을 트리거하여, 이 시점에서 Git 객체가 중복 제거됩니다.