GitLab에서 Git 개체 중복 처리 방법

GitLab 사용자가 프로젝트를 포크할 때, GitLab은 포크 당시 원본 프로젝트의 사본인 연관된 Git 리포지터리를 포함한 새 프로젝트를 생성합니다. 큰 프로젝트가 여러 번 포크되면 Git 리포지터리 저장 공간 사용량이 빠르게 증가할 수 있습니다. 이 문제를 해결하기 위해 GitLab은 포크용 Git 개체 중복을 추가하고 있습니다. 이 문서에서는 GitLab이 Git 개체 중복을 어떻게 구현하는지에 대해 설명합니다.

풀 리포지터리

Git 대체 이해

Git 수준에서 중복을 달성하기 위해 Git 대체를 사용합니다. Git 대체는 동일한 기계의 다른 리포지터리에서 리포지터리가 개체를 빌려올 수 있는 메커니즘입니다.

리포지터리 A가 리포지터리 B에서 빌림:

  1. 특별한 파일 A.git/objects/info/alternatesB.git/objects로 해결되는 경로를 작성하여 대체 링크를 설정합니다.
  2. A 리포지터리에서 git repack을 실행하여 A 리포지터리에 있는 B 리포지터리에도 있는 모든 개체를 제거합니다.

재패킹 후에 리포지터리 A는 더 이상 자체 포함되지 않지만 여전히 해당 refs와 구성을 포함합니다. B에 없는 A의 개체는 A에 남아 있습니다. 이 구성이 작동하려면 리포지터리 B에서 개체를 삭제해서는 안 됩니다. A 리포지터리에서 이를 필요로 할 수 있기 때문입니다.

caution
@pools 디렉터리에 저장된 개체 풀 리포지터리에서 git prune 또는 git gc를 실행하지 마십시오. 이로 인해 개체 풀에 의존하는 일반 리포지터리에서 데이터가 손실될 수 있습니다.

위험은 git prunegit gcgit prune을 호출한다는 점에 있습니다. 문제는 개체 풀 리포지터리에서 실행되는 git prune이 개체가 더 이상 필요하지 않은지를 신뢰할 수 없다는 것입니다.

GitLab의 Git 대체: 풀 리포지터리

GitLab은 이 객체 빌림을 특별한 풀 리포지터리를 생성하여 조직화하며 사용자로부터 숨깁니다. 그런 다음 Git 대체를 사용하여 여러 프로젝트 리포지터리의 컬렉션이 단일 풀 리포지터리에서 빌릴 수 있도록 합니다. 이러한 프로젝트 리포지터리의 컬렉션을 풀이라고 합니다. 풀은 사용자가 프로젝트를 포크할 때 형성되는 포크 네트워크와 유사하지만 동일하지는 않습니다.

Git 수준에서 풀 리포지터리는 Gitaly RPC 호출을 사용하여 생성 및 관리됩니다. 일반적인 리포지터리와 마찬가지로 풀 리포지터리의 존재 및 빌림 리포지터리 관리권한은 SQL에서 Rails 애플리케이션 수준에 있습니다.

결론적으로 GitLab 프로젝트 리포지터리 컬렉션 간의 효과적인 객체 중복을 위해 세 가지가 필요합니다:

  1. 풀 리포지터리가 있어야 합니다.
  2. 참여 프로젝트 리포지터리는 각각의 objects/info/alternates 파일을 통해 풀 리포지터리에 연결되어 있어야 합니다.
  3. 풀 리포지터리에는 참여 프로젝트 리포지터리에서 공통인 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).

가정

  • 풀에있는 모든 리포지터리는 해시 리포지터리를 사용해야 합니다. 이렇게 함으로써 object/info/alternates 파일의 경로를 업데이트할 필요가 없습니다.
  • 풀에 있는 모든 리포지터리는 동일한 Gitaly 리포지터리 샤드에 있어야 합니다. Git 대체 메커니즘은 여러 리포지터리를 통해 직접 디스크 액세스에 의존하며, 우리는 Gitaly 리포지터리 샤드 내에서 직접 디스크 액세스를 가능하다고 가정할 수 있습니다.
  • 풀의 멤버 프로젝트에서 풀을 제거하는 유일한 두 가지 방법은 (1) 프로젝트를 삭제하거나 (2) 프로젝트를 다른 Gitaly 리포지터리 샤드로 이동하는 것입니다.

풀 및 풀 멤버십 생성

  • 풀이 생성되면 소스 프로젝트가 있어야 합니다. 풀 리포지터리의 초기 내용은 소스 프로젝트 리포지터리의 Git 복제입니다.
  • 풀을 생성하는 시기는 기존의 적합한 (비공개, 해시 리포지터리, fork되지 않은) GitLab 프로젝트가 포크되었을 때입니다. 포크 부모 프로젝트는 새 풀의 소스 프로젝트가 되며, 포크 부모 및 포크 자식 프로젝트는 새 풀의 멤버가 됩니다.
  • 프로젝트 A가 풀의 소스 프로젝트가 되면 A의 모든 미래의 적합한 포크는 풀 멤버가 됩니다.
  • 포크 소스가 자체 포크인 경우, 결과 리포지터리는 리포지터리에도 가입하지 않으며 새 풀 리포지터리가 시드되지 않습니다.

    예:

    포크 A가 풀 리포지터리의 일부인 경우 포크 A에서 생성된 포크들은 fork 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는 DisconnectGitAlternates RPC를 사용하여 리포지터리 A를 “재중복”시키려고 시도합니다.

Git 객체 중복 및 GitLab Geo

Geo 기본 제공 역할을 하는 SQL에서 풀 리포지터리 레코드가 생성되면, 이는 결국 Geo 보조 역할을 하는 이벤트를 트리거합니다. 그럼 Geo 보조는 Gitaly에 풀 리포지터리를 생성합니다. 이로써 “결국적으로 일관된” 상황이 발생하는데, 각 풀 참여자가 동기화될 때마다 Geo는 보조 역할을 하는 곳에서 Gitaly에서 쓰레기 수집을 트리거하고, 그 단계에서 Git 객체가 중복 제거됩니다.