블로그 목록
테크 블로그 & 팁

Git을 어떤 규모에서도 확장하는 법 — Cursor의 Continuity 저장 구조

Git 서버 확장이 어려운 이유는 Git 자체 설계에 있다. Cursor는 S3 WAL을 진실의 원천으로 두는 Continuity로 이 문제를 다시 쓴다. 합의에 의존하지 않고도 일관성을 보장하고, 작은 저장소부터 거대 monorepo까지 복제본 수를 자유롭게 조절하는 구조를 소개한다.

2026년 8월 28일 7분 읽기

Git 서버 확장은 왜 늘 고통이었나

Git은 로컬에서는 단순하지만, 서버에서 대규모로 호스팅하면 구조적인 병목이 나타납니다. 저장소의 코드와 메타데이터는 packfile이라는 압축 바이너리에 저장되고, push와 fetch 역시 이 packfile 단위로 움직입니다. 이 형식은 로컬 디스크에서는 효율적이지만 서버 환경에서는 불리한 점이 많습니다. 객체가 DAG 순서대로 정렬되지 않고 크기 최소화 위주로 배치되기 때문에, packfile은 논리적 이동과 물리적 읽기 패턴이 일치하지 않습니다. 실제 Git 작업은 커밋 → 트리 → 블롭 → 부모 커밋 순으로 그래프를 따라가야 하는데, packfile 기반 서버에서는 이 과정이 네트워크 파일시스템과 잘 맞지 않아 성능이 급격히 떨어집니다.

GitHub는 이 문제를 해결하기 위해 파일시스템 분산부터 시작해 여러 시도를 했습니다. NFS, GFS, DRBD 같은 방식은 모두 packfile의 무작위 접근 패턴을 해결하지 못했고, 결국 전용 파일 서버에 RPC로 접근하는 구조로 전환했습니다. 그 다음이 Spokes입니다. Spokes는 Git 자체를 분산하지 않고 packfile 수준에서 복제하며, 모든 복제본이 NVMe 디스크에 일반 Git 저장소를 유지하고 3단계 커밋으로 동기화하는 구조입니다. 이 design은 13년간 업계 표준처럼 쓰였지만, 2026년의 사용 패턴에서는 한계가 분명해졌습니다. 이 배경은 Cursor의 공식 글 Git at Any Scale와 GeekNews의 해설 Git을 어떤 규모에서도 확장하는 법에서 확인할 수 있습니다.

Spokes의 구조와 3PC의 병목

Spokes가 오랫동안 작동한 데는 세 가지 선택이 있었습니다. 첫째, packfile 수준에서만 작동해 Git 클라이언트와의 호환성을 유지했습니다. 둘째, 실제 Git 저장소를 로컬 NVMe에 유지해 clone과 fetch 속도를 빠르게 유지했습니다. 셋째, 모든 복제본을 강하게 일관된 상태로 동기화했습니다.

하지만 이 합의 기반 구조는 확장성에서 균형이 깨졌습니다. 과거에는 저장소당 3개 복제본이 적절했지만, 오늘날 기업의 거대 monorepo는 CI 부하 때문에 더 많은 replica가 필요합니다. Spokes는 3PC를 사용하기 때문에 각 단계의 지연 시간이 클러스터 내 가장 느린 서버에 의해 결정됩니다. 복제본을 늘릴수록 push 처리량이 떨어지는 구조라, 큰 저장소에는 ceiling이 너무 낮습니다.

반대 방향 문제도 있습니다. AI 에이전트는 monorepo 밖에서 수많은 작은 임시 저장소를 만드는 경우가 많은데, Spokes는 이런 저장소에도 최소 3개 replica를 유지합니다. 대부분 유휴 상태인 작은 저장소에 replica 세 개는 과한 운영 부담이고, replica 수를 줄이면 정족수가 깨져 일관성이 깨질 수 있습니다. Spokes는 큰 저장소에는 너무 적고, 작은 저장소에는 너무 많은 구조입니다.

게다가 디스크 저장소가 진실의 원천이기 때문에 모든 replica의 위치, checksum, 건강 상태를 외부 라우팅 테이블로 추적해야 합니다. 손상된 사본은 정족수 문제를 일으키므로 복구가 늦으면 전체 시스템이 복잡해집니다.

Continuity: WAL을 진실의 원천으로 두는 설계

Cursor의 Continuity는 Spokes의 강점은 유지하면서 병목을 해결하는 다른 접근을 제시합니다. 핵심은 **S3 호환 객체 스토리지의 Write-Ahead Log(WAL)를 시스템의 유일한 진실의 원천(source of truth)**으로 두는 것입니다.

push가 들어오면 packfile을 디스크에 쓰는 동시에 S3에 WAL 항목으로 업로드합니다. 중요한 점은 WAL 항목이 완전히 영속되기 전까지 push 성공을 응답하지 않는다는 점입니다. 이후 로컬 Git 저장소에서 reference transaction을 준비하고, 별도의 WAL index 객체에 해당 entry를 추가해야 실제로 push가 보이게 됩니다. 이 index 갱신은 S3의 원자적 compare-and-swap으로 보호되어 충돌이 발생해도 손실되지 않습니다. 결과적으로 디스크 손상이나 network partition이 일어나도 WAL만 복구하면 일관성이 보존됩니다.

작은 저장소의 과도한 운영 비용 문제

Spokes는 큰 저장소일수록 불리하지만, 반대로 작은 저장소에도 부담이 큽니다. AI 에이전트가 생성하는 임시 저장소처럼 수명이 짧거나 트래픽이 거의 없는 경우에도 최소 3개 replica를 유지해야 하기 때문입니다. 이는 디스크와 운영 인력을 낭비합니다. Continuity는 replica 수를 저장소별로 조절할 수 있습니다. WAL만 있으면 복제가 가능하므로, 작은 저장소는 1개, 큰 저장소는 수십 개처럼 탄력적으로 운영할 수 있습니다. 이는 전통적인 합의 기반 호스팅에서는 어려웠던 방식입니다.

일관성 복구와 운영 복잡도의 변화

Spokes에서는 디스크 자체가 source of truth이므로, disk corruption는 정족수 깨짐으로 이어집니다. 관리자는 해당 저장소의 모든 replica를 교체해야 하며, 동기화 지연 동안 일부 클라이언트가 깨진 데이터를 읽을 가능성도 있었습니다.

Continuity에서는 WAL만 복구하면 디스크는 재생성 가능합니다. 읽기 경로는 여러 복제본에서 병렬로 이루어지기 때문에 한 replica가 느려도 전체 성능이 급락하지 않습니다. 대신에 WAL 압축, index 정합성 검증, S3 latency 예산 같은 새로운 운영 과제가 생기지만, 이는 디스크 기반 합의의 운영 부담과 성격이 다릅니다.

실제 적용에서 생각할 점

Continuity는 Git 저장소의 진실을 디스크에서 객체 저장소로 이동시킵니다. 이는 Git이 가지는 로컬 중심 가정과 대비되는 변화입니다. Git 클라이언트는 여전히 로컬 packfile과 ref transaction을 기반으로 작동하지만, 서버 측에서는 객체 저장소와 WAL을 중심으로 설계가 재구성됩니다. 기존 Git 호환성은 유지하면서 서버 확장성은 바뀌는 셈이죠.

Cursor가 내부적으로 이 구조를 어떤 범위까지 적용하는지, WAL 압축과 index 정책은 어떻게 운영하는지는 아직 공개된 문서가 적습니다. 다만 Spokes의 한계가 명확해진 시점에서, WAL을 source of truth로 두는 접근은 실용적인 대안으로 보입니다. Git 호스팅 설계를 고려한다면 확인해볼 만한 구조입니다.

#Git#Cursor#Continuity#Spokes#Git 호스팅#WAL
Robeedau

발행 전 운영자가 직접 큐레이션·검수·편집합니다.