왜 대용량 파일이 문제가 되는가
Git은 파일의 모든 버전을 히스토리에 그대로 저장한다. 100MB짜리 모델 가중치나 디자인 원본을 몇 번만 커밋해도 .git 디렉터리는 수 GB로 부풀고, 이 히스토리는 git clone 시 전부 전송된다. 모노레포에서는 프런트엔드, 백엔드, ML 팀이 같은 저장소를 공유하므로, ML 팀이 넣은 바이너리가 프런트엔드 개발자의 clone 시간까지 늘린다. 한 번 커밋된 대용량 파일은 이후 삭제해도 히스토리에 남아 용량이 줄지 않는다는 점이 특히 골칫거리다.
Git LFS의 동작 원리
Git LFS는 대용량 파일 자체를 별도 스토리지에 두고, Git에는 파일을 가리키는 작은 포인터 텍스트만 커밋한다. 포인터에는 SHA-256 해시와 크기만 담긴다. 실제 바이너리는 git checkout 시점에 해당 리비전에 필요한 것만 내려받으므로, 히스토리 전체 바이너리를 clone하지 않는다.
# 저장소에 실제로 커밋되는 포인터 파일 내용
version https://git-lfs.github.com/spec/v1
oid sha256:4d7a214614ab2935c943f9e0ff69d22eadbb8f32b1258daaa5e2ca24d17e2393
size 132650032
초기 설정
확장자나 경로 단위로 추적 규칙을 지정한다. 규칙은 .gitattributes에 기록되어 팀 전체가 공유한다.
# 클라이언트에 LFS 설치 (한 번만)
git lfs install
# 확장자 단위 추적
git lfs track "*.psd"
git lfs track "*.safetensors"
# 모노레포에서 특정 경로만 추적
git lfs track "ml/checkpoints/**"
# 규칙 파일을 반드시 커밋
git add .gitattributes
git commit -m "chore: track large binaries via LFS"
# 현재 추적 대상 확인
git lfs ls-files
CI/CD에서의 주의점
CI 러너 대부분은 기본적으로 LFS 객체를 받지 않는다. 실제 파일 대신 포인터 텍스트만 체크아웃되어, 빌드 스텝이 "손상된 파일"로 실패하는 사고가 흔하다. LFS를 명시적으로 켜야 한다.
# GitHub Actions
- uses: actions/checkout@v4
with:
lfs: true
# GitLab CI: 변수로 제어
variables:
GIT_LFS_SKIP_SMUDGE: "0"
ML 파이프라인처럼 특정 잡만 가중치가 필요하다면, 기본 체크아웃에서는 GIT_LFS_SKIP_SMUDGE=1로 포인터만 받고 해당 잡에서만 git lfs pull --include "ml/checkpoints/**"로 부분 fetch하면 러너 대역폭과 캐시 비용을 아낄 수 있다.
부분 체크아웃으로 비용 줄이기
모노레포에서 모두가 모든 바이너리를 받을 필요는 없다. include/exclude 필터로 필요한 경로만 내려받게 하면, 프런트엔드 개발자가 ML 체크포인트 수 GB를 받는 낭비를 막는다.
# 디자인 원본만 받고 ML 가중치는 제외
git config lfs.fetchinclude "design/**"
git config lfs.fetchexclude "ml/checkpoints/**"
# 이미 받은 오래된 객체 정리 (작업 트리 파일은 유지)
git lfs prune
대안과의 비교
| 방식 | 적합한 경우 | 한계 |
|---|---|---|
| Git LFS | Git 워크플로 유지, 버전당 소수 대용량 | 서버 스토리지·대역폭 비용, 별도 설정 필요 |
| DVC | 데이터셋·실험 버전 관리, S3 연동 | Git 밖 도구 학습 곡선 |
| 외부 오브젝트 스토리지 + 해시 참조 | 수십 GB 이상 산출물 | 일관성·정리를 직접 관리 |
운영 시 흔한 함정
먼저, 이미 일반 파일로 커밋된 바이너리는 track 규칙을 추가해도 자동 이전되지 않는다. 히스토리를 다시 써야 하며 이는 git lfs migrate import로 수행하지만 강제 푸시가 필요해 팀과 사전 조율이 필수다. 둘째, LFS 스토리지에는 대역폭 쿼터가 있는 호스팅이 많아, 대용량을 자주 갱신하면 한도에 걸릴 수 있다. 셋째, .gitattributes를 커밋하지 않으면 다른 개발자에게 규칙이 적용되지 않아 누군가는 실파일을, 누군가는 포인터를 커밋하는 혼선이 생긴다. 규칙 파일을 저장소 루트에 두고 코드 리뷰에서 함께 확인하는 습관이 사고를 줄인다.