사전 트렁크 기반 개발
패턴

트렁크 기반 개발

gabury1고친 사람 github-actions[bot]

트렁크 기반 개발은 팀이 코드를 합치는 곳을 트렁크라는 기준 브랜치 하나로 모읍니다. 각자 고친 것을 하루 안팎마다 그 브랜치에 밀어 넣습니다. 오래 사는 브랜치는 일부러 만들지 않습니다. 덜 끝난 기능은 스위치 하나로 꺼 둔 채 합쳐 넣습니다.

쉽고 빠른 이해

트렁크 기반 개발은 팀 전체가 한 브랜치에 모여 일합니다. 로그인 화면을 고치는 사람도 결제를 고치는 사람도 아침에 브랜치를 내고 그날 안에 트렁크로 되돌립니다.

브랜치를 오래 끌면 그사이 트렁크가 저 혼자 앞서갑니다. 두 주 뒤에 합치려고 보면 남이 고쳐 둔 코드와 부딪히고, 그 충돌을 푸는 데 며칠이 듭니다. 떨어져 있는 시간을 줄이면 부딪힐 것도 줄어듭니다.

  1. 브랜치를 내더라도 하루 안팎만 쓰고 트렁크로 되돌립니다
  2. 합칠 때마다 빌드와 테스트가 자동으로 돌아 트렁크를 지킵니다
  3. 덜 끝난 기능은 스위치로 꺼 둔 채 합쳐 둡니다

대가는 규율이 늘 필요하다는 것입니다. 트렁크가 깨지면 그 위에서 일하던 팀 전원이 함께 멈춥니다. 리뷰와 테스트가 몇 시간 안에 안 끝나면 이렇게 일하는 것 자체가 안 굴러갑니다.

상세

트렁크라는 한 줄기

트렁크는 저장소에서 모두가 기준으로 삼는 한 줄기입니다. 나무의 본줄기에 빗댄 이름입니다. 요즘 도구에서는 main 이나 master 로 있습니다. 브랜치는 그 줄기에서 갈라져 나온 다른 줄기를 말합니다.

트렁크 기반 개발은 이 트렁크 하나를 팀의 유일한 통합 지점으로 둡니다. 모든 변경이 결국 트렁크를 지나갑니다. 내보내는 판(릴리스)도 트렁크에서 나옵니다. 트렁크 말고 오래 사는 브랜치를 두지 않는 것이 트렁크 기반 개발이 내건 약속입니다.

오래 사는 브랜치가 비싸지는 까닭

브랜치를 따로 내면 그 순간부터 내 코드와 트렁크가 서로 다른 길을 갑니다. 내가 고치는 동안 남들도 트렁크를 고칩니다. 둘이 떨어져 있는 시간이 길수록 서로 모르는 변경이 쌓입니다.

합칠 때는 그 쌓인 차이를 한꺼번에 풀어야 합니다. 같은 파일의 같은 줄을 양쪽이 고쳤다면 병합 충돌이 납니다. 풀어야 할 충돌은 브랜치가 산 기간을 따라 늘어납니다. 두 주짜리 브랜치는 하루짜리 브랜치보다 훨씬 손이 많이 갑니다.

사흘을 끈 브랜치를 그려 보면 그 쌓임이 눈에 보입니다.

flowchart TD
    T1["트렁크 · 월요일"] --> T2["트렁크 · 화요일"]
    T2 --> T3["트렁크 · 수요일"]
    T1 -->|낸다| L1["긴 브랜치 · 첫날"]
    L1 --> L2["긴 브랜치 · 이튿날"]
    L2 --> L3["긴 브랜치 · 사흗날"]
    L3 -->|사흘치 차이를 한꺼번에 합친다| T3

트렁크는 그사이에도 화요일치와 수요일치 변경을 받습니다. 긴 브랜치는 그것을 모르는 채 자기 커밋만 쌓습니다. 마지막 화살표 하나가 그 사흘치 차이를 통째로 떠안습니다.

충돌이 안 났는데 깨지는 경우도 있습니다. 내가 부르던 함수의 뜻이 그사이 바뀌었다면 파일은 깨끗이 합쳐지고 동작만 어긋납니다. 이런 어긋남은 합치기 전에는 안 보입니다.

하루 안팎의 짧은 브랜치

트렁크에 바로 커밋하는 팀도 있습니다. 대개는 짧은 브랜치를 하나 내서 리뷰와 자동 검사를 거친 뒤 합칩니다. 이 브랜치의 수명은 하루 안팎입니다. 그보다 오래 걸릴 일은 애초에 더 작게 쪼갭니다.

아래는 그 한 바퀴를 명령으로 적은 것입니다.

터미널
git switch -c fix-login trunk   # 아침에 낸다
git commit -am "로그인 오류 고침"
git switch trunk
git merge fix-login             # 그날 안에 합친다
git branch -d fix-login         # 바로 지운다

브랜치를 내고 지우는 사이에 하루가 채 안 지납니다. 풀 리퀘스트를 쓰는 팀이라면 이 사이에 리뷰가 들어갑니다. 리뷰가 하루 안에 끝나야 이 주기가 돌아갑니다. 리뷰가 사흘씩 밀리는 팀에서는 브랜치가 저절로 길어집니다.

flowchart TD
    T1["트렁크 · 월요일"] --> T2["트렁크 · 화요일"]
    T2 --> T3["트렁크 · 수요일"]
    T1 -->|낸다| A["브랜치 A · 로그인"]
    A -->|합친다| T2
    T2 -->|낸다| B["브랜치 B · 결제"]
    B -->|합친다| T3

위아래로 곧게 이어지는 줄기가 트렁크입니다. 라벨이 붙은 화살표는 브랜치를 내고 합치는 동작입니다. 라벨이 없는 화살표는 그저 하루가 지난 것입니다. 앞 절의 긴 브랜치와 견주면 합류 화살표가 떠안는 차이가 하루치뿐입니다.

덜 끝난 기능을 트렁크에 넣는 법

바로 막히는 질문이 있습니다. 두 주 걸리는 기능을 어떻게 하루 만에 합치나. 답은 기능을 다 만들고 합치는 것이 아니라, 덜 끝난 상태로 합치되 사용자에게 안 보이게 두는 것입니다.

만들려는 상태는 하나입니다. 코드는 트렁크에 들어가 있는데 사용자는 아직 못 본다는 상태입니다. 그 상태에 이르는 수단이 셋 있고, 앞에서 스위치라고 부른 것이 그중 첫째인 기능 플래그입니다.

수단 무엇으로 감추나
기능 플래그 조건문 하나로 새 코드와 옛 코드를 가른다. 스위치 값은 코드 밖에 둔다
브랜치 바이 앱스트랙션 옛 구현과 새 구현 사이에 층을 하나 끼우고 그 뒤에서 갈아 끼운다
키스톤 인터페이스 화면에서 부르는 곳을 맨 마지막에 붙인다. 그 전까지 코드는 있어도 닿을 길이 없다

셋 중 무엇을 쓰든 코드가 밟는 순서는 같습니다.

stateDiagram-v2
    A: 트렁크에 들어갔으나 꺼져 있다
    B: 켜져서 사용자에게 열린다
    C: 감추는 장치를 걷어 낸다
    A --> B : 스위치를 켠다
    B --> C : 옛 갈래를 지운다

가운데 화살표가 기능을 여는 때입니다. 트렁크에 합치는 때는 그보다 앞인 첫 칸입니다. 이렇게 둘을 갈라 두어야 합치는 일과 기능을 여는 일을 따로 정할 수 있습니다.

늘 내보낼 수 있는 트렁크

전원이 한 브랜치에 모이면 그 브랜치가 깨졌을 때 전원이 멈춥니다. 그래서 트렁크는 언제나 빌드되고 테스트를 지나가는 상태여야 합니다.

합치기 전에 자동 검사를 돌리는 것이 기본입니다. 합칠 코드로 빌드와 테스트를 먼저 돌려 보고, 지나간 것만 트렁크에 들어갑니다. 합칠 때마다 이 검사를 거는 규율이 지속적 통합입니다.

검사는 사람이 앉아서 기다릴 만한 시간 안에 끝나야 합니다. 검사가 한 시간 걸리면 하루에 여러 번 합치는 일이 안 됩니다. 그래서 이렇게 일하는 팀은 검사 시간을 계속 줄입니다.

깨진 것이 트렁크에 들어갔다면 되돌리는 일이 먼저입니다. 원인을 찾는 동안에도 다른 사람들이 그 위에 계속 쌓기 때문입니다. 제대로 고쳐 넣는 것은 트렁크가 다시 돈 다음 일입니다.

합칠 코드가 밟는 길을 한 장으로 적으면 이렇습니다.

flowchart TD
    M["합칠 코드"] --> CI["자동 검사"]
    CI -->|지나감| TR["트렁크"]
    CI -->|깨짐| X["못 들어간다"]
    TR -->|그래도 깨졌으면| RB["되돌린다"]
    RB --> CI

되돌린 코드는 고친 뒤 다시 자동 검사부터 밟습니다. 트렁크로 바로 들어가는 길은 없습니다.

트렁크에서 잘라 내는 릴리스 판

내보낼 때가 되면 두 방식 중 하나를 씁니다. 하나는 트렁크에서 릴리스 브랜치를 그때 잘라 내는 것이고, 다른 하나는 트렁크 자체를 그대로 내보내는 것입니다.

flowchart TD
    T["트렁크"] -->|내보낼 때 잘라 낸다| R["릴리스 브랜치"]
    R --> F["버그 고침"]
    F --> S["출시"]
    F -->|같은 고침을 되돌린다| T

릴리스 브랜치는 새 기능을 더 받지 않고 버그만 고칩니다. 거기서 고친 것은 반드시 트렁크에도 넣습니다. 안 넣으면 다음 판에서 같은 버그가 되살아납니다. 이 브랜치는 내보내고 나면 지우므로 오래 사는 브랜치가 아닙니다.

트렁크 자체를 내보내는 팀은 문제가 났을 때 되돌리는 대신 앞으로 고쳐 나갑니다. 고친 것을 다시 트렁크에서 내보내는 식입니다. 이렇게 하려면 배포가 하루에도 여러 번 돌 만큼 짧아야 합니다.

값을 치르는 곳

트렁크 기반 개발은 공짜가 아닙니다. 먼저 규율이 늘 필요합니다. 한 사람이 깨진 코드를 밀어 넣으면 트렁크 위에 선 모두가 영향을 받습니다. 그래서 합치기 전에 검사를 돌리는 습관이 팀 전체에 있어야 합니다.

일을 작게 쪼개는 것도 따로 배워야 합니다. 두 주짜리 기능을 하루짜리 조각으로 가르는 일은 저절로 되지 않습니다. 화면과 저장 구조와 업무 규칙이 한 덩어리로 묶인 코드에서는 쪼갤 선이 잘 안 보입니다.

감추는 장치가 쌓이면 그 자체가 짐이 됩니다. 플래그를 심어 두고 안 걷으면 코드에 조건문만 남습니다. 거쳐 갈 수 있는 경로가 불어나 테스트할 조합이 늘어납니다.

리뷰가 병목이 되기도 합니다. 하루 안에 합치려면 리뷰도 하루 안에 끝나야 합니다. 읽어 줄 사람이 몇 없는 팀에서는 이 요구가 그 몇 사람에게 몰립니다.

이 방식이 안 맞는 곳

가르는 기준은 트렁크 하나를 늘 지킬 수 있느냐입니다. 아래 셋은 그 기준이 안 서는 경우입니다. 트렁크를 아무에게나 열 수 없거나, 트렁크 말고 오래 사는 줄기가 따로 필요하거나, 깨진 것을 걸러 낼 수단이 없습니다.

  • 누구나 고쳐 보낼 수 있는 오픈소스 프로젝트입니다. 낯선 사람의 변경을 바로 트렁크에 넣을 수 없습니다. 받는 쪽이 검토해 들이는 포크 흐름이 맞습니다
  • 옛 판을 여러 개 동시에 떠받쳐야 하는 제품입니다. 판마다 유지보수 브랜치가 반년씩 살면서 트렁크와 갈라집니다. 한 번 고친 것을 트렁크와 그 브랜치들에 따로따로 넣어야 합니다
  • 자동 테스트가 거의 없는 코드베이스입니다. 합치기 전에 걸러 낼 수단이 없어 트렁크가 늘 깨져 있게 됩니다. 테스트를 먼저 깔지 않고 들이면 팀 전체가 멈추는 일이 잦아집니다

관련 항목

트렁크 기반 개발과 나란히 놓이는 브랜치 운영 흐름

브랜치 전략 · 기능 브랜치 · Git 플로우 · 깃허브 플로우 · 릴리스 브랜치 · 핫픽스 브랜치

트렁크 기반 개발이 딛고 서는 개발 습관

지속적 통합 · 지속적 전달 · 지속적 배포 · 코드 리뷰 · 자동 테스트 · 빌드 자동화

덜 끝난 코드를 감추려고 같이 쓰는 수단

기능 플래그 · 브랜치 바이 앱스트랙션 · 키스톤 인터페이스 · 다크 런칭 · 병렬 변경

트렁크에 합칠 때 밟는 동작

병합 · 리베이스 · 스쿼시 병합 · 풀 리퀘스트 · 머지 큐 · 빨리 감기

트렁크를 가리키는 다른 이름

트렁크 · 메인라인 · 기본 브랜치 · 통합 브랜치

짧게 자주 합쳐서 줄이려는 문제

병합 충돌 · 장수 브랜치 · 통합 지옥 · 기술 부채 · 죽은 코드

이 방식이 움직이는 배포 지표

배포 빈도 · 변경 실패율 · 리드 타임 · 배치 크기 · 서비스 복구 시간

트렁크 기반 개발이 속하는 상위 분류

버전관리 · 소스 코드 관리 · 릴리스 엔지니어링 · 데브옵스 · 배포 파이프라인

다른 이름: trunk-based development · trunk based development · TBD · 트렁크기반개발