리포지터리 크기 축소

Tier: Free, Premium, Ultimate Offering: GitLab.com, Self-managed, GitLab Dedicated

시간이 지남에 따라 Git 리포지터리는 점점 커지게 됩니다. Git 리포지터리에 큰 파일이 추가되면:

  • 모든 사람이 파일을 다운로드해야 하기 때문에 리포지터리를 가져오는 데 시간이 더 걸립니다.
  • 서버의 저장 공간을 크게 차지합니다.
  • Git 리포지터리 저장 공간 제한이 도달할 수 있습니다.

리포지터리를 재작성하면 원하지 않는 이력을 제거하여 리포지터리를 더 작게 만들 수 있습니다. 우리는 git filter-repogit filter-branchBFG보다 더 추천합니다.

caution
리포지터리 이력을 재작성하는 것은 파괴적인 작업입니다. 시작하기 전에 리포지터리를 백업해야 합니다. 리포지터리를 백업하는 가장 좋은 방법은 프로젝트를 내보내기하는 것입니다.

리포지터리 크기 계산

리포지터리의 크기는 리포지터리에 있는 모든 파일의 누적 크기를 계산하여 결정됩니다. 이는 리포지터리의 해시된 저장 경로에서 du --summarize --bytes 명령을 실행하는 것과 유사합니다.

리포지터리 이력에서 파일 제거

GitLab은 정기적인 작업의 일환으로 도달할 수 없는 객체를 제거(prune)합니다. GitLab에서 매뉴얼으로 리포지터리 디스크 크기를 줄이려면 먼저 GitLab이 만든 브랜치, 태그 다른 내부 참조(refs)에서 큰 파일에 대한 참조를 제거해야 합니다. 이러한 참조(refs)에는 다음이 포함됩니다:

  • refs/merge-requests/*
  • refs/pipelines/*
  • refs/environments/*
  • refs/keep-around/*
note
이러한 각 참조에 대한 자세한 내용은 GitLab 특정 참조를 참조하세요.

이러한 참조(refs)는 자동으로 다운로드되지 않으며 숨겨진 참조는 광고되지 않지만, 프로젝트 내보내기를 사용하여 이러한 참조를 제거할 수 있습니다.

caution
이 프로세스는 리포지터리에서 비밀 데이터(예: 비밀번호 또는 키)를 제거하는 데 적합하지 않습니다. 파일 내용을 포함한 커밋 정보는 데이터베이스에 캐시되어 있고 리포지터리에서 제거된 후에도 여전히 표시됩니다.

GitLab 리포지터리에서 파일을 제거하려면:

  1. 지원되는 패키지 관리자나 소스에서 git filter-repo 및 선택적으로 git-sizer를 설치하세요.

  2. 프로젝트로부터 새로운 내보내기를 생성하고 다운로드하세요. 이 프로젝트 내보내기에는 리포지터리 참조를 정리할 수 있는 백업 복사본이 포함되어 있습니다.

  3. tar를 사용하여 백업을 압축 해제하세요:

    tar xzf project-backup.tar.gz
    

    이것에는 git bundle에 의해 생성된 project.bundle 파일이 포함되어 있습니다.

  4. --bare--mirror 옵션을 사용하여 번들에서 리포지터리의 새로운 복사본을 클론하세요:

    git clone --bare --mirror /path/to/project.bundle
    
  5. project.git 디렉터리로 이동하세요:

    cd project.git
    
  6. 번들 파일에서 클론하는 것은 origin 원격을 로컬 번들 파일의 URL로 설정하므로, 이를 리포지터리의 URL로 변경하세요:

    git remote set-url origin https://gitlab.example.com/<namespace>/<project_name>.git
    
  7. git filter-repo 또는 git-sizer를 사용하여 리포지터리를 분석하고 결과를 검토하여 제거할 항목을 결정하세요:

    # git filter-repo 사용
    git filter-repo --analyze
    head filter-repo/analysis/*-{all,deleted}-sizes.txt
       
    # git-sizer 사용
    git-sizer
    
  8. 관련된 git filter-repo 옵션을 사용하여 리포지터리의 이력을 정리하세요. 두 가지 일반적인 옵션은 다음과 같습니다:

    • 특정 파일을 제거하려면 --path--invert-paths를 사용하세요:

      git filter-repo --path path/to/file.ext --invert-paths
      
    • 예를 들어 10M보다 큰 모든 파일을 제거하려면 --strip-blobs-bigger-than을 사용하세요:

      git filter-repo --strip-blobs-bigger-than 10M
      

    더 많은 예제 및 완전한 설명서는 git filter-repo 설명서를 참조하세요.

  9. 내부 참조를 제거하려면 각 실행별로 생성된 commit-map 파일이 필요합니다. 각 git filter-repo 실행은 새로운 commit-map을 생성하며 이전 실행에서 생성된 commit-map을 덮어씁니다. 각 commit-map 파일을 백업하려면 다음 명령을 사용하세요:

    cp filter-repo/commit-map ./_filter_repo_commit_map_$(date +%s)
    

    이 단계와 리포지터리 정리 단계를 실행할 때마다 이후 단계를 (Protected branches](../protected_branches.md)를 포함한 모든 내용을 반복해야 합니다.

  10. 변경 사항을 강제로 푸시하려면 미러 플래그를 해제해야 합니다:

     git config --unset remote.origin.mirror
    
  11. GitLab의 모든 브랜치를 덮어쓰기 위해 변경 사항을 강제로 푸시하세요:

    git push origin --force 'refs/heads/*'
    

    Protected branches로 인해 실패합니다. 진행하려면 브랜치 보호를 제거하고 푸시한 다음 다시 브랜치 보호를 활성화해야 합니다.

  12. 태그된 릴리스에서 큰 파일을 제거하려면 GitLab의 모든 태그에 변경 사항을 강제로 푸시하세요:

    git push origin --force 'refs/tags/*'
    

    Protected tags로 인해 실패합니다. 계속하려면 태그 보호를 제거하고 푸시한 다음 다시 태그 보호를 활성화해야 합니다.

  13. 더 이상 존재하지 않는 커밋에 대한 데드 링크를 방지하려면 git filter-repo에 의해 생성된 refs/replace를 푸시하세요.

    git push origin --force 'refs/replace/*'
    

    작동 방식에 대한 정보는 Git replace 설명서를 참조하세요.

  14. 다음 단계를 시도하기 전에 최소 30분을 기다리세요.
  15. 리포지터리 정리를 실행하세요. 이 프로세스는 30분 이상 된 객체만 정리합니다. 자세한 내용은 Space not being freed를 참조하세요.

리포지터리 정리

리포지터리 정리를 통해 GitLab은 해당 객체에 대한 내부 Git 참조를 제거합니다. 이 작업은 git filter-repo를 사용하여 만든 객체 디렉터리( commit-map 파일)을 사용할 수 있게 합니다.

도입됨 in GitLab 13.6, 리포지터리를 안전하게 정리하려면 작업 중에 해당 리포지터리를 읽기 전용으로 만들어야 합니다. 이것은 자동으로 발생하지만, 진행 중인 쓰기 작업이 있으면 정리 요청을 제출할 수 없으므로 계속하기 전에 진행 중인 git push 작업을 취소하세요.

caution
내부 Git 참조를 제거하면 관련 Merge Request 커밋, 파이프라인 및 변경 내용이 더 이상 사용할 수 없게 됩니다.

리포지터리를 정리하려면:

  1. 왼쪽 사이드바에서 검색 또는 이동을 선택하고 프로젝트를 찾습니다.
  2. 설정 > 리포지터리로 이동합니다.
  3. 객체 디렉터리을 업로드합니다. 예를 들어, git filter-repo에 의해 생성된 commit-map 파일을 업로드합니다. 이 파일은 filter-repo 디렉터리에 있습니다.

    만약 commit-map 파일이 약 250KB 또는 3000줄보다 크다면, 파일을 나누어 조각별로 업로드할 수 있습니다:

    split -l 3000 filter-repo/commit-map filter-repo/commit-map-
    
  4. 정리 시작을 선택합니다.

이렇게 하면:

  • 이전 커밋에 대한 내부 Git 참조가 제거됩니다.
  • 리포지터리에 대해 git gc --prune=30.minutes.ago가 실행되어 참조되지 않는 객체가 제거됩니다. 리포지터리를 재패킹하면 일시적으로 리포지터리 크기가 크게 증가합니다. 이는 이전 팩 파일이 새로운 팩 파일이 생성될 때까지 제거되지 않기 때문입니다.
  • 프로젝트에 연결된 사용되지 않는 LFS 객체가 연결 해제되어 저장 공간이 확보됩니다.
  • 디스크의 리포지터리 크기를 다시 계산합니다.

GitLab은 정리가 완료되면 리포지터리 크기가 재계산된 이메일 알림을 보냅니다.

리포지터리 크기가 줄어들지 않으면, 이는 지난 30분 동안 Git 작업에서 참조되어 있는 미사용 객체 때문일 수 있습니다. 리포지터리가 적어도 30분 동안 대기한 후 이러한 단계를 다시 실행해 보세요.

Repository Cleanup를 사용할 때 유의해야 할 사항:

  • 프로젝트 통계는 캐시됩니다. 저장 공간 이용률을 줄이려면 5-10분을 기다려야 할 수 있습니다.
  • 정리는 30분보다 오래된 미사용 객체를 제거합니다. 이는 지난 30분 동안 추가되거나 참조된 객체가 즉시 제거되지 않음을 의미합니다. Gitaly 서버에 액세스할 수 있다면 이 지연을 건너뛰고 즉시 git gc --prune=now를 실행하여 모든 미사용 객체를 제거할 수 있습니다.
  • 이 과정은 GitLab 캐시와 데이터베이스에서 재작성된 커밋의 일부 사본을 제거하지만, 여전히 커버리지에 많은 틈이 있고 일부 사본이 무기한으로 유지될 수 있습니다. 인스턴스 캐시를 지우는 것은 일부 사본을 제거하는 데 도움이 될 수 있지만, 보안 목적으로 의존해서는 안 됩니다!

리포지터리 용량 제한

리포지터리 크기 제한:

프로젝트가 크기 제한에 도달하면 다음을 할 수 없습니다:

  • 프로젝트에 푸시하지 못합니다.
  • 새로운 Merge Request을 만들지 못합니다.
  • 기존 Merge Request을 Merge하지 못합니다.
  • LFS 객체를 업로드하지 못합니다.

그럼에도 불구하고 다음을 할 수 있습니다:

  • 새 이슈를 만들 수 있습니다.
  • 프로젝트를 복제할 수 있습니다.

리포지터리 크기 제한을 초과한 경우, 다음을 수행할 수 있습니다:

  1. 일부 데이터를 제거합니다.
  2. 새 커밋을 만듭니다.
  3. 리포지터리에 다시 푸시합니다.

이러한 조치가 부족한 경우 다음을 수행할 수 있습니다:

  • 일부 Blob을 LFS로 이동시킵니다.
  • 역사에서 일부 오래된 의존성 업데이트를 제거합니다.

유감스럽게도, 이 워크플로는 작동하지 않습니다. 커밋에서 파일을 삭제하면 이전 커밋과 Blob이 여전히 존재하기 때문에 리포지터리 크기가 실제로 줄지 않습니다. 대신에 역사를 다시 작성해야 합니다. 우리는 오픈 소스 커뮤니티 지원 도구인 git filter-repo를 권장합니다.

note
git gc가 GitLab 측에서 실행되기 전까지 “제거된” 커밋과 Blob은 여전히 존재합니다. 또한 이미 최대 크기 제한을 초과했다면 재작성된 역사를 GitLab에 푸시해야 합니다.

이러한 제한을 해제하려면, Self-managed GitLab 인스턴스의 관리자가 초과한 특정 프로젝트의 제한을 높여야 합니다. 따라서 제한을 항상 미리 초과하지 않도록 주의하는 것이 좋습니다. 제한을 초과하면 임시로 높일 수 없는 경우 아래와 같이 할 수 있습니다:

  1. 로컬에서 필요 없는 것을 모두 정리합니다.
  2. GitLab에서 새 프로젝트를 만들고 대신 사용합니다.

문제 해결

GUI에 표시된 리포지터리 통계가 부정확한 경우

표시된 크기나 커밋 번호가 내보낸 .tar.gz 또는 로컬 리포지터리와 다른 경우, GitLab 관리자에게 업데이트를 강제할 것을 요청할 수 있습니다.

레일 콘솔을 사용하여:

p = Project.find_by_full_path('<namespace>/<project>')
pp p.statistics
p.statistics.refresh!
pp p.statistics
# 이전 값과 비교

# 프로젝트 통계를 지우는 다른 방법
p.repository.expire_all_method_caches
UpdateProjectStatisticsWorker.perform_async(p.id, ["commit_count","repository_size","storage_size","lfs_objects_size"])

# 별도로 총 artifact 저장 공간을 확인
builds_with_artifacts = p.builds.with_downloadable_artifacts.all

artifact_storage = 0
builds_with_artifacts.find_each do |build|
  artifact_storage += build.artifacts_size
end

puts "#{artifact_storage} bytes"

공간이 해제되지 않음

이 페이지에서 정의된 과정은 리포지터리 내보내기의 크기를 줄일 수 있지만, 웹 UI와 터미널 모두에 나타나는 파일 시스템의 사용량이 변하지 않습니다.

이 과정은 리포지터리에 여전히 도달할 수 없는 많은 객체를 남겨두게 합니다. 도달할 수 없기 때문에 내보내기에 포함되지 않지만, 파일 시스템에 여전히 저장됩니다. 이러한 파일은 2주의 유예 기간 이후에 가지를 제거합니다. 가지를 잘라내어 이러한 파일을 삭제하고 저장 공간 사용 통계가 정확하도록 보장합니다.

이 과정을 빠르게 수행하려면 도달할 수 없는 객체 잘라내기 작업을 참조하세요.

Sidekiq 프로세스가 프로젝트를 내보내지 못하는 경우

Tier: Free, Premium, Ultimate Offering: Self-managed, GitLab Dedicated

가끔 Sidekiq 프로세스가 프로젝트를 내보내지 못할 수 있습니다. 예를 들어, 실행 중에 종료된 경우입니다.

GitLab.com 사용자는 지원팀에 문의하여 이 문제를 해결할 수 있습니다.

Self-managed 사용자는 Rails 콘솔을 사용하여 Sidekiq 프로세스를 우회하고 프로젝트 내보내기를 매뉴얼으로 시작할 수 있습니다:

project = Project.find(1)
current_user = User.find_by(username: '내 사용자 이름')
RequestStore.begin!
ActiveRecord::Base.logger = Logger.new(STDOUT)
params = {}

::Projects::ImportExport::ExportService.new(project, current_user, params).execute(nil)

이렇게 하면 UI를 통해 내보낸 내용을 확인할 수 있지만, 사용자에게 이메일을 보내지는 않습니다. 프로젝트 내보내기와 사용자에게 이메일을 매뉴얼으로 보내려면:

project = Project.find(1)
current_user = User.find_by(username: '내 사용자 이름')
RequestStore.begin!
ActiveRecord::Base.logger = Logger.new(STDOUT)
params = {}

ProjectExportWorker.new.perform(current_user.id, project.id)