TECH 으로 돌아가기
TECH HACKER NEWS 오늘 7분 읽기 26 READS

Git 릴리스 이력, 수천 개의 태그 대신 커밋 체인으로 기록하기

운영 규모가 커질수록 배포 이력을 어디에 어떻게 남길지는 생각보다 까다로운 문제가 된다. Mark Dominus가 ZipRecruiter 재직 시절 겪은 사례는 이 고민을 잘 보여준다. 당시 사내 Git 모노레포는 배포가 일어날 때마다 태그 객체를 하나씩 생성했다. 태그 이름에는 대상 티어(stg, prod), 애플리케이션과 호스트 종류(www.our-app), 배포 일시, 그리고 배포된 트리의 해시가 담겼다. 무엇이 언제 어디에 배포됐는지를 완결된 형태로 기록하려는 합리적인 의도였다.

문제는 이 방식이 쌓이면서 드러났다. 시간이 지나자 이런 태그 객체가 수천 개에 이르렀다. Git은 수천 개의 커밋을 다루도록 잘 설계돼 있지만, 수천 개의 ref를 다루는 데는 그렇지 않다. 당시 Git은 ref를 packed-refs라는 파일에 각 ref 이름과 가리키는 객체 ID 쌍으로 저장했고, ref 이름을 객체로 해석하는 작업은 이 파일을 훑어야 했다. 하루에도 여러 번 수행되는 기본 동작에 체감될 만큼 시간이 걸리기 시작했으며, 로컬뿐 아니라 Git 서버 작업까지 느려졌다. git fetch나 git branch의 출력도 이력 기록용 태그 이름으로 어수선해졌다.

태그가 이력 저장소로 부적합한 이유

Dominus가 지적하는 더 근본적인 문제는 성능이 아니라 구조다. 우선 메타데이터가 순전히 태그 이름 규약에만 존재했다. 이름 안에 트리 해시가 들어 있어도, 태그가 실제로 그 트리를 가진 커밋을 가리키도록 강제하는 장치는 Git에 없었다. 이름 형식은 사람의 약속일 뿐 Git이 이해하는 정보가 아니었다. 또한 이력을 저장소에 남기는 중요한 이유 하나는 불변성 확보인데, 태그는 불변이 아니다. 언제든 삭제되거나 임의의 이름으로 새로 끼워 넣을 수 있다.

실무에서 가장 자연스럽게 나오는 질문을 생각해보면 한계는 더 분명해진다. "www.our-app을 운영에 마지막으로 배포한 게 언제였지?"에 답하려면 전체 태그 목록을 받아 prod.www.our-app으로 필터링하고 날짜순으로 정렬한 뒤 마지막 항목을 꺼내야 했다. 직전 스테이징 배포가 잘못돼 그 이전 버전으로 되돌리고 싶을 때도 마찬가지로 수천 개를 걸러 정렬한 다음 끝에서 두 번째를 골라야 했다. 이력을 기록하는 목적 자체에 비해 조회가 지나치게 번거로웠다.

커밋 체인으로 다시 설계하기

2019년에 그가 제안한 대안의 핵심은 간단하다. (티어, 앱) 조합마다 ref를 단 하나만 둔다. 배포가 몇 번 일어나든 stg.www.our-app이라는 ref 하나면 된다. 이 ref는 실제 릴리스 커밋이 아니라 트리 해시 같은 메타정보를 담은 '메타정보 커밋'을 가리킨다. 이 커밋은 부모를 둘 가진다. 첫 번째 부모는 직전 릴리스의 메타정보 커밋이고, 두 번째 부모는 이번에 실제로 배포된 릴리스 커밋이다. 생성은 git commit-tree에 -p 옵션 두 개를 넘기는 것으로 끝나며, 명령은 새 커밋의 SHA를 출력한다. 배포 시각은 따로 저장할 필요가 없다. 메타정보 커밋이 만들어진 시각이 곧 배포 시각이기 때문이다.

이 구조의 장점은 Git이 평소 이력을 엮는 방식과 완전히 같다는 데 있다. 메타정보 커밋들이 일반 커밋 이력처럼 연결되므로 git log stg.www.our-app이 그대로 릴리스 이력을 역순으로 나열한다. 전체 이력은 단 한 번의 fetch로 가져올 수 있고, git log의 모든 옵션이 그대로 먹힌다. 특정 날짜 이전의 마지막 릴리스를 찾는 일도 평소 쓰던 방식으로 처리된다. rev-parse 표기도 자연스럽게 들어맞는다. 메타정보 커밋 M에 대해 M^는 직전 릴리스의 메타정보 커밋, M^{2}는 실제 배포된 커밋, M^{2}^{tree}는 그때 배포된 트리다. 트리 ID를 이름에 박아 넣을 필요가 사라진다. 롤백할 때는 옛 메타정보 커밋과 새 메타정보 커밋이 동일한 릴리스 커밋 객체를 공유할 수 있어, 과거와 완전히 같은 커밋으로 되돌아간다.

기록할 정보의 유연성도 확보된다. 메타정보 커밋은 생성 날짜 등 온전한 커밋 메타데이터와 커밋 메시지를 가지므로, 비정형 텍스트는 메시지에 넣고, TOML로 구조를 강제하거나 git-interpret-trailers로 반정형 정보를 담을 수 있다. 커밋 메시지로 부족하면 커밋에 딸린 트리에 임의 분량의 구조화된 정보를 둘 수도 있다. 예시에서는 메타정보 커밋의 트리를 릴리스 커밋의 트리와 동일하게 뒀는데, 그러면 배포된 파일을 어느 쪽에서 봐도 되어 일부 명령이 단순해진다. 물론 전혀 다른 트리를 써도 된다.

성능 우려에 대한 답도 명쾌하다. 연결 리스트 형태의 커밋을 따라가는 것은 git log가 평소 하는 바로 그 동작이다. Git의 어떤 연산이 빠르다면 그게 바로 이 연산이다. 모노레포의 27만 5천 개 커밋 이력을 추적하는 데도 몇 초면 충분한데, 수백 개 규모의 릴리스 커밋을 따라가는 일은 문제가 되지 않는다. 정작 Dominus 본인은 이 제안이 끝내 채택되지 않았고, 아직 보지 못한 결함이 있을 수 있다고 솔직하게 덧붙인다. 그럼에도 ref 폭증으로 저장소가 느려지는 비슷한 상황에 놓인 팀이라면, 배포 이력을 Git이 이미 잘 다루는 자료 구조 위에 올려두는 이 접근은 한 번쯤 검토해볼 가치가 있다.

SOURCE · HACKER NEWS
원문 전체 보기 → https://blog.plover.com/prog/git/history-proposal.html
SHARE
NEXT · CHOOSE

변화를 읽었다면,
내가 만들 수익 구조를 고릅니다.

정보를 더 모으는 데서 멈추지 않고, 광고·외주·판매·중개·구독 중 내 상황에 맞는 출발점을 정해보세요.

21가지 수익 구조 살펴보기 →
처리 중...