리포지터리 크기 축소

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

시간이 지남에 따라 Git 리포지터리는 점점 커지게 됩니다. 대용량 파일이 Git 리포지터리에 추가되면 다음과 같은 문제점이 발생합니다:

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

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

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

리포지터리 크기 계산

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

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

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

  • refs/merge-requests/*
  • refs/pipelines/*
  • refs/environments/*
  • refs/keep-around/*
note
각 참조에 대한 자세한 내용은 GitLab-specific references를 참조하십시오.

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

caution
이 프로세스는 리포지터리에서 암호 또는 키와 같은 민감한 데이터를 제거하기에 적합하지 않습니다. 파일 내용을 포함한 커밋 정보는 데이터베이스에 캐시되어 있으며 이러한 정보는 리포지터리에서 제거된 후에도 여전히 보입니다.

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

  1. 지원되는 패키지 관리자 또는 소스에서 git filter-repo 및 옵션으로 git-sizer를 설치합니다.

  2. 프로젝트를 [내보내서(../settings/import_export.md#export-a-project-and-its-data) 최신 정보로 받아오고 다운로드합니다. 이 프로젝트 내보내기에는 리포지터리 참조를 제거할 수 있는 백업 복사본이 포함되어 있습니다.

  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로 변경합니다:

    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
      
    • --strip-blobs-bigger-than을 사용하여 10M보다 큰 모든 파일을 지웁니다:

      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)
    

    이 단계와 이후의 모든 단계(이전 단계 및 리포지터리 정리 단계 포함)는 git filter-repo 명령을 실행할 때마다 재실행해야 합니다.

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

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

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

    보호된 브랜치로 인해 이 작업이 실패합니다. 계속 진행하려면 브랜치 보호를 제거한 후 푸시하고 다시 브랜치를 보호해야 합니다.

  12. 태그된 릴리스에서 큰 파일을 제거하려면 변경 사항을 강제로 푸시하여 GitLab의 모든 태그를 업데이트합니다:

    git push origin --force 'refs/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 파일)을 생성할 수 있습니다.

리포지터리를 안전하게 정리하려면 작업하는 동안 읽기 전용으로 만들어야 합니다. 이는 자동으로 수행되지만 작성 중인 어떤 쓰기도 있으면 정리 요청을 제출하지 못하므로 계속 진행하기 전에 해당 git push 작업을 취소하십시오.

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

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

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

    commit-map 파일이 약 250 KB 이상 또는 3000행 이상인 경우 파일을 나누어 여러 부분으로 업로드할 수 있습니다:

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

이렇게 하면:

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

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

만약 리포지터리 크기가 줄어들지 않는다면, 이는 리포지터리가 30분 이내에 발생한 Git 작업에서 참조되었기 때문에 느슨한 객체가 유지되고 있기 때문일 수 있습니다. 리포지터리가 적어도 30분 동안 사용되지 않은 채로 대기한 후 이러한 단계를 다시 실행해보세요.

리포지터리 정리를 사용할 때 유의해야 할 점:

  • 프로젝트 통계는 캐시됩니다. 리포지터리 사용량이 감소하는 데 5-10분 정도 기다려야 할 수 있습니다.
  • 정리 작업은 30분 이전에 추가된 느슨한 객체를 제거합니다. 따라서 마지막 30분 동안 추가되거나 참조된 객체는 즉시 제거되지 않습니다. Gitaly 서버에 액세스할 수 있는 경우 이 지연을 건너뛰고 git gc --prune=now를 실행하여 모든 느슨한 객체를 즉시 제거할 수 있습니다.
  • 이 프로세스는 GitLab 캐시 및 데이터베이스에서 재작성된 커밋의 몇 가지 사본을 제거하지만 그 사본 중 일부는 계속해서 존재할 수 있습니다. 인스턴스 캐시 지우기는 일부 사본을 제거하는 데 도움이 될 수 있지만, 보안 목적으로 의존해서는 안 됩니다!

리포지터리 제한

리포지터리 크기 제한:

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

  • 프로젝트에 푸시할 수 없습니다.
  • 새로운 Merge Request을 생성할 수 없습니다.
  • 기존 Merge Request을 Merge할 수 없습니다.
  • LFS(Large File Storage) 객체를 업로드할 수 없습니다.

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

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

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

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

이러한 조치로 충분하지 않을 경우 다음을 수행할 수도 있습니다:

  • 일부 블롭을 LFS로 이동합니다.
  • 히스토리에서 일부 이전 의존성 업데이트를 삭제합니다.

불행히도, 이 작업 흐름은 잘 작동하지 않습니다. 커밋에서 파일을 삭제하는 것만으로는 리포지터리의 크기가 실제로 줄지 않습니다. 왜냐하면 이전 커밋과 블롭이 여전히 존재하기 때문입니다. 대신에 히스토리를 다시 작성해야 합니다. 오픈 소스 커뮤니티에서 유지되는 도구인 git filter-repo를 권장합니다.

note
git gc가 GitLab 측에서 실행될 때까지 “삭제된” 커밋과 블롭은 여전히 존재합니다. 또한 이미 최대 크기 제한을 초과한 경우에는 이미 다시 작성한 히스토리를 GitLab에 푸시할 수 있어야 합니다.

이러한 제한을 해제하려면 Self-Managed형 GitLab 인스턴스의 관리자가 초과된 특정 프로젝트의 제한을 늘려야 합니다. 따라서 제한 이하에 머물러 있는 것이 항상 좋습니다. 제한에 도달한 경우 일시적으로 늘릴 수 없다면 유일한 옵션은 다음과 같습니다:

  1. 로컬에서 모든 필요하지 않은 것을 정리합니다.
  2. GitLab에서 새 프로젝트를 만들고 대신 사용합니다.

문제 해결

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

표시된 크기나 커밋 번호가 내보낸 .tar.gz나 로컬 리포지터리와 다르면 GitLab 관리자에게 업데이트를 강제할 수 있습니다.

Rails 콘솔을 사용하여:

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"])

# 전체 아티팩트 저장 공간을 별도로 확인합니다
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} 바이트"

해제되지 않는 공간

이 페이지에서 정의한 프로세스는 리포지터리 내보내기의 크기를 줄일 수 있지만, 웹 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)