GitLab에서 Git 객체 중복 제거가 작동하는 방법

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

Pool 리포지터리

Git 대체 이해

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

리포지터리 A가 리포지터리 B에서 빌릴 수 있도록 하려면:

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

재팩 한 이후에는 리포지터리 A가 더 이상 자체 포함된 상태가 아니지만 자체 참조와 구성은 그대로 유지합니다. B에 없는 A의 객체는 A에 남아 있습니다. 이 구성이 작동하려면 B 리포지터리에서 객체를 삭제하지 않도록해야 합니다. A 리포지터리가 해당 객체를 필요로 할 수 있기 때문입니다.

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

위험 요소는 git prune이며, git gcgit prune을 호출합니다. 문제는 객체 풀 리포지터리에서 실행되는 git prune이 더 이상 필요하지 않은 객체를 신뢰할 수 없다는 것입니다.

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

GitLab은 이 객체 빌리기를 사용자에게 숨겨진 특수 풀 리포지터리를 만들어 조직화합니다. 그런 다음 Git 대체를 사용하여 프로젝트 리포지터리의 컬렉션이 단일 풀 리포지터리에서 빌리도록 합니다. 이와 같은 프로젝트 리포지터리의 모음을 풀이라고 하며, 풀은 유저가 프로젝트를 포크할 때 형성되는 포크 네트워크와 유사하지만 동일하지는 않습니다.

Git 수준에서 풀 리포지터리는 Gitaly RPC 호출을 사용하여 생성 및 관리됩니다. 일반적인 리포지터리와 마찬가지로 풀 리포지터리의 존재와 해당 리포지터리가 빌려 오는 리포지터리는 SQL에서 레일 응용 프로그램 수준에서 권한이 있습니다.

요약하면, GitLab 프로젝트 리포지터리 컬렉션 간에 효과적인 객체 중복을 위해 세 가지가 필요합니다:

  1. 풀 리포지터리가 있어야 합니다.
  2. 참가하는 프로젝트 리포지터리는 각자의 objects/info/alternates 파일을 통해 풀 리포지터리에 연결되어 있어야 합니다.
  3. 풀 리포지터리에는 참가하는 프로젝트 리포지터리에 공통인 Git 객체 데이터가 있어야 합니다.

중복 제거 요인

GitLab의 Git 객체 중복의 효과는 풀 리포지터리와 해당 참가자 간의 중첩 양에 따라 달라집니다. 소스 프로젝트에서 가비지 수집이 실행될 때마다 소스 프로젝트의 Git 객체가 풀 리포지터리로 이동합니다. 가비지 수집이 실행될 때마다 새로 추가된 객체로 다른 회원 프로젝트가 혜택을 받게 됩니다.

SQL 모델

GitLab 11.8부터 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 클론입니다.
  • 풀을 생성하는 기회는 기존의 적합한(비공개, 해시 저장, 포크되지 않은) GitLab 프로젝트가 포크되었을 때입니다. 포크 원본 프로젝트가 새 풀의 원본 프로젝트가 되고, 포크 원본과 포크 자식 프로젝트가 새 풀의 구성원이 됩니다.
  • 프로젝트 A가 풀의 원본 프로젝트가 되면, A의 모든 향후 적합한 포크가 풀 구성원이 됩니다.
  • 포크 원본이 자체로 포크된 경우, 결과 리포지터리는 해당 리포지터리에 합류하지 않으며 새 풀 리포지터리로 설정되지도 않습니다.

    다음과 같이:

    포크 A가 풀 리포지터리의 일부인 경우, 포크 A에서 생성된 어떤 포크도 포크 A가 속한 풀 리포지터리의 일부가 아닙니다.

    B가 A의 포크이고, A가 객체 풀에 속하지 않으면 C가 B의 포크로 생성됩니다. C는 풀 리포지터리의 일부가 아닙니다.

결과

  • 풀에 참여하는 일반 프로젝트가 다른 Gitaly 저장 샤드로 이동되면 “PoolRepository에 속함” 관계가 끊어집니다. 리포지터리를 샤드 간에 이동하는 방식 때문에 새롭고 독립적인 프로젝트 리포지터리의 복사본을 새 저장 샤드에 얻게 됩니다.
  • 풀의 원본 프로젝트가 다른 Gitaly 저장 샤드로 이동되거나 삭제되면 “원본 프로젝트” 관계는 끊어지지 않습니다. 그러나 GitLab 12.0부터는 풀은 원본이 동일한 Gitaly 샤드에 있는 경우에만 원본에서 가져오지 않습니다.

SQL 풀 관계와 Gitaly 간의 일관성

Gitaly의 경우, SQL 풀 관계는 Gitaly 서버의 상태에 대해 두 가지 유형의 주장을 제기합니다: 풀 리포지터리의 존재와 리포지터리와 풀 간의 대체 연결의 존재.

풀 존재

GitLab이 풀 리포지터리가 존재한다고 생각하지만(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 객체가 중복 처리됩니다.