본문 바로가기
FrostK
테마 색상
로그인

읽는 데 8분Cat

GitHub 저장소는 빨라져도, 같은 브랜치의 push는 거절될 수 있어요

IT 인프라 개발원리
GitHub 저장소는 빨라져도, 같은 브랜치의 push는 거절될 수 있어요

두 작성자가 같은 커밋에서 출발해 서로 다른 파일을 추가했어요. 각자 다른 브랜치로 보내면 둘 다 성공했지만, 같은 main에 차례로 보내자 두 번째 push는 거절됐어요. 격리한 작은 Git 저장소에서 비교한 결과예요.

이 차이를 짚어 볼 만한 발표가 나왔어요. GitHub는 11월 5일 새 Git 저장소 아키텍처인 Rubicon을 설명하며, 내부 시험에서 저장소 하나에 에이전트 5,000개가 작업하고 초당 7,200번의 push를 보내는 부하를 처리했다고 밝혔어요. 읽기 요청도 낮은 지연으로 처리했다는 설명이에요. 서버의 처리량이 늘어날 때, 함께 일하는 개발자의 작업 방식에는 어떤 경계가 남을까요?

어디를 나눴길래 빨라지나요

Git에는 이미 만들어진 내용을 담는 객체와, 그중 어느 커밋을 가리킬지 정하는 참조(ref)가 있어요. 브랜치는 참조의 한 종류예요. 예를 들어 main의 끝을 새 커밋으로 옮기는 것은 참조 갱신이에요.

객체는 만들어진 뒤 내용을 바꾸지 않고 내용에 따른 이름을 가져요. 서로 다른 작성자가 객체를 저장할 때마다 저장소 전체가 순서를 합의할 필요가 적죠. 반면 같은 브랜치의 끝을 여러 작성자가 바꾸려면 누구의 갱신을 받아들일지 정해야 해요.

Rubicon은 이 차이에 맞춰 일을 나눴어요.

나눈 대상 맡는 곳 동작의 차이
Git 객체를 묶은 팩 데이터 Azure Blob Storage 가상 복제본이 업로드해 영구 저장해요
브랜치 등의 참조와 팩 목록 Ref-CAS라는 복제 로그 서비스 갱신 대상 참조의 예상 값이 맞는지 확인하고 기록해요
여러 팩을 합치는 정리 작업 별도의 정리 노드 요청을 받는 가상 복제본과 계산 자원을 분리해요

가상 복제본은 읽기와 쓰기 요청을 처리하는 서버예요. 로컬 디스크는 다시 채울 수 있는 캐시로 쓰고, 영구 데이터는 위 저장소들에 둬요. 바빠지면 요청 처리 서버와 정리 노드를 각각 늘릴 수 있는 구조죠.

Git·API·웹 요청이 Spokes를 거쳐 가상 복제본으로 가고, 쓰기는 팩을 Azure Blob Storage에 먼저 올린 뒤 Ref-CAS에 팩 목록과 참조 갱신을 한 트랜잭션으로 기록하며, 읽기는 Ref-CAS에서 버전을 확인한 뒤 필요한 팩만 Blob Storage에서 캐시로 가져오는 구조. 정리 노드는 복제본과 별도로 두 저장소를 직접 다룬다
쓰기는 팩을 먼저 올리고 참조는 그다음에 바꿔요. 읽기는 Ref-CAS의 버전부터 확인해요.

Ref-CAS의 갱신은 건드리는 참조에만 잠금을 걸고, 예상한 기존 값과 맞는지 검사해요. 서로 다른 브랜치의 갱신은 경합을 줄여 병렬로 처리할 수 있어요. GitHub는 에이전트들이 주로 자기 브랜치로 push하는 특성이 이 설계와 맞는다고 설명해요.

서로 다른 파일인데 왜 거절될까요

이 설계의 의미를 일반 Git의 동작과 견줘 봤어요. Rubicon 서버 대신, Git 2.39.5와 로컬 bare 저장소를 사용했어요. bare 저장소는 편집용 작업 폴더 없이 이력을 받아 두는 저장소예요.

기준 커밋 하나를 두 작업 폴더로 복제했어요. A는 a.txt, B는 b.txt만 추가해 각각 커밋했어요. 두 커밋 모두 같은 기준에서 갈라졌고, 서로의 새 커밋은 포함하지 않아요. push는 동시에 보내지 않고 차례로 실행했어요.

보낸 곳 A의 결과 B의 결과
각자 별도 브랜치 agent-a, agent-b 성공 성공
같은 브랜치 main 성공 fetch first로 거절

서로 다른 파일이어도 B의 커밋은 A의 커밋을 이어받지 않았어요. A가 먼저 옮긴 main을 B의 이력으로 바꾸면, 브랜치가 가리키는 이력에서 A의 작업이 빠져요. Git은 기본적으로 이런 갱신을 거절해요. 공식 git push 문서의 fast-forward 설명과도 일치하는 결과예요.

다시 push하려면 먼저 상대의 커밋을 가져와 두 작업의 이력을 이어야 해요. 가져온 이력을 병합하거나 내 변경을 그 위에 다시 올린 뒤 push하는 방법이 있죠. 파일 수정 위치가 다르다는 사실만으로 같은 브랜치의 push가 둘 다 받아들여지지는 않아요.

Rubicon이 줄이려는 것은 저장소 전체가 불필요하게 순서를 기다리는 비용이에요. 팀의 이력을 하나로 모으는 일은 여전히 별도의 작업으로 남아요.

여러 브랜치를 한 번에 바꿔야 한다면

브랜치별로 일을 나누더라도, 여러 참조를 함께 바꿔야 하는 작업이 있어요. Rubicon의 Ref-CAS 설명도 트랜잭션이 건드리는 참조들을 잠그고 예상 값을 검사한다고 해요. 하나의 트랜잭션에서 여러 참조를 함께 갱신할 수도 있어요.

일반 Git에서는 서버가 지원할 때 --atomic으로 여러 참조의 갱신을 전부 성공시키거나 전부 하지 않도록 요청할 수 있어요. 같은 로컬 시험에서 release는 기준 커밋에 둔 채, 뒤처진 B가 다음 push를 보내게 했어요.

git push --atomic origin HEAD:refs/heads/main HEAD:refs/heads/release

main은 앞서 A가 갱신했으므로 거절됐어요. 혼자라면 갱신 가능한 release도 atomic push failed로 거절됐고, 저장소를 다시 조회하니 기준 커밋 그대로였어요.

이 결과는 여러 참조를 함께 바꿔야 할 때의 전부 아니면 전무라는 조건을 보여 줘요. 서버 처리량과 별개로, 작업을 서로 다른 브랜치로 나눌지, 여러 브랜치를 하나의 갱신으로 묶을지도 설계해야 해요.

아직 내 저장소에 적용됐다고 볼 수는 없어요

GitHub 발표 기준으로 Rubicon은 제한된 비공개 프리뷰예요. 더 넓은 배포는 2027년 초 계획이라고 밝혔어요. 초당 7,200회도 GitHub의 내부 시험 결과이며, 모든 저장소에서 지금 이용할 수 있는 성능 보장은 아니에요.

개발자가 주목할 부분은 숫자와 함께 제시된 병렬화 단위예요. 요청 처리 서버를 늘리는 일, 브랜치 갱신을 받아들이는 일, 여러 작성자의 이력을 합치는 일을 나눠 보면 발표의 적용 범위가 더 분명해져요.

확인한 범위

  • 기준일: 2026-11-07. GitHub 원문 발표일은 2026-11-05예요.
  • 직접 한 것: 비관리자 격리 컨테이너의 Git 2.39.5로 로컬 bare 저장소와 두 작업 폴더를 만들었어요. 별도 브랜치, 같은 브랜치, atomic 다중 참조 push 결과와 최종 참조를 비교했어요. GitHub 설계를 공식 git push의 이력 보존·atomic 설명과 대조했어요.
  • 읽은 것: GitHub Rubicon 설계 글과 Git 공식 push 문서, 같은 발표를 소개한 한국어 서버포럼 글.
  • 확인하지 못한 것: Rubicon 구현이나 동시 부하·성능은 시험하지 않았어요. 로컬 시험은 같은 출발점의 두 작성자가 순서대로 push한 경우이며 GitHub 보호 규칙·서비스 정책은 시험 범위 밖이에요.

출처

Inside GitHub's new Git storage architecture
GitHub's new Git storage architecture is built so thousands of agents can push to a single repository at once. Here's how it works.
Git - git-push Documentation