내부 작업에서 출발해, 조직과 시스템을 식별할 수 있는 정보는 빼고 운영 원칙만 정리했다.

머지큐를 도입한 뒤 CI 대기 시간을 줄이려는 개선이 이어졌다. 그 과정에서 이런 안내를 보았다.

큐에 넣기 전에 PR 브랜치를 최신 main 기준으로 맞춰 달라. 그래야 큐에서 합친 코드가 PR에서 검증한 코드와 같아져 CI를 건너뛸 수 있다.

문장만 보면 틀리지 않다. 최신 main 위의 PR은 큐가 만들 merge group과 같아질 가능성이 높고, 그 경우 기존 PR 결과를 재사용할 수 있다. 빠른 경로다.

그런데 이것이 기본 규칙이 되는 순간, 한 가지 질문이 남는다.

우리는 왜 머지큐를 도입했는가?

한 줄 요약

  • 머지큐는 최신 main 반영과 조합 검증을 개발자의 수동 작업에서 시스템으로 옮기는 장치다.
  • PR 결과 재사용은 merge group이 PR과 실제로 같을 때만 성립하는 최적화다.
  • “최신 main으로 맞춘 뒤 큐에 넣기”가 상시 규칙이 되면, 머지큐가 없애려던 수동 동기화와 중복 검증을 다시 개발자에게 돌려준다.
  • 먼저 정할 것은 캐시 설정이 아니라 main을 어떤 성격의 브랜치로 운영할지다.

머지큐가 해결하려던 일

공유 브랜치에 PR이 많이 모이면 이상한 경주가 생긴다.

  1. PR A가 CI를 통과한다.
  2. 그 사이 PR B가 main에 들어간다.
  3. A는 더 이상 최신 main 기준이 아니다.
  4. A 작성자가 main을 병합하거나 rebase하고 CI를 다시 돌린다.
  5. 그 사이 또 다른 PR이 들어가면 같은 일을 반복한다.

이 흐름에서 개발자가 하는 일은 제품을 더 안전하게 만드는 판단이 아니다. 최신 상태를 따라잡기 위한 기계적인 동기화다. CI가 길고 main 변경이 잦을수록, 사람은 검증 결과를 지켜보며 다음 재시도를 기다리는 역할이 된다.

GitHub가 머지큐를 설명하는 방식도 이 문제에서 출발한다. 큐는 대상 브랜치의 최신 상태, 앞선 대기 PR, 현재 PR을 합친 임시 merge group을 만들고 그 조합을 검증한다. 통과한 조합만 대상 브랜치에 반영한다. GitHub 공식 문서

즉 큐의 본업은 “PR이 마지막으로 본 main”을 믿는 것이 아니라, 실제로 병합될 최신 조합을 검증하는 일이다.

GitHub 내부 사례도 같은 방향을 말한다. 머지큐를 대규모로 운영하며 강조한 것은 엔지니어가 수동 병합과 반복 검증을 하지 않게 만드는 것이었다. 사람은 변경을 준비하고 큐에 넣은 뒤 다음 일로 넘어가고, 조합과 순서는 시스템이 책임진다. How GitHub uses merge queue to ship hundreds of changes every day

결과 재사용은 기본 동작이 아니다

PR에서 CI가 통과했다. 큐가 만든 merge group도 정확히 같은 Git tree라면, 같은 검증을 다시 돌릴 이유가 없다. 이때 기존 결과를 재사용하는 것은 합리적이다.

하지만 다음 중 하나라도 달라지면 이야기가 바뀐다.

  • 대상 브랜치에 다른 변경이 들어왔다.
  • 앞선 큐 항목이 같은 merge group에 합쳐졌다.
  • 큐가 merge commit, rebase, squash 등 다른 병합 결과를 만들었다.
  • 검증이 소스 코드 외의 시간·환경·외부 의존성에 영향을 받는다.

이때 재사용할 수 없는 것은 도구의 낭비가 아니다. 검증 대상이 바뀌었기 때문이다.

결과 재사용은 그래서 다음처럼 다뤄야 한다.

merge group과 PR의 검증 대상과 조건이 동일할 때만 쓰는 빠른 경로.

반대로 다음처럼 바뀌면 안 된다.

결과 재사용을 위해 개발자가 항상 최신 main을 수동으로 반영해야 하는 팀 규칙.

후자는 큐의 성공률을 높이는 대신, 대기와 재검증 비용을 각 PR 작성자에게 분산한다. 큐 대시보드만 보면 빨라질 수 있다. 그러나 팀 전체의 리드 타임과 사람의 전환 비용은 오히려 늘 수 있다.

main은 어떤 브랜치인가

머지큐가 잘 작동하려면 main의 역할이 비교적 명확해야 한다. 단순히 모든 브랜치가 모이는 장소가 아니라, 현재 변경들을 통합해 다음 배포 후보를 만들 수 있는 브랜치여야 한다.

이 뜻은 모든 제품이 같은 정책을 가져야 한다는 말은 아니다. 오히려 제품과 배포 방식에 따라 역할을 나눠야 한다.

상황 우선할 운영 방식
main이 곧 제품 통합 브랜치이고, 조합 결함 비용이 큼 머지큐로 최신 조합 검증. main은 항상 배포 후보 상태 유지
태그로 특정 시점만 배포하고, main의 변경이 곧바로 릴리스되지 않음 main 통합 검증과 태그 릴리스 승인을 분리
모노레포에서 앱별 영향 범위가 뚜렷함 변경 범위 기반 태스크 실행과 앱별 검증을 우선 설계
긴 E2E·성능 검증이 있음 모든 변경에 같은 비용을 부과하지 말고 위험도·배포 직전·야간 검증으로 분리
장기 실험이나 외부 의존 작업이 많음 기능 플래그, 별도 통합 브랜치, 배포 단위를 먼저 설계

여기서 중요한 것은 “트렁크 기반 개발”이나 “머지큐”라는 이름이 아니다. main에 들어간 코드가 무엇을 약속하는지, 누가 어떤 시점에 그 약속을 검증하는지다.

main이 배포 가능한 제품 후보라면 머지큐의 전체 조합 검증은 비용이 아니라 보험에 가깝다. 반대로 main이 실험과 장기 통합을 쌓는 장소라면, 모든 PR에 같은 큐 정책을 강제하는 것이 적절한지 다시 봐야 한다.

좋은 사례가 줄인 것은 기다림만이 아니었다

좋은 사례들은 “큐에서 CI를 안 돌렸다”로 끝나지 않는다. 병목을 나누고, 사람이 하던 반복 작업을 시스템으로 옮겼다.

Reddit의 모바일 CI 사례는 컨테이너·Git 캐시와 초기화 단계를 다듬어 큐 시간을 줄였다고 설명한다. 핵심은 개발자가 최신 변경을 수동으로 맞추도록 만드는 것이 아니라, 큐가 빠르게 상태를 만들고 필요한 작업을 시작하도록 한 데 있다. Reddit engineering case study

Uber의 모노레포 사례도 패키지 매니저 하나를 교체해서 해결한 이야기가 아니다. 빌드 실행 환경, 캐시, 태스크 구조를 분석해 병목을 줄였다. How Uber halved monorepo build times with Buildkite

여기서 얻을 수 있는 구분은 명확하다.

층 확인할 질문
의존성 설치 패키지 저장소 캐시가 복원되는가? 다운로드와 설치가 실제 병목인가?
태스크 실행 build·typecheck·test 결과를 안전하게 재사용할 수 있는가?
큐 조합 검증 merge group이 바뀌었을 때 어떤 검증이 반드시 필요한가?
러너·인프라 캐시 서버 접근, 아키텍처, 권한, 할당량이 실제로 병목인가?
개발자 경험 수동 main 반영과 재실행이 얼마나 자주 발생하는가?

의존성 설치 캐시는 다운로드와 설치 비용을 줄인다. 태스크 캐시는 build나 typecheck 결과를 재사용한다. 머지큐는 최신 조합의 안전성을 보장한다. 셋을 하나의 “CI가 느리다”로 묶으면, 캐시나 러너의 문제를 개발자 rebase로 해결하려는 결론에 도달하기 쉽다.

최적화가 정책을 대체할 때

어떤 최적화는 특정 조건에서만 효과가 있다. PR과 merge group이 같을 때의 결과 재사용이 그렇다.

그 조건을 충족하는 비율을 높이는 것은 좋은 일이다. 다만 방법은 두 갈래다.

  1. 개발자가 큐 직전에 직접 main을 반영한다.
  2. 큐가 최신 조합을 만들고, 동일할 때만 재사용하며 다르면 검증한다.

첫 번째는 당장의 재사용률을 올릴 수 있다. 그러나 main 변경이 잦은 팀에서는 “누가 먼저 들어갔는지”에 따라 여러 사람이 같은 동기화 작업을 반복한다. 사람이 큐의 빈틈을 메우는 구조다.

두 번째는 큐가 처리할 일이 남는다. 대신 개발자는 변경의 품질과 리뷰에 집중하고, 시스템은 병합 순서와 조합 검증을 책임진다. 머지큐를 둔 이유에 더 가깝다.

이 선택은 성능만의 문제가 아니다. 어떤 비용을 중앙의 시스템에 둘지, 어떤 비용을 개별 개발자에게 되돌릴지에 대한 운영 정책이다.

먼저 합의할 것

머지큐를 운영하며 다음 질문에는 문서로 답할 수 있어야 한다.

  1. main은 항상 배포 가능한 제품 후보인가?
  2. 큐에서 반드시 다시 검증해야 하는 조건은 무엇인가?
  3. PR 결과 재사용은 기본 동작인가, 동일한 tree에서만 쓰는 최적화인가?
  4. 긴 검증은 어느 위험도와 시점에서 실행하는가?
  5. 설치·빌드·타입체크·테스트·큐 대기를 각각 어떻게 측정하는가?
  6. 수동 main 반영 횟수와 큐 이탈 사유를 누가 보고 개선하는가?

이 질문에 답하지 않은 채 “최신 main을 먼저 맞춰 달라”고만 하면, 정책의 빈자리를 개발자의 습관으로 메우게 된다. 규칙이 모호해 보일 수는 있다. 하지만 제품과 배포 단위가 다른 조직에서 필요한 것은 하나의 구호가 아니라, 조건이 달라질 때 어떤 정책을 적용할지 결정하고 계속 관리하는 일이다.

마치며

머지큐는 사람을 더 빠르게 rebase하게 만드는 도구가 아니다. 사람이 최신 상태를 따라가며 같은 CI를 반복하지 않도록, 최신 조합의 검증을 시스템이 맡게 하는 도구다.

캐시와 결과 재사용은 그 흐름을 빠르게 만드는 좋은 수단이다. 다만 수단이 목적을 바꾸면 안 된다. 먼저 main이 어떤 약속을 하는 브랜치인지 정하고, 그 약속을 지킬 검증을 큐에 둔 뒤, 동일한 결과를 재사용할 수 있는 곳만 조심스럽게 줄이는 편이 맞다.

머지큐를 도입하기 전에 정의할 것은 “누가 최신 main을 맞출 것인가”가 아니라, “main에 들어가는 코드를 어떤 품질로 보장할 것인가”다.