Git에서 대용량 파일로 인해 발생하는 성능 문제 해결
Git은 텍스트 기반 코드의 변경 사항을 추적하는 데 탁월한 매우 강력한 분산 버전 관리 시스템입니다. 그러나 모든 복제본이 저장소 기록의 전체 복사본을 가져오는 분산된 특성으로 인해 이미지, 오디오, 비디오 또는 컴파일된 자산과 같은 대용량 바이너리 파일을 처리할 때 상당한 어려움이 따릅니다. 이러한 파일을 Git 기록에 직접 커밋하면 심각한 성능 병목 현상이 발생하여 복제, 가져오기(fetching), 푸시와 같은 일반적인 작업이 극도로 느려질 수 있습니다.
이 문서에서는 Git에서 대용량 파일로 인해 발생하는 성능 문제의 근본 원인을 깊이 있게 다룹니다. 우리는 이러한 문제가 애초에 발생하지 않도록 Git Large File Storage (LFS)를 활용하는 사전 예방적 전략을 탐색하고, 저장소 기록에 이미 존재하는 대용량 파일 팽창을 해결하는 방법에 대한 명확하고 실행 가능한 가이드를 제공할 것입니다. 이 문서를 마치면 콘텐츠에 관계없이 Git 저장소를 효율적으로 관리할 수 있는 지식과 도구를 갖추게 될 것입니다.
Git에서 대용량 파일의 문제점
Git의 설계 철학은 소스 코드의 효율성에 중점을 둡니다. 파일 콘텐츠를 "블롭(blobs)"으로 저장하고 버전 간의 변경 사항을 스냅샷으로 추적하며, 정교한 델타 압축을 사용하여 텍스트 파일의 저장소 크기를 관리하기 쉽게 유지합니다. 그러나 이러한 접근 방식은 대용량 바이너리 파일에는 적합하지 않습니다.
- 불량한 압축: 바이너리 파일은 변경 사항을 쉽게 비교(diff)할 수 없기 때문에 Git의 델타 압축 알고리즘을 사용해도 잘 압축되지 않는 경우가 많습니다. 대용량 바이너리 파일에 작은 변경 사항이 있어도 Git은 완전히 새로운 대용량 블롭을 저장하게 될 수 있습니다.
- 저장소 팽창: 저장소 기록에 커밋된 대용량 바이너리 파일의 모든 버전은 전체 크기에 상당한 기여를 합니다. Git은 분산되어 있으므로, 복제하거나 업데이트를 가져오는 모든 협업자는 모든 기록을 다운로드합니다.
- 느린 작업: 대용량 저장소는 Git 작업 속도를 직접적으로 저하시킵니다.
git clone: 엄청난 대역폭과 디스크 공간을 소비하며 매우 오랜 시간이 걸릴 수 있습니다.git fetch/git pull: 업데이트를 가져오는 속도가 느려집니다.git push: 대용량 파일이 포함된 새 커밋을 전송하는 속도가 느립니다.git checkout: Git이 파일 시스템을 재구성하므로 브랜치를 전환하거나 이전 버전으로 복원하는 속도가 느릴 수 있습니다.
궁극적으로 이는 그래픽 자산, 게임 개발 파일 또는 대용량 데이터 세트를 다루는 팀 간에 좌절감, 생산성 저하 및 효과적인 버전 관리 관행을 저해하는 결과를 초래합니다.
대용량 파일 문제 방지: Git LFS 구현
대용량 파일 문제를 방지하는 가장 효과적인 방법은 처음부터 Git Large File Storage (LFS)를 구현하는 것입니다. Git LFS는 Git용 오픈소스 확장 기능으로, 저장소의 대용량 파일을 작은 포인터 파일로 대체하고 실제 파일 콘텐츠는 원격 LFS 서버(GitHub, GitLab, Bitbucket과 같은 플랫폼의 Git 저장소와 함께 호스팅될 수 있음)에 저장됩니다.
Git LFS 작동 방식
Git LFS로 파일 형식을 추적하면:
- 커밋: 실제 대용량 파일 대신, Git은 저장소에 작은 포인터 파일을 커밋합니다. 이 포인터 파일에는 대용량 파일의 OID (콘텐츠의 SHA-256 해시를 기반으로 한 고유 식별자) 및 크기와 같은 정보가 포함됩니다.
- 푸시:
git push를 실행하면 실제 대용량 파일 콘텐츠는 LFS 서버에 업로드되고, 포인터 파일은 표준 Git 원격으로 푸시됩니다. - 클론/가져오기:
git clone또는git fetch를 실행하면 Git은 포인터 파일을 다운로드합니다. 그러면 Git LFS가 이 포인터를 가로채서 LFS 서버에서 실제 대용량 파일을 작업 디렉토리로 다운로드합니다.
이 메커니즘은 작은 포인터 파일만 포함하므로 주 Git 저장소를 가볍고 빠르게 유지합니다.
Git LFS 설정
Git LFS 설정은 간단합니다:
1. Git LFS 설치
먼저 Git LFS 명령줄 확장을 설치해야 합니다. Git LFS 공식 웹사이트에서 다운로드하거나 패키지 관리자를 사용할 수 있습니다.
# macOS에서 Homebrew 사용
brew install git-lfs
# Debian/Ubuntu에서
sudo apt-get install git-lfs
# Fedora에서
sudo dnf install git-lfs
# Windows에서 (Chocolatey)
choco install git-lfs
설치 후, LFS를 초기화하기 위해 사용자 계정당 한 번 다음 명령어를 실행합니다:
git lfs install
이 명령어는 LFS 파일을 자동으로 처리하는 데 필요한 Git 훅(hooks)을 추가합니다.
2. Git LFS로 파일 추적
이제 Git LFS에 어떤 파일 형식 또는 특정 파일을 관리해야 하는지 알려줍니다. git lfs track을 사용하고 패턴을 .gitattributes 파일에 추가하여 이를 수행합니다.
예를 들어, 모든 PSD 파일과 MP4 비디오를 추적하려면:
git lfs track "*.psd"
git lfs track "*.mp4"
이 명령어들은 저장소에 .gitattributes 파일을 수정하거나 생성하며, 그 내용은 다음과 유사할 것입니다:
*.psd filter=lfs diff=lfs merge=lfs -text
*.mp4 filter=lfs diff=lfs merge=lfs -text
중요: .gitattributes 파일을 저장소에 커밋합니다. 이렇게 하면 모든 협업자가 동일한 LFS 추적 규칙을 사용하게 됩니다.
git add .gitattributes
git commit -m "Configure Git LFS for PSD and MP4 files"
3. LFS 추적 파일 커밋 및 푸시
git lfs track이 구성되고 커밋되면, 패턴과 일치하는 모든 새 파일(또는 수정하는 기존 파일)은 커밋 및 푸시할 때 자동으로 LFS에 의해 처리됩니다. 워크플로는 거의 동일하게 유지됩니다:
git add my_design.psd
git commit -m "Add new design file (tracked by LFS)"
git push origin main
푸시할 때 Git은 포인터 파일을 Git 원격으로 업로드하고, Git LFS는 실제 my_design.psd를 LFS 서버에 업로드하는 것을 처리할 것입니다.
Git LFS 모범 사례
- 조기에 추적: 대용량 파일이 Git에 직접 커밋되기 전에 LFS를 구성하는 것이 가장 좋습니다. 이렇게 하면 나중에 기록을 다시 작성할 필요가 없습니다.
- 패턴을 구체적으로:
*.png또는*.jpg가 흔하지만, 모든 이미지 파일에 LFS가 필요한지 고려하십시오. 때로는 작은 이미지는 Git에 그대로 두어도 괜찮고, 더 큰 이미지에만 LFS 추적을 적용하는 것이 좋습니다. - 추적 확인:
git lfs ls-files를 사용하여 작업 디렉토리에서 현재 LFS가 추적하는 파일을 확인하십시오. - 팀 교육: 모든 팀원이 LFS 작동 방식과 올바른 설치 및 구성 방법을 이해하도록 하십시오.
- 스토리지 제한 고려: LFS 스토리지는 일반적으로 호스팅 플랫폼에서 비용이 발생합니다. 사용량을 모니터링하십시오.
기존 대용량 파일 문제 해결 (기록 재작성)
대용량 파일이 이미 Git 기록에 존재하는 경우, 단순히 Git LFS를 활성화한다고 해서 저장소의 과거 크기가 줄어들지는 않습니다. 기록상의 팽창을 정리하려면 저장소 기록을 다시 작성하여 실제 대용량 파일을 LFS 포인터로 대체해야 합니다. 이것은 강력하지만 잠재적으로 파괴적인 작업이므로 주의해서 진행하십시오.
경고: 기록 재작성은 커밋 SHA를 변경하여 협업자에게 상당한 혼란을 야기할 수 있습니다. 항상 진행하기 전에 저장소를 백업하고 팀원들에게 명확하게 소통하십시오.
git lfs migrate를 사용하여 기존 파일 변환
git lfs migrate 명령어는 이 목적을 위해 특별히 설계되었습니다. 이 명령어는 저장소의 기록을 분석하고, 대용량 파일을 식별하며, 이를 LFS 포인터로 대체한 다음, 그에 따라 기록을 다시 작성할 수 있습니다.
1. 후보 파일 식별
마이그레이션하기 전에 어떤 파일이 저장소 크기에 가장 큰 영향을 미치는지 식별하는 것이 도움이 됩니다. git lfs migrate info는 이를 위한 훌륭한 도구입니다:
git lfs migrate info
# 또는 특정 크기 이상의 파일을 보려면
git lfs migrate info --everything --above=10MB
이 명령어는 크기별로 가장 큰 파일과 기록에서 차지하는 총 공간을 나열하여 마이그레이션에 포함할 패턴을 결정하는 데 도움을 줍니다.
2. 마이그레이션 수행
git lfs migrate import를 사용하여 기록을 다시 작성하고 지정된 파일을 LFS로 변환하십시오. 이 명령어는 필요한 .gitattributes 항목을 생성하고 기록상의 블롭을 변환합니다.
# 예시: 전체 기록에서 모든 .psd 및 .mp4 파일 마이그레이션
git lfs migrate import --include="*.psd,*.mp4"
# 특정 크기(예: 5MB) 이상의 파일만 마이그레이션하려면
git lfs migrate import --above=5MB
# 특정 날짜 이후에 추가된 파일 마이그레이션 (최근 팽창에 유용)
git lfs migrate import --include="*.zip" --since="2023-01-01"
플래그 설명:
* --include: 마이그레이션할 파일 패턴을 지정합니다 (쉼표로 구분).
* --above: 지정된 크기(예: 10MB, 500KB)보다 큰 모든 파일을 마이그레이션합니다.
* --since/--everything: 스캔할 기록 범위를 제어합니다. 전체 기록을 정리하려면 --everything이 일반적으로 안전합니다. --since는 범위를 제한할 수 있습니다.
이 명령어를 실행하면 로컬 저장소의 기록이 다시 작성되고 .gitattributes 파일이 업데이트됩니다.
3. 마이그레이션 확인
마이그레이션 후, 파일이 LFS에 의해 추적되는지, 그리고 저장소 크기가 줄어들었는지 확인하십시오:
# .gitattributes 파일 확인
cat .gitattributes
# 로컬 저장소 크기 확인 (예: Linux/macOS에서 'du -sh .git' 사용)
du -sh .git
# 선택적으로, 작업 디렉토리의 특정 대용량 파일을 검사합니다.
# 'git lfs ls-files'는 해당 파일이 LFS 파일임을 보여주어야 합니다.
4. 원격으로 강제 푸시
기록을 다시 작성했으므로, 일반적인 git push는 거부될 것입니다. 원격 저장소를 업데이트하려면 강제 푸시(force push)를 수행해야 합니다. 이 부분이 팀과의 소통이 매우 중요한 지점입니다.
git push --force origin main # 또는 메인 브랜치 이름
# 정리가 필요한 여러 브랜치가 있다면, 해당 브랜치들도 강제 푸시해야 합니다.
# 더 안전한 강제 푸시를 위해 force-with-lease를 고려하십시오.
git push --force-with-lease origin main
경고: 강제 푸시는 원격 기록을 덮어씁니다. 모든 협업자가 강제 푸시하기 전에 최신 변경 사항을 가져왔는지 확인하거나, 더 좋은 방법은 그들이 이를 인지하고 새 기록을 기반으로 작업을 리베이스할 수 있도록 하는 것입니다. 일반적으로 유지 보수 시간 동안 또는 아무도 저장소에서 활발하게 작업하지 않을 때 이 작업을 수행하는 것이 가장 좋습니다.
5. 오래된 참조 정리 (선택 사항이지만 권장)
강제 푸시 후에도 오래된 대용량 객체는 일정 기간 동안 원격 서버에 남아 있을 수 있습니다 (종종 "reflog" 또는 "old objects" 저장소에). 공간을 완전히 회수하려면 서버 측에서 git gc를 실행하거나 Git 호스팅 제공업체에 특정 정리 프로세스가 있을 수 있습니다.
로컬에서는 도달할 수 없는 오래된 객체를 정리할 수 있습니다:
git reflog expire --expire=now --all
git gc --prune=now
팁 및 경고
- 먼저 백업: 모든 기록 재작업을 수행하기 전에 항상 저장소의 전체 백업을 만드십시오 (예:
git clone --mirror). - 팀과 소통: 기록 재작업은 모든 사람에게 영향을 미칩니다. 사전에 팀과 조율하고 로컬 복제본을 업데이트하기 위한 명확한 지침을 제공하십시오 (그들은 아마도 다시 복제하거나 특정 리베이스/리셋 작업을 수행해야 할 것입니다).
- 철저한 테스트: 가능하다면, 영향을 이해하기 위해 먼저 테스트 저장소에서 마이그레이션을 수행하십시오.
filter-repo대안: 더 복잡한 기록 재작성 시나리오 (예: 기록에서 파일을 완전히 제거하고 LFS로 변환하는 것이 아님)의 경우,git filter-repo는 더 이상 사용되지 않는git filter-branch또는 BFG Repo-Cleaner의 현대적이고 빠르며 유연한 대안입니다. 그러나 LFS 변환의 경우git lfs migrate import가 일반적으로 더 간단하고 목적에 맞게 제작되었습니다.- 저장소 크기 모니터링: 새로운 문제가 발생하면 조기에 파악하기 위해 주기적으로 저장소 크기 및 LFS 사용량을 확인하십시오.
결론
대용량 바이너리 파일은 Git 저장소의 상당한 성능 저하를 초래하여 작업 속도를 늦추고 개발자에게 좌절감을 줄 수 있습니다. 새 파일에 Git LFS를 사전에 구현하고 git lfs migrate import를 활용하여 기록상의 팽창을 해결함으로써, 가볍고 효율적이며 성능이 뛰어난 버전 관리 시스템을 유지할 수 있습니다. 핵심 단계를 기억하십시오: Git LFS를 설치하고, 대용량 파일을 추적하며, 필요한 경우 git lfs migrate로 기록을 신중하게 다시 작성하고, 항상 팀과의 소통과 백업을 우선시하십시오. 잘 관리된 Git 저장소는 모든 관련자에게 더 원활한 협업과 생산적인 개발 워크플로를 보장합니다.