본문 바로가기

직접 만든 소프트웨어, 두 가지 개발 기록

구현 구조와 적용 범위, 확인한 기능과 남은 한계를 함께 기록합니다.

소프트웨어개발

GitHub 새 저장 구조 미리보기: 푸시가 늘어도 브랜치 충돌이 남는 이유

by 아빠띠띠뽀 2026. 11. 10.

여러 개발자나 AI 에이전트가 같은 GitHub 저장소에 푸시할 때, 저장소가 더 많은 요청을 처리할 수 있어도 같은 브랜치를 서로 다른 방향으로 바꾸는 문제는 남습니다. GitHub의 새 저장 구조는 이미 만들어진 Git 데이터를 여러 곳에서 쓰는 일과, 브랜치가 어느 커밋을 가리킬지 결정하는 일을 나눴습니다. 팀에서 기대할 변화와 그대로 지켜야 할 브랜치 규칙을 이 구분으로 살펴볼 수 있어요.

GitHub는 2026년 11월 5일 자 엔지니어링 글에서 새 구조를 공개 설명했습니다. 이 글을 확인한 11월 9일 기준, 원문에서 안내하는 제공 단계는 제한적 비공개 미리보기입니다. GitHub 내부 시험과 공개 아키텍처 설명을 소개하는 글이며, 개별 계정에서 기능이 켜져 있는지 보거나 저장소에 부하를 걸어 본 결과는 아닙니다.

푸시 한 번 안에는 서로 다른 일이 들어 있습니다

작은 프로젝트에서 두 사람이 각자 다른 수정을 끝내고 같은 원격 저장소에 올린다고 상상해 볼게요. 파일의 내용과 새 커밋을 저장소에 보내는 일이 먼저 떠오릅니다. 그런데 Git은 받은 데이터를 보관하는 것만으로 푸시를 끝내지 않습니다. 해당 브랜치 이름이 이제 어느 커밋을 가리켜야 하는지도 결정해야 합니다.

GitHub의 설명에서 Git 객체는 내용으로 이름이 정해지고 이미 만들어진 내용은 바뀌지 않습니다. 파일 내용, 파일 구조, 커밋 같은 단위가 이런 객체에 해당합니다. 서로 독립된 객체를 보관하는 일은 누가 먼저 저장해야 하는지 매번 합의할 필요가 적습니다. 반면 브랜치의 끝을 나타내는 참조는 새 커밋이 생길 때 달라집니다. 두 요청이 같은 이름을 다른 곳으로 옮기려 하면 둘 다의 기대를 한꺼번에 만족할 수 없는 경우가 생겨요.

이 차이를 메모로 표현하면 ‘새 쪽지를 보관함에 넣기’와 ‘공용 표지에 현재 페이지를 표시하기’의 차이와 비슷합니다. 쪽지 두 장은 각각 보관할 수 있습니다. 그러나 같은 공용 표지는 어느 페이지를 가리킬지 정해야 합니다. 비유는 쓰기 작업의 성격을 설명하려고 든 예입니다. 실제 GitHub 저장 구조를 그대로 본뜬 것은 아닙니다.

푸시에서 하는 일 무엇이 달라질 수 있나요?
Git 객체를 전달하고 보관 변경 내용과 커밋에 필요한 객체를 저장
브랜치 참조를 갱신 브랜치 이름이 가리키는 커밋을 결정
규칙을 검사 그 사용자의 변경이 저장소 정책에 맞는지 확인
나중에 브랜치를 합침 파일 내용이 충돌하는지 별도로 살펴봄

GitHub가 나눈 것은 저장, 처리, 정리입니다

엔지니어링 글은 새 구조를 Rubicon이라고 부릅니다. 원래의 요청 경로 중 라우팅과 부하 분산을 담당하는 Spokes는 유지됩니다. 요청은 해당 저장소에 배정된 가상 복제본에 전달됩니다. 이 복제본은 요청을 처리할 계산 자원이고, 로컬 디스크에는 다시 만들 수 있는 캐시를 둔다고 설명합니다. 요청 하나가 꼭 특정 노드에 도착해야만 결과가 올바르게 되는 설계는 아니라는 것이 글의 요점입니다.

오래 보관해야 하는 객체 묶음은 Azure Blob Storage에 둡니다. 한 번 보관하는 객체 데이터와, 자주 바뀌는 참조 상태의 성격이 달라서입니다. 새 구조에서 참조와 객체 묶음의 목록을 다루는 서비스는 Ref-CAS입니다. GitHub 설명에 따르면 가상 복제본은 푸시의 객체 묶음을 먼저 저장한 뒤 참조 갱신을 확정합니다. 참조가 가리키는 객체가 아직 보관되지 않은 상태를 피하기 위한 순서입니다.

이 설계가 나온 배경도 흥미롭습니다. GitHub는 처음에 저장소의 참조 변경 기록을 하나의 블롭으로 색인하고, 조건이 맞을 때만 그 블롭을 바꾸는 방법을 시험했다고 설명합니다. 내용이 바뀌지 않았는지 읽기는 쉬웠지만, 한 블롭에 조건부 쓰기를 동시에 여러 개 진행할 수 없어 쓰기를 한 번씩 기다려야 했습니다. 작업을 묶어 보내도 하나의 왕복 요청이 저장소 쓰기의 병목이 될 수 있었습니다.

Ref-CAS라는 이름의 CAS는 현재 값이 기대한 값과 맞는지 비교한 뒤 바꾸는 방식을 가리킵니다. 간단히 말해 ‘브랜치가 아직 내가 본 곳을 가리키면 새 위치로 바꾸자’라는 조건을 다룰 수 있습니다. 설명을 위한 단순화이므로 실제 Git의 모든 푸시 검사가 한 문장으로 끝난다는 뜻은 아닙니다. 참조의 순서를 정하는 일과 이미 내용이 정해진 데이터를 보관하는 일을 나눠 생각하면, 처리량과 이력 보존이 왜 별개인지 이해하기 쉽습니다.

참조 상태가 변하지 않았는지 확인하는 방식도 따로 설명합니다. 요청을 처리하는 쪽에서 현재 갖고 있는 참조의 버전을 전달하고, 최신과 같으면 참조 내용을 다시 보낼 필요가 없다는 설계입니다. 이 부분에서 중요한 것은 개발자가 자기 저장소에 무슨 설정을 추가해야 한다는 안내가 아니라는 점이에요. 읽기와 쓰기 요청을 운영하는 GitHub 쪽에서 조정하는 내부 구조입니다.

푸시할 때마다 객체 묶음이 늘어나면 그것을 읽는 쪽에서도 비용이 생깁니다. 그래서 여러 묶음을 합쳐 정리하는 작업을 별도의 노드에서 처리합니다. GitHub는 이 작업이 원래 요청을 받는 복제본과 분리돼 있고, 새로 만든 묶음의 객체가 입력과 같은지 확인하고 목록을 갱신한다고 설명합니다. 쓰기 요청을 받는 곳에 정리 작업을 모두 얹지 않는 것이 중요한 설계 선택입니다.

내부 시험 숫자를 팀의 예상 속도로 쓰지 마세요

공식 글에는 한 저장소에서 동시에 일하는 에이전트 5,000개와 한 저장소로 보내는 초당 7,200회 푸시를 지원한 내부 시험이 소개됩니다. GitHub가 특정 환경에서 새 구조의 성능을 확인한 결과입니다. 내 저장소의 제한이 그 숫자로 바뀌었다거나, 어떤 에이전트가 동시에 보내도 모두 성공한다는 약속으로 읽으면 안 됩니다. 11월 9일 확인한 원문은 제한적 비공개 미리보기 상태이며, 팀별 제공 여부를 이 글에서 확인한 것은 아닙니다.

푸시 수치가 보여 주는 것은 GitHub가 요청을 처리할 수 있도록 내부 구조를 바꿨다는 점입니다. 팀의 작업 시간이 줄어들지는 브랜치 수, 변경 내용, CI의 실행 시간, 검토 순서에도 달려 있습니다. 예를 들어 서버 응답이 빨라져도 같은 파일을 여러 사람이 서로 다르게 고친 경우에는 통합할 사람이 여전히 필요합니다. 반대로 늦게 끝난 배포가 모두 저장소의 쓰기 속도 문제라는 뜻도 아닙니다.

이를 구분하려면 가상의 팀에서 ‘원격에 변경이 보인 때’, ‘검사 대기열에 들어간 때’, ‘검사와 검토가 끝난 때’를 별도로 기록해 볼 수 있습니다. 검사가 기다리는 시간을 푸시 자체에 걸린 시간과 합치면 개선할 곳을 잘못 고를 수 있어요. 그 기록이 GitHub 내부의 스토리지 지연을 직접 측정하는 방법은 아닙니다. 팀이 볼 수 있는 단계에서 어디에 대기가 있었는지 구분하는 관찰 예입니다.

따라서 이 발표를 팀에 소개한다면 수치 옆에 ‘벤더 내부 시험’, ‘한 저장소’, ‘개별 저장소에서 직접 측정하지 않음’을 적어 보세요. 현재 기능이 켜져 있는지, 초당 몇 건을 목표로 잡을지, 브랜치 정책을 바꿀지는 서로 다른 결정입니다. 첫 번째가 확인되지 않았는데 나머지를 확정할 이유가 없습니다.

팀 작업에서는 푸시와 병합의 실패를 따로 읽으세요

가상의 예에서 두 에이전트가 서로 다른 작업 브랜치로 푸시했다고 합시다. 같은 원격 저장소에 보냈어도 서로 다른 이름을 갱신하므로, 같은 브랜치의 끝을 동시에 바꾸는 상황과 다릅니다. 하지만 두 변경이 같은 파일을 건드렸다면 나중에 합칠 때 충돌할 수 있습니다. 브랜치를 나누는 일은 작업 범위를 구분하는 데 도움이 되지만 파일의 의미나 최종 병합을 대신 해결하지 않습니다.

이번에는 두 요청이 같은 브랜치의 같은 시작점을 보고 각각 새 커밋을 만들었다고 해 볼게요. 첫 요청이 먼저 반영되면 두 번째 변경이 현재 원격 커밋을 포함하지 않을 수 있습니다. Git의 공식 문서는 이처럼 기존 이력을 이어 가지 않는 갱신을 일반 푸시에서 거부할 수 있는 경우로 설명합니다. 거부가 났다면 푸시할 대상과 현재 브랜치 이력을 먼저 대조하세요. 무작정 강제로 덮어쓰면 앞서 들어간 변경을 잃을 수 있습니다.

관찰한 모습 먼저 구분할 질문
요청이 오래 걸리거나 응답이 없음 서버 응답과 내 쪽 연결 중 어디까지 보였나요?
브랜치 이력 갱신이 거부됨 현재 원격 브랜치의 변경을 내 작업이 포함하나요?
저장소 정책으로 거부됨 해당 브랜치의 권한이나 검토 조건에 맞나요?
푸시는 됐는데 병합에서 충돌 두 브랜치의 수정 범위가 어디에서 겹치나요?

팀 회의에서는 ‘푸시 실패’라는 한 줄 대신 화면에서 확인한 단계와 오류를 적어 보세요. 어떤 저장소와 브랜치를 대상으로 했는지, 변경이 원격 목록에 보였는지, 이후 검사가 시작됐는지 정도면 다음 확인 순서를 잡을 수 있습니다. 승인된 저장소에서 읽기 상태부터 보며, 문제의 종류가 드러나기 전에 재제출을 반복하지 않는 것이 좋습니다. 이미 보낸 내용과 팀원의 변경을 보존하는 방법도 함께 정하세요.

코드가 아직 합쳐지지 않은 상태에서 작업을 다른 사람에게 넘길 때는 마지막으로 확인한 커밋과 그 커밋이 올라간 브랜치, 아직 병합하지 않은 이유를 남기면 됩니다. 예컨대 ‘원격 브랜치에서 커밋은 확인했지만 같은 설정 파일을 수정한 다른 변경과 검토가 필요함’이라고 적을 수 있습니다. 인프라 처리량을 진단하지 않더라도, 이 정도 기록으로 다음 사람이 변경을 잃지 않고 이어서 확인할 수 있습니다.

자주 나오는 질문

지금부터 팀원이 수천 개의 작업을 동시에 돌려도 되나요? 원문에 나온 것은 내부 시험과 제한적 비공개 미리보기입니다. 자신의 저장소의 제공 상태나 실제 처리 목표를 대신하지 않습니다. 팀이 이미 정한 권한과 실행 범위를 유지하고, 병렬 작업이 필요하다면 승인된 범위에서 작은 단위로 나누어 확인하세요.

브랜치 보호나 PR 절차도 달라졌나요? 아키텍처 글은 개발자가 기존처럼 푸시·가져오기·브랜치를 사용하고 기존 보호도 유지된다고 설명합니다. 실제 권한과 보호 규칙은 저장소 설정에 따릅니다. 느린 푸시나 거부를 해결하려고 검토 규칙을 해제할 이유는 없습니다.

브랜치에 푸시했다면 태그도 올라갔나요? Git에서 어떤 참조를 업데이트했는지 별도로 확인해야 합니다. 브랜치 푸시와 태그 전송을 구분한 글에서 확인 질문을 더 볼 수 있습니다. 여기서 다룬 저장 구조 개선은 푸시한 대상의 종류나 브랜치의 권한을 바꾸는 기능이 아닙니다.

확인한 공식 자료

2026년 11월 9일 GitHub의 11월 5일 저장 구조 해설과 Git의 푸시 공식 문서를 확인했습니다. 앞의 자료는 GitHub의 내부 설계와 시험 범위를, 뒤의 문서는 참조 갱신과 푸시 거부 조건을 설명합니다. 팀에서 쓸 수 있는지와 실제 성능은 별도로 확인해야 합니다.

GitHub 저장 구조에서 Git 객체, 브랜치 참조, 보관과 처리, 팀의 푸시·병합을 나눈 자체 개념도
객체 보관, 참조 갱신, 팀의 푸시와 병합을 나누어 설명한 자체 개념도입니다.