GitHub이 저장소 전체 잠금을 참조 단위로 쪼개 Azure 이전을 끝낸다

Hacker News10일 전

GitHub이 Git 저장소의 쓰기 일관성 범위를 저장소 전체에서 개별 참조(ref) 단위로 좁히는 작업을 진행하고 있다. 저장소 복제본 일부가 Azure로 넘어가면서 생긴 왕복 지연을 상쇄하고, 그동안 데이터센터에 남아 있던 가장 바쁜 저장소들까지 클라우드로 끌어들이기 위한 중간 단계다. GitHub는 이 작업을 차세대 스토리지 아키텍처로 건너가는 다리라고 설명한다.

규모부터 짚어보자. 지난 1년 사이 Git 트래픽이 급증하면서 GitHub 저장소의 99.99999%가 자체 데이터센터와 Azure 두 곳에 동시에 존재하게 됐다. 첫 복제본이 Azure 센트럴 US 리전에 도착한 것은 2025년 8월이다. 현재 대부분의 저장소는 5개 복제본 가운데 2개를 Azure에 두고 있고, Git 트래픽의 절반 정도를 Azure가 처리한다. 문제는 나머지 0.00001%였다. 플랫폼에서 가장 바쁜 이 저장소들은 데이터센터와 Azure 사이 약 1,000마일 거리에서 발생하는 왕복 25ms 지연 하나에 발목이 잡혀 있었다.

이전 시점을 앞당긴 배경에는 에이전트가 있다. 에이전트가 만들어내는 트래픽이 빠르게 늘어나는 와중에 데이터센터와 주변 지역의 전력 용량은 한계에 부딪혔고, 물리적 공급망 제약까지 겹쳤다. 가능한 모든 확장을 동원해도 안전하게 옮길 시간은 확보되지만, 느긋하게 옮길 여유는 없다는 판단이었다. 저장소 데이터는 GitHub의 거의 모든 기능이 의존하는 최상위 등급 서비스이기 때문에, 새 아키텍처를 적용하는 일과 데이터를 옮기는 일을 동시에 밀어붙이는 대신 데이터를 먼저 보내고 구조는 나중에 손보는 순서를 택했다.

병목의 정체는 Spokes 쿼럼에 있었다. 과반수의 응답만 있으면 쓰기를 확정할 수 있지만, 실제로는 정상 상태인 모든 복제본의 응답을 기다린다. 결과적으로 가장 느린 복제본이 모든 쓰기의 지연을 결정하고, 쓰기 하나가 커밋될 때까지 저장소 전체 락을 쥐고 있기 때문에 그 지연이 곧 처리량 상한이 된다. 원거리 복제본이 하나 끼면 왕복이 여러 번 필요한 쓰기마다 25ms가 붙고, 이미 한계에 가까운 저장소는 밀어 넣을수록 더 뒤처진다.

Git의 일관성 요구는 사실 참조 하나하나에 적용된다. 서버가 어떤 참조의 갱신을 받아들인 뒤에는 클라이언트가 그보다 오래된 값을 보게 되지 않는다는 뜻이며, 이는 fast-forward든 force push든 삭제든 마찬가지다. 반면 서로 다른 참조에 대한 갱신은 대개 독립적이다. 그래서 GitHub는 직렬화가 필요 없는 것까지 줄세우지 않기로 하고, 쿼럼의 범위를 저장소에서 개별 참조로 옮겼다. 같은 참조를 두고 경쟁하는 쓰기만 서로를 기다리고, 각자 자기 브랜치로 푸시하는 에이전트와 개발자의 쓰기는 동시에 진행될 수 있다. 내부 테스트에서 서로 다른 참조에 대한 쓰기는 처리량이 3~4배 높아지고 지연은 낮아졌다.

다음 단계는 읽기와 쓰기의 분리다. 대부분의 저장소는 쓰기 1회당 읽기가 100~1,000회 수준으로 읽기에 극단적으로 치우쳐 있는데, 지금은 읽기와 쓰기가 같은 복제본을 두고 자원을 다툰다. GitHub는 저장소 데이터를 Azure Blob Storage에 내구성 있게 보관하고, 이를 쿼럼 구성원으로 참여시키는 가상 복제본(virtual replica)을 도입했다. 읽기는 서비스하지 않고 들어오는 쓰기를 객체 스토리지로 흘려보내는 완전한 임시 노드로, 객체 스토리지에 기록이 끝나야 쓰기가 승인된다. 서로 다른 참조의 트랜잭션을 묶어 네트워크 I/O 한 번으로 처리하기 때문에, 바쁜 저장소도 동시 쓰기 여러 건에 왕복 한 번 값만 치른다.

이 구조가 자리 잡으면 저장소가 필요로 하는 만큼 가상 읽기 복제본을 늘릴 수 있다. 이 복제본들은 Blob Storage에서 캐시를 데운 뒤 쿼럼에 들어가지 않고 읽기 트래픽을 받으며, 로컬 상태는 백그라운드에서 객체 스토리지와 맞춰 나간다. 분산 부하 테스트에서는 현재 아키텍처의 한계에 자주 부딪히던 하네스가 가상 읽기 복제본을 상대로는 오히려 자기 한계에 먼저 걸렸다.

한계도 분명하다. 백그라운드 정합 방식은 일부 읽기 워크로드에는 잘 맞지만 모든 종류의 읽기를 감당하려면 정합을 인라인으로 끌어와야 한다. 복제본이 마지막으로 따라잡은 시점 이후 바뀐 참조 상태와 데이터만 가져오는 방식으로 바꿔야 한다는 것이다. 또한 가상 쓰기 복제본은 당분간 컴팩션까지 맡고, 읽기 복제본은 그렇게 압축된 상태를 물려받는다.