Zuul의 Speculative Merge와 Cross-project Dependencies 정리본
0. 들어가며
최근 Zuul 프로젝트의 concepts.po 파일을 한글로 번역 하는 컨트리뷰션
작업을 진행하면서, '‘Speculative Merge(추측 기반 병합)’라는 용어를 접하게 되었습니다.
처음에는 단순히 “가상 병합” 정도로 이해했지만, 이 개념이 실제로 어떻게 동작하며 왜 대규모 협업 환경에서 중요한지 정확히 이해하고 싶어 추가로 학습하게 되었습니다.
특히 실습을 진행하는 과정에서
Zuul Hands-on Part 6: Cross-project Dependencies(교차 프로젝트 의존성) ( Zuul Hands on - part 6 - Cross project dependencies - Software Factory ) 문서를 참고하여
추측 기반 병합과 교차 프로젝트 의존성 기능을 직접 실험해 보았습니다.
1. 기존 CI/CD 도구(Jenkins)의 한계
전통적인 Jenkins 기반 CI는 주로 Post-merge(병합 후 검증) 방식으로 동작합니다.
즉, 코드가 main 브랜치에 병합된 이후 테스트가 수행됩니다.
문제 상황
- 개발자 A와 B가 각각 테스트를 통과함
- 두 패치가 함께 존재할 때 충돌이 발생함
main브랜치가 **Broken State(빌드 오류 상태)**가 됨
결과
- 빌드 복구 전까지 전체 팀 생산성 저하됨
- 병합 이후 문제 발견 → 롤백 비용 증가함
2. Zuul의 추측 기반 병합
Zuul은 Pre-merge Gating 모델을 사용합니다.
변경 사항이 실제 병합되기 전에 모든 검증을 완료해야 합니다.
핵심 아이디어
- 대기열(queue)에 있는 패치들이 이미 병합되었다고 가정함
- 미래의 가상 상태를 시뮬레이션하여 테스트 수행함
동작 예시
- A 패치 테스트 수행
- B 패치 테스트 시 → A가 이미 병합되었다고 가정함
- C 패치 테스트 시 → A + B 병합 상태 가정함
내부적으로는 다음과 같은 가상 병합을 수행합니다:
git checkout main
git merge A
git merge B
이 방식이 바로 추측 기반 병합입니다.
3. Dependent(의존형) vs Independent(독립형) Pipeline
Independent Pipeline (독립형 파이프라인)
- 각 변경 사항을 독립적인 워크 스페이스에서 테스트함
- 다른 패치와 상호 영향을 주고 받지 않음
Dependent Pipeline (의존형 파이프라인)
- 변경 사항을 큐에 묶어 순차적으로 추측 기반 병합함
- 대규모 협업 환경에서 프로젝트 간 영향을 파악하기에 적합함
4. 교차 프로젝트 의존성
여러 프로젝트가 서로 의존하는 경우, 단일 프로젝트 테스트만으로는 안정성을 보장할 수 없습니다.
예를 들어, 라이브러리와 애플리케이션이 분리된 구조에서는 다음과 같은 문제가 발생할 수 있습니다.
- 라이브러리 변경 사항은 테스트를 통과함
- 애플리케이션도 기존 라이브러리 버전 기준으로는 정상 동작함
- 그러나 두 변경 사항이 함께 존재하면 오류 발생
Zuul은 다음 두 가지 방식으로 이를 해결합니다:
required-projects: 정적(구조적) 의존성Depends-On: 동적(변경 단위) 의존성
두 기능은 목적과 동작 범위가 다릅니다.
5. required-projects 설정 예제
이제 교차 프로젝트 테스트를 가능하게 만드는 실제 설정을 살펴보겠습니다.
.zuul.yaml
- job:
name: tox-demorepo
description: tox test for demo-repo with dependencies
parent: tox-py27
required-projects: # required-projects
- demo-repo
- demo-lib
vars:
zuul_work_dir: "{{ zuul.projects\['sftests.com/demo-repo'\].src_dir }}"
- project:
check:
jobs:
- tox-demorepo
- tox-pep8
gate:
jobs:
- tox-demorepo
- tox-pep8
설명
demo-lib와demo-repo를 동일 워크스페이스에 체크아웃함demo-lib변경 사항이demo-repo테스트에 즉각 반영됨- 라이브러리 변경이 애플리케이션에 어떤 영향을 미치는지 병합 전에 확인함
즉, required-projects는 “어떤 프로젝트를 함께 테스트할 것인가” 를 정의합니다.
6. 실습 예제 구성: 왜 의존성 관리가 필요한가?
구조를 단순화한 예제를 통해 동작 방식을 살펴보겠습니다.
demo-lib (라이브러리)
# demo_lib/math_utils.py
def add(a, b):
return a + b
demo-repo (애플리케이션)
# app.py
from demo_lib.math_utils import add
def calculate():
return add(3, 4)
7. 실패 시나리오
그렇다면 required-projects 설정이 없다면 어떤 문제가 발생할까요?
demo-lib 변경
def add(a, b):
return str(a + b)
이 경우 라이브러리 단독 테스트는 통과할 수 있습니다.
demo-repo 코드
그러나 demo-repo에서는:
result = add(3, 4)
print(result + 1) # 문자열과 숫자의 더하기 연산 발생
결과
TypeError가 발생함
이 경우 다음과 같은 상황이 벌어집니다:
- 라이브러리 테스트는 통과함
- 애플리케이션 테스트도 (기존 라이브러리 기준으로는) 통과함
- 하지만 실제 병합되어 함께 배포되면 장애가 발생함
required-projects가 있다면, 라이브러리 변경을 적용한 상태로 애플리케이션 테스트를 먼저 수행하여 병합 전에 문제를 차단합니다.
8. Depends-On 사용 예제
이번에는 라이브러리와 애플리케이션을 동시에 수정하는 상황입니다.
상황
demo-lib에multiply함수를 새로 추가함demo-repo에서 해당 함수 사용하도록 수정함- 두 변경 모두 아직
main에 병합되지 않음
이때 단순히 프로젝트를 묶어주는 required-projects만으로는 부족합니다.
그 이유는 다음과 같습니다:
- 라이브러리의 특정 패치와
- 애플리케이션의 특정 패치를
- 하나의 논리적 변경 세트로 묶어서 검증해야 하기 때문입니다.
demo-repo 커밋 메시지
Depends-On: https://review.example.com/c/demo-lib/+/12345
Zuul 동작
demo-lib의 해당 패치(12345)를 추측 기반 병합함- 그 위에
demo-repo변경 사항 추측 기반 병합함 - 통합 테스트를 실행함
즉, Depends-On은 “이번 변경이 어떤 변경 위에서 동작해야 하는지”를 선언합니다.
정리
required-projects는 프로젝트 단위Depends-On은 변경 사항 단위- 전자는 구조적 의존성
- 후자는 일시적, 동적 의존성
한 문장으로 정리하면:
required-projects는 프로젝트를 묶고,
Depends-On은 변경 사항을 묶습니다.
9. 결론
추측 기반 병합은 단순한 가상 병합이 아닙니다.
- 병합될 가능성이 있는 미래 상태를 먼저 검증함
- 프로젝트 간 의존성을 사전에 확인함
- 변경 사항 간의 논리적 관계까지 추적함
이를 통해 main 브랜치는 항상 배포 가능한 상태를 유지할 수 있습니다.
대규모 협업 환경, 특히 멀티 레포 구조에서는
required-projects로 구조를 묶고,
Depends-On으로 변경을 묶는 전략 이 필수적입니다.
핵심 키워드
- Speculative Merge
- Dependent Pipeline
- Gate
- required-projects
- Depends-On



