You can run Git on object storage if you re-make packfiles
Git은 packfile을 다시 만들면 object storage에서도 실행할 수 있습니다
Git 저장소를 object storage에 직접 올리려면 로컬 파일시스템을 전제로 설계된 기존 packfile이 병목이 됩니다. Tigris의 objgit은 Range request에 맞춘 자체 packfile과 인덱스를 도입해 push 요청을 최대 약 300배, clone 요청을 최대 약 20배 줄였지만 아직 인증·권한·압축(compaction)이 구현되지 않은 실험 단계입니다.
- 주제
AI 요약
Git 저장소를 object storage 위에서 동작시키는 일은 겉보기보다 간단하지 않습니다. Git은 커밋, 트리, 파일 내용을 content-addressed object로 저장하고, 작은 저장소에서는 각 object를 파일로 둘 수 있지만 Linux kernel처럼 수백만 개의 object를 가진 저장소에서는 packfile로 묶습니다. Tigris Data의 글은 이 packfile이 로컬 파일시스템에서는 매우 효율적이지만, 네트워크 기반 object storage에 그대로 옮기면 지연시간과 접근 방식의 차이 때문에 성능이 급격히 나빠진다고 설명합니다.
■ Git object와 packfile이 필요한 이유
간단한 저장소에서 Git object는 .git/objects 아래에 zlib으로 압축된 파일로 저장됩니다. 그러나 Linux kernel 저장소에는 11,827,138개의 object가 하나의 packfile에 들어 있습니다. 글에서는 각 object를 개별적으로 object storage에서 가져온다고 가정하면, GetObject 호출 하나에 매우 낙관적으로 10ms만 잡아도 전부 가져오는 데 한 시간이 넘게 걸린다고 설명합니다. 실제 Git은 수많은 object를 하나의 압축된 packfile로 묶어 이 문제를 피합니다.
기존 packfile에는 object의 위치를 설명하는 인덱스가 함께 있습니다. 인덱스는 각 object가 packfile의 어디에 있고 얼마나 읽어야 하는지 알려주며, Git은 로컬 파일시스템에서 packfile을 mmap(m 리눅스 커널? no)하여 필요한 페이지를 커널이 읽도록 합니다. 이 방식은 파일시스템 캐시와 메모리 매핑을 활용하므로 로컬 디스크에서는 빠릅니다. 반면 object storage에서는 파일에 쓰자마자 다시 읽는 동작도 문제가 됩니다. 아직 PutObject가 끝나지 않은 object는 GetObject으로 읽을 수 없고, 로컬 파일 읽기의 수십 나노초 수준 접근과 네트워크 왕복의 최소 수 밀리초 사이에는 큰 차이가 있기 때문입니다.
■ HTTP Range request만으로는 부족했던 이유
packfile의 일부만 가져오기 위해 HTTP Range request를 사용하면 필요한 object만 내려받을 수 있습니다. 하지만 기존 인덱스에는 object의 압축 해제 후 크기와 위치에 관한 정보는 있어도, Range request를 구성하는 데 필요한 압축된 데이터의 길이를 바로 알 수 없는 경우가 있습니다. 따라서 인덱스만 순서대로 읽어서는 정확한 byte 범위를 계산하기 어렵습니다. 글에서는 이 문제가 object-storage용 자체 packfile 형식을 만든 핵심 이유 중 하나라고 설명합니다.
새 형식은 CD 이미지의 .bin 파일과 탐색 정보를 담는 .cue sheet에서 아이디어를 가져왔습니다. object는 최대 128MiB 크기의 .bin 파일에 차례로 저장하고, 어떤 object가 어느 위치에 있는지에 대한 메타데이터는 별도의 바이너리 인코딩 .cue sheet에 둡니다. 특히 각 object에 대해 압축된 크기와 압축 해제된 크기를 모두 offset과 함께 저장합니다. 그러면 object storage에 요청할 정확한 HTTP Range를 바로 구성할 수 있습니다. 이 구조는 Git 저장소를 object storage에 맞춘 columnar store처럼 다루게 합니다.
또한 새 형식은 zstd 같은 최신 압축 라이브러리를 사용할 수 있도록 설계하고, delta object를 원본 object 뒤에 붙이는 대신 독립적인 object로 저장합니다. 기존 방식에서는 어떤 object를 읽기 위해 원본과 그 뒤에 연결된 delta까지 읽어야 할 수 있지만, 독립적으로 저장하면 요청한 object만 읽을 수 있습니다. 글은 Git이 파일의 전체 버전과 버전 간 차이를 함께 저장하는 이유가 커밋 변경을 계산하는 비용을 줄이기 위해서라고 설명합니다.
■ 읽기 지연을 줄이는 방식
objgit은 packfile에서 object 하나를 요청받으면 object storage에서 전체 packfile을 임시 폴더로 내려받습니다. 아직 다운로드가 따라잡지 못한 packfile의 뒤쪽 데이터는 Range request로 필요한 부분을 가져옵니다. 이후 백그라운드 다운로드가 해당 위치까지 따라오면 로컬에서 읽습니다. 작성자는 이 방식이 Git 라이브러리의 동작 특성상 피하기 어려운 지연을 줄이면서, object가 필요할 때 packfile이 대체로 제때 준비되도록 한다고 설명합니다. 필요하다면 push나 pull을 시작할 때 해당 저장소의 최신 packfile을 미리 가져오는 방식도 고려하고 있습니다.
packfile 크기를 128MiB로 제한한 것은 형식 자체의 제약이 아니라 빠른 다운로드를 위한 운영상의 선택입니다. 큰 바이너리 파일이나 백업을 Git 저장소에 직접 넣으면 이보다 큰 packfile이 만들어질 수 있으며, 작성자는 이런 사용 사례를 Git Large File Storage로 해결할 계획이라고 밝힙니다. 현재는 대형 바이너리 파일을 다루는 workflow라면 objgit과 다른 아키텍처를 고려하라고 안내합니다.
■ 벤치마크 결과
성능 비교는 16코어 Mac에서 Wi-Fi를 사용해 진행했으며, Git 2.55.0, Go 1.26.5, 기존 build v1.0.2와 수정된 origin/main을 비교했습니다. 테스트 대상은 비교적 새로운 objgit 저장소, 10년 이상 유지한 실험용 monorepo인 x, 이미지와 압축이 어려운 object가 많은 Tigris blog 저장소입니다.
Push에서는 object storage 호출 수를 줄이는 효과가 크게 나타났습니다. objgit 저장소는 기존 8.7초와 231회의 S3 요청에서 새 형식 적용 후 2.2초와 18회 요청으로 줄었습니다. x 저장소는 3분 29.4초와 9,236회 요청에서 14.3초와 30회 요청으로 바뀌었습니다. Tigris blog 저장소도 2분 13.4초와 3,324회 요청에서 26.5초와 136회 요청으로 감소했습니다. 특히 x 저장소에서는 push 시간이 약 14.7분의 1 수준으로 줄었습니다.
Clone에서도 같은 방향의 개선이 나타났습니다. objgit은 11.8초에서 2.6초로, object storage 요청은 323회에서 17회로 줄었습니다. x 저장소는 3분 23.5초에서 54.4초로, 요청은 6,428회에서 17회로 감소했습니다. Tigris blog 저장소는 2분 23.6초에서 1분 22초로, 요청은 3,675회에서 158회로 줄었습니다. 다만 clone에서 전송된 데이터 양은 대체로 비슷했으므로, 개선의 중심은 데이터 압축률보다 작은 네트워크 왕복과 object storage 호출 수를 줄인 데 있습니다.
■ 아직 운영에 바로 사용할 수 없는 이유
작성자는 objgit을 자신의 프로젝트에 사용할 만큼 신뢰하지는 않는다고 명시합니다. 인증(authentication), 권한 부여(authorization), API, SigV4 인증, 적절한 rate limit이 아직 구현되지 않았습니다. 현재 상태로 인터넷에 노출하면 서버에 연결할 수 있는 누구나 원하는 내용을 pull하거나 push할 수 있으므로 그렇게 사용하지 말라고 경고합니다.
또한 작은 push가 반복될 때 packfile이 계속 쌓이며, 이를 더 큰 packfile로 주기적으로 합치는 compaction 설계는 아직 진행 중입니다. Git의 기존 packfile 형식은 실제 파일시스템이라는 제약에는 훌륭하지만, 네트워크 왕복이 들어가는 순간 그 전제가 무너진다는 것이 글의 결론입니다. objgit 저장소에는 이 .bin/.cue 컨테이너, columnar index, 그리고 글에서 설명한 네 단계 읽기 구조가 공개되어 있습니다.
■ Hacker News 반응
• @mmastrac — 좋습니다. durable object 기반의 Git 프로젝트를 몇 개 본 적이 있지만, 저는 단순한 object storage 방식을 훨씬 선호합니다.
• @mgrandl — 멋집니다. 이게 아직 구현되지 않았다는 사실이 저에게는 꽤 놀랍습니다. GitLab, Forgejo, Gitea 등이 모든 것을 object storage로 지원한다면 훨씬 좋을 텐데요. 현재는 항상 Git 저장소를 위해 파일시스템이 필요합니다.
• @throwaway7356 — 이미 구현된 적이 있습니다. 효율적이지 않아서 더는 그렇게 하지 않는 것입니다. 맞습니다. 그것은 “object storage”는 아니었지만, Git은 정적 파일을 제공하는 dumb HTTP 서버에서 서비스할 수 있습니다. 이제 누군가 object storage도 HTTP를 통해 정적 파일을 제공할 수 있다는 사실을 알아냈군요. 와우!
• @eru — Linux에서는 Fuse가 유용합니다. 예를 들어 https://github.com/matthiasgoergens/git-snap-fs 를 보세요.
• @baalimago — > 이게 아직 구현되지 않았다는 사실이 놀랍습니다 https://github.com/awslabs/git-remote-s3 이 방식은 매끄럽게 동작하므로 `git remote add origin s3://my-git-bucket/my-repo`를 실행할 수 있습니다.
원문: Tigris Data / 번역·요약: Trawling