GitLab에서 Git 객체 중복 제거 작동 방식

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

풀 저장소

Git alternates 이해

Git 레벨에서 중복을 제거하기 위해 Git alternates를 사용합니다. Git alternates는 동일한 컴퓨터의 다른 저장소에서 저장소가 객체를 빌릴 수 있는 메커니즘입니다.

저장소 A에서 저장소 B로부터 빌릴 수 있도록 하려면:

  1. 특별한 파일 A.git/objects/info/alternatesB.git/objects로 해결되는 경로를 기록합니다.
  2. 저장소 A에서 git repack을 실행하여 저장소 A에도 있는 모든 객체를 제거합니다.

이후 repack 이후, 저장소 A는 더는 자체 포함된 상태가 아니지만 여전히 자체 참조와 구성을 포함합니다. 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에서 레일즈 애플리케이션 수준에 있습니다.

결론적으로, 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 alternates 메커니즘은 여러 저장소 간의 직접 디스크 접근에 의존하며, 우리는 직접적인 디스크 접근이 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에 따르면 존재함), 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 객체가 중복 처리됩니다.