InfoGrab DocsInfoGrab Docs

GitLab에서 Git 오브젝트 중복 제거가 작동하는 방식

요약

GitLab 사용자가 프로젝트를 포크하면, GitLab은 포크 시점의 원본 프로젝트 복사본인 Git 리포지터리가 연결된 새 프로젝트를 생성합니다. Git 수준에서는 Git alternates를 사용하여 중복 제거를 달성합니다.

GitLab 사용자가 프로젝트를 포크하면, GitLab은 포크 시점의 원본 프로젝트 복사본인 Git 리포지터리가 연결된 새 프로젝트를 생성합니다. 대규모 프로젝트가 자주 포크될 경우, Git 리포지터리 스토리지 디스크 사용량이 빠르게 증가할 수 있습니다. 이 문제를 해결하기 위해 GitLab에 포크를 위한 Git 오브젝트 중복 제거 기능을 추가하고 있습니다. 이 문서에서는 GitLab이 Git 오브젝트 중복 제거를 어떻게 구현하는지 설명합니다.

풀 리포지터리#

Git alternates 이해하기#

Git 수준에서는 Git alternates를 사용하여 중복 제거를 달성합니다. Git alternates는 리포지터리가 동일 머신의 다른 리포지터리에서 오브젝트를 빌려올 수 있게 해주는 메커니즘입니다.

리포지터리 A가 리포지터리 B에서 빌려오도록 만들려면 다음을 수행합니다:

  • 특수 파일 A.git/objects/info/alternatesB.git/objects로 해석되는 경로를 작성하여 alternates 링크를 설정합니다.

  • 리포지터리 A에서 git repack을 실행하여 리포지터리 B에도 존재하는 모든 오브젝트를 A에서 제거합니다.

repack 후, 리포지터리 A는 더 이상 자급자족하지 않지만 자체 refs와 설정은 유지됩니다. B에 없는 A의 오브젝트는 A에 남아 있습니다. 이 구성이 제대로 작동하려면 리포지터리 B에서 오브젝트를 삭제하면 안 됩니다. 리포지터리 A가 해당 오브젝트를 필요로 할 수 있기 때문입니다.

@pools 디렉터리에 저장된 오브젝트 풀 리포지터리에서는 git prune 또는 git gc를 실행하지 마십시오. 이는 오브젝트 풀에 의존하는 일반 리포지터리에서 데이터 손실을 일으킬 수 있습니다.

위험은 git prune에 있으며, git gcgit prune을 호출합니다. 풀 리포지터리에서 실행될 때 git prune은 오브젝트가 더 이상 필요하지 않은지 신뢰할 수 있게 판단하지 못하는 문제가 있습니다.

GitLab에서의 Git alternates: 풀 리포지터리#

GitLab은 사용자에게 숨겨진 특수 풀 리포지터리를 생성하여 이 오브젝트 빌려오기를 체계적으로 관리합니다. 그런 다음 Git alternates를 사용하여 프로젝트 리포지터리 컬렉션이 단일 풀 리포지터리에서 빌려오도록 합니다. 이러한 프로젝트 리포지터리 컬렉션을 풀이라고 부릅니다. 풀은 단일 풀에서 빌려오는 리포지터리의 별 모양 네트워크를 형성하며, 이는 사용자가 프로젝트를 포크할 때 형성되는 포크 네트워크와 유사하지만(완전히 동일하지는 않습니다).

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

결론적으로, 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 alternates 메커니즘은 여러 리포지터리 간의 직접 디스크 접근에 의존하며, 직접 디스크 접근은 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 서버의 상태에 대해 두 가지 유형의 주장을 합니다: 풀 리포지터리 존재 여부, 그리고 리포지터리와 풀 간의 alternates 연결 존재 여부입니다.

풀 존재#

GitLab이 풀 리포지터리가 존재한다고 생각하지만(즉, SQL에 따라 존재하지만) Gitaly 서버에는 없는 경우, Gitaly에 의해 즉시 생성됩니다.

풀 관계 존재#

여기서 잘못될 수 있는 세 가지 경우가 있습니다.

1. SQL은 리포지터리 A가 풀 P에 속한다고 하지만 Gitaly는 A에 alternate 오브젝트가 없다고 함#

이 경우 디스크 공간 절약을 놓치게 되지만 A 자체에 대한 모든 RPC는 정상적으로 작동합니다. 다음에 A에서 가비지 컬렉션이 실행될 때, alternates 연결이 Gitaly에서 설정됩니다. 이는 GitLab Rails의 Projects::GitDeduplicationService에 의해 수행됩니다.

2. SQL은 리포지터리 A가 풀 P1에 속한다고 하지만 Gitaly는 A에 풀 P2의 alternate 오브젝트가 있다고 함#

이 경우 Projects::GitDeduplicationService가 예외를 던집니다.

3. SQL은 리포지터리 A가 어떤 풀에도 속하지 않는다고 하지만 Gitaly는 A가 P에 속한다고 함#

이 경우 Projects::GitDeduplicationService는 DisconnectGitAlternates RPC를 사용하여 리포지터리 A를 "재중복"하려고 시도합니다.

Git 오브젝트 중복 제거와 GitLab Geo#

Geo 프라이머리의 SQL에서 풀 리포지터리 레코드가 생성되면, 이는 결국 Geo 세컨더리에서 이벤트를 트리거합니다. 그러면 Geo 세컨더리는 Gitaly에 풀 리포지터리를 생성합니다. 각 풀 참여자가 동기화됨에 따라 Geo가 결국 세컨더리의 Gitaly에서 가비지 컬렉션을 트리거하고, 이 단계에서 Git 오브젝트가 중복 제거되므로 "결과적 일관성" 상황이 발생합니다.

GitLab에서 Git 오브젝트 중복 제거가 작동하는 방식

GitLab v19.1
원문 보기
요약

GitLab 사용자가 프로젝트를 포크하면, GitLab은 포크 시점의 원본 프로젝트 복사본인 Git 리포지터리가 연결된 새 프로젝트를 생성합니다. Git 수준에서는 Git alternates를 사용하여 중복 제거를 달성합니다.

GitLab 사용자가 프로젝트를 포크하면, GitLab은 포크 시점의 원본 프로젝트 복사본인 Git 리포지터리가 연결된 새 프로젝트를 생성합니다. 대규모 프로젝트가 자주 포크될 경우, Git 리포지터리 스토리지 디스크 사용량이 빠르게 증가할 수 있습니다. 이 문제를 해결하기 위해 GitLab에 포크를 위한 Git 오브젝트 중복 제거 기능을 추가하고 있습니다. 이 문서에서는 GitLab이 Git 오브젝트 중복 제거를 어떻게 구현하는지 설명합니다.

풀 리포지터리#

Git alternates 이해하기#

Git 수준에서는 Git alternates를 사용하여 중복 제거를 달성합니다. Git alternates는 리포지터리가 동일 머신의 다른 리포지터리에서 오브젝트를 빌려올 수 있게 해주는 메커니즘입니다.

리포지터리 A가 리포지터리 B에서 빌려오도록 만들려면 다음을 수행합니다:

  • 특수 파일 A.git/objects/info/alternatesB.git/objects로 해석되는 경로를 작성하여 alternates 링크를 설정합니다.

  • 리포지터리 A에서 git repack을 실행하여 리포지터리 B에도 존재하는 모든 오브젝트를 A에서 제거합니다.

repack 후, 리포지터리 A는 더 이상 자급자족하지 않지만 자체 refs와 설정은 유지됩니다. B에 없는 A의 오브젝트는 A에 남아 있습니다. 이 구성이 제대로 작동하려면 리포지터리 B에서 오브젝트를 삭제하면 안 됩니다. 리포지터리 A가 해당 오브젝트를 필요로 할 수 있기 때문입니다.

@pools 디렉터리에 저장된 오브젝트 풀 리포지터리에서는 git prune 또는 git gc를 실행하지 마십시오. 이는 오브젝트 풀에 의존하는 일반 리포지터리에서 데이터 손실을 일으킬 수 있습니다.

위험은 git prune에 있으며, git gcgit prune을 호출합니다. 풀 리포지터리에서 실행될 때 git prune은 오브젝트가 더 이상 필요하지 않은지 신뢰할 수 있게 판단하지 못하는 문제가 있습니다.

GitLab에서의 Git alternates: 풀 리포지터리#

GitLab은 사용자에게 숨겨진 특수 풀 리포지터리를 생성하여 이 오브젝트 빌려오기를 체계적으로 관리합니다. 그런 다음 Git alternates를 사용하여 프로젝트 리포지터리 컬렉션이 단일 풀 리포지터리에서 빌려오도록 합니다. 이러한 프로젝트 리포지터리 컬렉션을 풀이라고 부릅니다. 풀은 단일 풀에서 빌려오는 리포지터리의 별 모양 네트워크를 형성하며, 이는 사용자가 프로젝트를 포크할 때 형성되는 포크 네트워크와 유사하지만(완전히 동일하지는 않습니다).

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

결론적으로, 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 alternates 메커니즘은 여러 리포지터리 간의 직접 디스크 접근에 의존하며, 직접 디스크 접근은 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 서버의 상태에 대해 두 가지 유형의 주장을 합니다: 풀 리포지터리 존재 여부, 그리고 리포지터리와 풀 간의 alternates 연결 존재 여부입니다.

풀 존재#

GitLab이 풀 리포지터리가 존재한다고 생각하지만(즉, SQL에 따라 존재하지만) Gitaly 서버에는 없는 경우, Gitaly에 의해 즉시 생성됩니다.

풀 관계 존재#

여기서 잘못될 수 있는 세 가지 경우가 있습니다.

1. SQL은 리포지터리 A가 풀 P에 속한다고 하지만 Gitaly는 A에 alternate 오브젝트가 없다고 함#

이 경우 디스크 공간 절약을 놓치게 되지만 A 자체에 대한 모든 RPC는 정상적으로 작동합니다. 다음에 A에서 가비지 컬렉션이 실행될 때, alternates 연결이 Gitaly에서 설정됩니다. 이는 GitLab Rails의 Projects::GitDeduplicationService에 의해 수행됩니다.

2. SQL은 리포지터리 A가 풀 P1에 속한다고 하지만 Gitaly는 A에 풀 P2의 alternate 오브젝트가 있다고 함#

이 경우 Projects::GitDeduplicationService가 예외를 던집니다.

3. SQL은 리포지터리 A가 어떤 풀에도 속하지 않는다고 하지만 Gitaly는 A가 P에 속한다고 함#

이 경우 Projects::GitDeduplicationService는 DisconnectGitAlternates RPC를 사용하여 리포지터리 A를 "재중복"하려고 시도합니다.

Git 오브젝트 중복 제거와 GitLab Geo#

Geo 프라이머리의 SQL에서 풀 리포지터리 레코드가 생성되면, 이는 결국 Geo 세컨더리에서 이벤트를 트리거합니다. 그러면 Geo 세컨더리는 Gitaly에 풀 리포지터리를 생성합니다. 각 풀 참여자가 동기화됨에 따라 Geo가 결국 세컨더리의 Gitaly에서 가비지 컬렉션을 트리거하고, 이 단계에서 Git 오브젝트가 중복 제거되므로 "결과적 일관성" 상황이 발생합니다.