“다음 PR 머지하려는데, main 이 빨개졌다. 누가 깨뜨렸지?”
이 한 줄을 주에 한 번 보기 시작하면, 어딘가에서 merge queue 라는 단어를 듣게 된다. GitHub 가 2023 년 9 월에 GA 시킨, PR 을 한 줄로 세워서 차례로 머지해 주는 도구.
화두 자체는 가볍지 않다. 큰 팀의 main 안정성 을 한 단계 끌어올린 자리로 평가받는다. 그런데 우리 팀 자리에 들여다 보면 — 정말 필요할까. 한 번 정리해 두면서 그 결도 같이 살펴봤다.
merge queue 가 하는 일 — 한 단락 요약
PR 이 머지 직전에 queue 에 들어간다. GitHub 가 queue 의 base + 앞 PR 들이 머지된 가상 base 를 만들어, 거기서 CI 를 돌린다. 통과한 것만 실제 main 에 머지된다. 한 줄로 — 사람이 “rebase → CI 기다림 → 머지 → 다음 PR rebase” 를 하던 결을 queue 가 대신 처리한다. 기존 Branch protection 의 “머지 직전 base 와 동기화 + CI 통과해야 머지 가능” 이 직렬화된 자동화 로 옮겨간 자리라 보면 된다.
merge queue 가 풀어주는 진짜 문제
queue 의 본질은 순서 자동화 가 아니다. 풀어주는 진짜 문제는 한 줄로 —
각자 base 에서는 통과했는데, 둘 다 머지된 main 에서는 깨지는 자리.
git 은 코드 충돌 은 알려주지만 논리 충돌 은 못 알려준다. 두 PR 이 같은 함수 를 건드리지 않았다면 git 은 깨끗하게 머지하지만, 그 함수가 호출되는 자리의 의미 가 어긋나면 — main 이 빨갛게 변한다. 이걸 semantic conflict 또는 logical merge skew 라고 부른다.
merge queue 는 queue 안에서 가상으로 미리 머지해 보고 CI 를 돌리니, 두 PR 의 가상 머지 결과 에서 깨지면 그 자리에서 잡힌다. main 은 빨개지지 않는다.
queue 는 main 의 색깔을 빨강에서 초록으로 옮기는 도구다.
언제 빛나는가
다음 세 자리가 동시에 있는 팀 에서 결이 분명해진다.
(1) 머지가 잦다. 하루 PR 머지가 수십 건 이상 으로 가면, 직전 base 와 동기화 → CI 의 사람 손이 본전을 못 뽑는다.
(2) CI 가 길다. CI 가 5 분 안에 끝나면 사람이 직렬로 처리해도 큰 문제가 없다. 15 분 / 30 분 으로 늘어나면 기다리는 자리 자체 가 비용이 된다.
(3) main 의 안정성이 비싼 자리다. main 이 항상 배포 가능 상태 여야 하는 결. trunk-based development 의 결로 가는 팀, 또는 main = production 결의 팀.
이 셋이 동시에 있으면 semantic conflict 가 매주 한 번씩 본인을 찾아오는 자리 에 도달한다. 거기서 merge queue 의 결이 빛난다.
언제 빛나지 않는가
반대 자리도 분명히 있다.
(1) PR 수가 적다. 하루 머지가 한 자리 수 인 팀에서는, 두 PR 이 동시에 머지될 확률 자체가 낮다. queue 에 들어갈 일이 없는 queue 가 만들어진다.
(2) CI 가 짧고 안정적이다. 3 분 안에 끝나는 CI 에서는 base 동기화 → CI → 머지 의 사람 손이 충분히 빠르다. 직렬화가 오히려 느려지는 자리 도 있다 — queue 안에서 한 PR 이 깨지면 뒤의 PR 들이 다시 줄을 서야 한다.
(3) main 이 깨져도 수습 비용이 작다. 작은 팀, 같은 시간대 일하는 팀, main 빨강 → 한 시간 안에 누가 고침 의 결이 자연스러운 자리. semantic conflict 자체가 드물고, 일어나도 사람의 결로 풀린다.
(4) queue 가 막혔을 때 푸는 자리가 비어 있다. queue 에서 한 PR 이 깨지면 작성자가 queue 밖에서 손봐서 다시 줄에 세우는 결 이 필요하다. 이 결을 누가 운영할 것인가 가 정해지지 않으면, queue 는 생긴 첫 주에 한 번 막히고 그대로 방치되는 자리에 도착한다.
이 네 자리 중 하나라도 분명히 그렇다 면, merge queue 는 지금의 자리에 필요한 도구 가 아니다. 언제든 도입할 수 있는 도구로 두고, 임계점이 옮겨오면 그때 들이는 결이 자연스럽다.
도입 전 세 줄 점검
거창한 결정 절차를 만들 일은 아니다. 도입 검토 자리에 세 줄 만 짚어두면 결이 잡힌다.
[충돌 빈도] main 이 주에 몇 번 빨갛게 변하는가,
그 중 코드 충돌이 아닌 *논리 충돌* 은 몇 번인가.
[CI 비용] CI 가 몇 분 걸리는가,
사람이 *직전 base 동기화 → CI 기다림* 에 주에 몇 시간 쓰는가.
[운영 자리] queue 가 막혔을 때 *누가 어떤 결로 푸는지* 정해져 있는가.
세 줄 다 그렇다 라면 도입의 결이 분명하다. 세 줄 중 하나라도 흐릿하면 — 기다려도 된다. 기다리는 결이 도입의 결만큼 가치 있을 때가 있다.
주변 도구 한 줄
native merge queue 가 들어오기 전에는 Bors, Mergify, Aviator, Trunk Merge, Graphite Merge Queue 같은 외부 도구의 자리였다. 지금도 이들이 모두 사라진 건 아니다 — 더 정교한 큐 정책, 그룹 머지, 우선순위 조정 같은 결은 외부 도구가 여전히 한 칸 더 들어가 있다.
다만 세 줄 점검 이 그렇다 가 되어 있는 자리에서, 처음 들이는 큐로는 native 가 가장 가벼운 결이다. 외부 도구는 native 의 한계가 보이기 시작할 때 옮겨가는 결 이 자연스럽다.
마치며 — 우리 자리는 어디인가
merge queue 는 큰 팀의 main 을 위해 잘 설계된 도구다. 다만 큰 팀의 도구 라고 해서 모든 팀의 도구 는 아니다.
위 세 줄 점검 으로 우리 자리를 들여다 보면 — 충돌 빈도, CI 비용, 운영 자리 셋 다 지금은 임계점에 못 미치는 자리에 있을 가능성이 더 크다. 그 자리에서 도구를 들이면 queue 자체를 운영하는 비용 이 원래 풀려고 했던 비용 보다 커진다. 도구가 문제를 해결하는 게 아니라 문제를 만드는 자리 에 도착하는 것.
merge queue 는 임계점에 도달했을 때 들이면 빛나는 도구 다. 지금 들여서 익숙해 두는 도구 가 아니다.
queue 는 자리를 잡아주는 도구이지, 결정해 주지는 않는다. 임계점이 어디 있는지를 결정하는 일은 — 여전히 사람의 자리에 남아 있다.