트렁크 기반 개발이란? 하루 한 번 main에 합치기

요약

트렁크 기반 개발은 모든 개발자가 작은 변경을 하나의 공유 브랜치, 보통 main 또는 trunk에 하루에 한 번 이상 합치는 방식이에요. 브랜치는 몇 주가 아니라 몇 시간만 살아 있어야 하고, 끝나지 않은 기능은 피처 플래그 뒤에 숨겨 둡니다. DORA의 State of DevOps 연구는 이 습관을 더 빠른 배포와 더 높은 안정성, 가용성과 연결해요.

짙은 책상과 모니터에 빛나는 세로 선 하나, 공유 trunk 하나를 상징하는 장면

트렁크 기반 개발이란 모든 사람이 작은 변경을 하나의 공유 브랜치, 보통 main이나 trunk에 하루에 한 번 이상 합치는 브랜치 전략이에요. 브랜치를 쓴다면 며칠이 아니라 몇 시간만 살아 있어야 해요. 끝나지 않은 작업은 피처 플래그 뒤에 숨겨서 trunk를 어느 커밋에서든 배포 가능한 상태로 유지합니다. 이름은 거창하지만 개념은 생각보다 단순해요.

예를 들어 3주 동안 방치했던 사이드 프로젝트를 다시 열었다고 해볼게요. 기능 브랜치는 main보다 커밋이 41개 뒤처져 있고, 파일 두 개에서 충돌이 나고, 만들던 기능은 주변 코드와 더 이상 맞지 않아요. 트렁크 기반 개발은 이 장면이 생기지 않도록 막아 주는 습관이에요.

트렁크 기반 개발이 실제로 뜻하는 것

trunkbaseddevelopment.com 참고 사이트는 개발자들이 하나의 공유 브랜치에서 협업하고, 그 밖의 장기 개발 브랜치는 피하는 소스 관리 모델이라고 설명해요. 아주 작은 팀이라면 trunk에 직접 커밋해도 괜찮아요. 짧게 살고 한 사람의 작업에서 나온 피처 브랜치라면 풀 리퀘스트도 괜찮습니다.

핵심 규칙은 세 가지예요.

빠진 것도 눈여겨보세요. develop 브랜치도, 기능마다 따로 두는 긴 릴리스 브랜치도, 마지막에 모든 코드가 맞는지 확인하는 통합 단계도 없어요. 예전 모델에서 실제로 아픈 부분은 고통을 미뤄 두었다가 한꺼번에 치르는 데 있어요.

가지가 몇 개 없는 굵은 줄기 하나짜리 큰 나무

긴 브랜치보다 작은 팀에 유리한 이유

긴 브랜치는 일종의 대출이에요. 브랜치가 살아 있는 날마다 main에서 조금씩 멀어지고, 그 이자는 머지 충돌, 낡은 가정, 60개 파일을 건드려서 아무도 끝내지 못하는 리뷰로 돌아와요.

DORA의 트렁크 기반 개발 역량 페이지는 State of DevOps 연구를 바탕으로 이 실천법을 더 빠른 배포, 안정성, 가용성과 연결해요. 활성 브랜치는 3개 이하로, trunk에는 하루에 한 번 이상 합치고, 코드 프리즈와 통합 단계는 피하라는 식이에요. 측정할 수 있는 목표가 있다는 점이 이 분야에서는 드물어요.

혼자 개발하거나 세 명 정도가 일한다면 논리는 더 단순해요. 작은 머지는 리뷰하기 쉽고, 되돌리기 쉽고, 여섯 달 뒤에 git blame을 열었을 때 과거의 내가 무슨 생각이었는지 이해하기도 쉬워요.

이유는 세 가지이고, 하지 않을 이유는 하나예요. 이유는 더 작은 diff, CI에서 더 빠른 피드백, 금요일에도 부담 없이 배포할 수 있는 main이에요. 하지 않을 이유는 테스트가 얇다면 매일 합치는 것이 문제를 더 빨리 퍼뜨린다는 점이에요. 이건 아래에서 다시 다룰게요.

짧은 브랜치를 쓸까, main에 바로 커밋할까?

둘 다 트렁크 기반 개발이에요. 차이는 코드가 들어가기 전에 얼마나 많은 안전장치를 원하느냐예요.

혼자이거나 둘이 일하고, 로컬 빌드가 1분 안에 끝나고, 테스트를 믿을 수 있다면 main에 바로 커밋하는 방식이 잘 맞아요. 가장 빠른 루프죠. 많은 1인 빌더가 이름도 붙이지 않고 이미 이렇게 하고 있어요.

두 번째 사람이나 세 번째 사람이 합류하거나, CI가 머지 전에 깨끗한 체크아웃에서 돌아가길 원한다면 짧은 브랜치와 풀 리퀘스트가 맞아요. 핵심은 짧다는 단어에 있어요. 다음 날까지 살아남은 브랜치는 이미 여러분이 떠나려는 모델을 향해 가고 있는 거예요.

실제로 잘 돌아가는 루틴은 이래요.

하루 안에 끝낼 수 없는 변경이라면 답은 더 긴 브랜치가 아니에요. 변경을 더 얇게 자르는 것이 답이에요.

끝나지 않은 작업을 main을 깨지 않고 배포하는 법은?

이 질문에서 대부분 멈춰요. 매일 합친다면 아직 존재하지 않는 기능의 코드도 합치게 될 거예요. 이를 처리하는 방법은 두 가지예요.

첫 번째는 피처 플래그예요. 새 코드 경로는 합치되 꺼 둬서 사용자에게는 보이지 않게 해요. 환경 변수 하나의 boolean만으로도 충분해요.

const showNewCheckout = process.env.FEATURE_NEW_CHECKOUT === "true";

export function Checkout() {
  return showNewCheckout ? <NewCheckout /> : <OldCheckout />;
}

사이드 프로젝트라면 이것으로 충분해요. 대상 사용자 지정, 점진적 롤아웃, 재배포 없이 끄는 킬 스위치가 필요해지면 관리형 서비스가 값을 해요.

두 번째는 branch by abstraction이에요. DB 클라이언트나 결제 제공자를 교체하는 것처럼 큰 변경에 써요. 기존 구현 앞에 인터페이스를 두고, 같은 인터페이스 뒤에 새 구현을 만든 다음, 호출하는 곳을 조금씩 옮기고, 마지막에 옛 구현을 지워요. 모든 단계가 trunk로 가는 작은 머지이고, 어떤 것도 긴 브랜치에 머물지 않아요.

켜짐과 꺼짐이 섞인 물리 토글 스위치 패널, 피처 플래그를 닮은 모습

플래그에 대해서는 솔직해야 해요. 플래그는 부채예요. 1년 동안 코드에 남아 있는 플래그는 위장한 브랜치예요. 각 플래그에 담당자와 제거 날짜를 정해 두고, 기능이 완전히 롤아웃되면 지우세요.

이걸 시작하기 전에 CI에 필요한 것

안전망 없는 트렁크 기반 개발은 그냥 빠른 파손이에요. 매일 머지하는 습관을 들이기 전에 세 가지가 필요해요.

정적 분석은 값싼 추가 안전장치예요. 사람이 diff를 읽기 전에 명백한 버그, 보안 냄새, 중복 코드 같은 문제를 잡아내요.

리뷰어가 본인 한 명뿐이라면 위험한 핫스팟을 짚어 주는 도구가 더 큰 도움이 돼요. 계속 건드리는 파일이 점점 엉망이 되고 있다는 사실을 아무도 알려주지 않을 테니까요.

첫날부터 무거운 파이프라인이 필요하다고 생각하지 마세요. main을 설치하고, 린트하고, 테스트하고, 배포하는 워크플로 파일 하나면 충분한 출발점이에요. 완벽하지는 않지만 해 볼 가치는 있어요. 처음으로 동작하는 버전이고, 나중에 다듬으면 돼요.

릴리스 브랜치, 핫픽스, 여전히 신경 써야 할 것들

트렁크 기반 개발은 릴리스 브랜치를 금지하지 않아요. 어떤 팀은 릴리스 직전에 trunk에서 브랜치를 따서 다듬고, 배포하고, 지워요. 다른 팀은 trunk에서 바로 배포하고 버그가 나면 앞으로 고쳐요(fix forward). 일주일에 여러 번 배포하는 웹 앱이라면 앞으로 고치는 쪽이 거의 항상 더 단순해요.

모바일 앱이나 스토어 심사가 느린 제품은 이야기가 달라요. 이미 사용자 손에 있는 버전을 패치해야 하니 버전마다 짧게 살아 있는 릴리스 브랜치를 둘 수도 있어요. 그래도 규칙은 같아요. 수정은 항상 trunk에 먼저 들어가고, 그다음 cherry-pick으로 옮겨요. 반대 방향은 안 돼요.

핫픽스도 같은 논리를 따라요. trunk에서 고치고, 배포하면 끝이에요. 다시 합쳐지지 않는 별도 핫픽스 브랜치는 결국 두 개의 갈라진 히스토리를 만들어요.

어두운 코드 에디터가 켜진 노트북, 밤의 카페 테이블 위

현실적인 첫 주

1일차에는 지금 저장소가 어떻게 브랜치를 쓰는지 확인해요. 열려 있는 브랜치를 모두 나열하고 각각의 나이를 적어 보세요. 며칠보다 오래된 것은 머지하거나 지울 후보예요. 이것만으로도 마찰이 어디서 생기는지 자주 드러나요.

2일차에는 플래그 메커니즘을 추가해요. 환경 변수 하나라도 괜찮아요. 진행 중인 기능 하나를 골라 진입점을 플래그 뒤에 두고, 지금까지 한 작업을 머지하세요.

3일차부터 5일차까지는 습관을 바꿔요. 작업을 하루 안에 들어갈 조각으로 자르고, CI가 통과하면 조각을 하나씩 머지해요. 처음 조각들은 너무 작게 느껴질 거예요. 그래도 괜찮아요. 아직 쓰이지 않는 DB 컬럼 하나나 호출되지 않는 함수 하나도 충분히 가치 있는 진전이에요. 안전하고, 리뷰되었고, 이미 통합되어 있으니까요.

주가 끝나면 두 가지를 알게 돼요. 테스트를 믿을 만한지, 그리고 코드의 어떤 부분이 작은 변경을 거부하는지예요. 둘 다 유용하고, 긴 브랜치에서 작업하는 동안에는 보이지 않아요.

이걸 알아내기 위해 대시보드가 필요하지는 않아요. 브랜치가 얼마나 오래 살아 있는지, main이 얼마나 자주 빨갛게 깨지는지, 첫 커밋부터 운영 배포까지 변경이 얼마나 걸리는지 보면 돼요. 브랜치 수명이 줄고 배포가 지루해지고 있다면 습관이 효과를 내는 거예요.

플래그 뒤에 숨긴 기능이 실제 사용자에게 어떤 영향을 주는지 숫자로 보고 싶다면, 실험 도구로 롤아웃을 실제 지표와 연결할 수 있어요.

혼자 일한다면 평범한 노트도 충분해요. 한 달 동안 매주 첫 커밋부터 배포까지 걸린 시간을 적어 보세요. 추세는 어떤 단일 숫자보다 많은 것을 알려줘요.

지금 바꿀지, 기다릴지

혼자, 둘이, 또는 테스트를 반쯤만 믿는 작은 팀이면서 개선할 여지가 있는 CI가 있다면 바꾸세요. 시도하는 비용은 일주일의 약간 불편한 작은 커밋이에요. 긴 브랜치에 머무는 비용은 충돌과 느린 리뷰로 치러지고, 협업자가 늘어날수록 커져요.

자동화된 테스트가 전혀 없고 되돌릴 방법도 없다면 기다리세요. 그 전에 그것부터 고치세요. 테스트 없는 trunk에 매일 머지하는 것은 고통의 위치만 옮길 뿐이에요.

완벽한 방법은 아니에요. 다만 실용적인 방법이에요. 지금 프로젝트에서 다음 작은 변경을 하나 고르고, 오늘 머지해서 어떤 느낌인지 확인해 보세요. 오늘 배포하기 전에 현재 기능에서 출시할 수 있는 가장 작은 조각은 무엇인가요?

자주 묻는 질문

트렁크 기반 개발과 GitFlow의 차이는 무엇인가요?
GitFlow는 develop, feature, release, hotfix 같은 여러 장기 브랜치를 두는 모델이에요. 트렁크 기반 개발은 공유 브랜치 하나에 짧게 사는 브랜치만 쓰고, 미완성 작업은 피처 플래그로 숨겨요. 일주일에 여러 번 배포하는 웹 앱에는 보통 후자가 더 단순해요.
하루에 한 번 합치기가 현실적이지 않다면 어떻게 하나요?
변경을 더 작은 조각으로 나누세요. 하루 안에 끝나지 않는 작업이라면 호출되지 않는 코드나 꺼진 플래그 뒤의 코드부터 먼저 합칠 수 있어요.
피처 플래그는 언제 제거해야 하나요?
기능이 모든 사용자에게 완전히 롤아웃되면 바로 지우세요. 각 플래그에 담당자와 제거 날짜를 적어 두면 잊히지 않아요.
혼자 개발해도 트렁크 기반 개발을 쓸 가치가 있나요?
네. 혼자 일할 때는 리뷰어가 없으니 작은 머지가 특히 유용해요. 다만 자동 테스트와 빠른 롤백 수단이 먼저 있어야 해요.
릴리스 브랜치를 완전히 없애야 하나요?
꼭 그럴 필요는 없어요. 스토어 심사가 느린 모바일 앱처럼 이미 배포된 버전을 패치해야 한다면 짧게 살아 있는 릴리스 브랜치를 둘 수 있어요. 단, 수정은 항상 trunk에 먼저 들어가야 해요.
fix forward는 무슨 뜻인가요?
배포된 버그를 이전 버전으로 되돌리는 대신, trunk에서 새 수정 커밋을 만들어 바로 다음 배포로 고치는 방식이에요.