Git Flow에서 트렁크 기반 개발으로 나아가기

안녕하세요, 맘편한세상의 백엔드 엔지니어 이재찬입니다.

맘편한세상의 백엔드 팀은 최근 1년 동안 Git Flow의 대안으로서 트렁크 기반 개발을 활용해 왔습니다. 이 글에서는 두 브랜치 전략의 이론적 사용법에 대해서, 그리고 실제로 사용해오면서 느낀 점들을 공유해보고자 합니다.

Git Flow

Git Flow는 Vincent Driessen이 2010년에 제시한 Git 브랜치 전략으로 널리 알려져 있습니다.

A successful Git branching model - Vincent Driessen

image Git Flow를 설명하는 그래프

Git Flow 전략의 개요를 간단히 한번 짚고 넘어가 보겠습니다. Git Flow의 핵심은 masterdevelop 두 개의 주 브랜치입니다. master 브랜치에 있는 작업은 운영환경에 배포될 수 있는 것, develop 브랜치에 있는 작업은 다음 릴리즈를 위해 준비된 것입니다. 이 외에도 feature, release, hotfix 이 세 가지 보조 브랜치가 존재합니다.

  • feature: 새로운 기능을 develop 브랜치 위에서 개발하기 위해 사용
  • release: develop의 변경사항을 운영환경에 배포하기 위해 사용
  • hotfix: 운영환경에 시급하게 수정되어야 할 이슈가 있을 때, master를 빠르게 변경하기 위해 사용

Git Flow의 어려움

Vincent Driessen이 2010년에 Git Flow를 처음 제시했던 블로그 글을 보면, 그가 글을 작성한 지 10년 뒤인 2020년에 추가한 Note Of Reflection 을 확인할 수 있습니다.

이 부분에서는 Git Flow가 웹 앱(web apps) 개발을 염두에 두지 않고 만들어졌고, 적합하지 않다고 회고합니다. 즉 그의 말에 따르면 우리가 일반적으로 작성하는 백엔드, 프론트 애플리케이션은 Git Flow를 사용하기 적합하지 않다는 것이죠.

image

Git Flow의 어려움

그렇다면 어떤 이유에서 Git Flow가 웹 앱 개발에 적합하지 않다는 것일까요? 제가 생각하는 Git Flow의 어려움은 아래와 같습니다.

브랜치 관리 규약이 복잡하다

A successful Git branching model 에서는 feature, release, hotfix 세 가지의 보조 브랜치가 어디서 생성될 수 있으며, 어디로 머지(Merge)되어야 하는지, 그리고 어떤 이름을 가질 수 있는지 상세하게 설명합니다. 이러한 규약을 익히는 것도 어려운 점 중 하나이지만, 규약으로 인해 브랜치 관리의 본질적인 복잡성이 증가하는 것 또한 문제가 됩니다. 이러한 복잡성을 보여주는 예시를 간단히 짚어보자면 아래와 같습니다.

  • releasehotfix 브랜치를 머지하기 복잡합니다. 기본적으로 두 종류의 브랜치는 변경 사항이 덮어씌워지는 것을 방지하기 위해 masterdevelop에 동시에 머지되어야 하며, 활성화된 release 브랜치가 있다면 이것도 신경써야 합니다.
  • 새로운 작업을 feature 브랜치와 hotfix 브랜치 중 어떤 것으로 해야 할지 판단하기 어렵습니다. 일반적으로는 계획된 기능을 feature, 계획되지 않은 픽스를 hotfix로 하지만 이런 분류가 애매해지는 순간이 분명 존재합니다.
  • hotfix에서 새로 발생한 변경 사항을 feature 브랜치에 적용하고 싶다면, 최소 3번의 머지가 필요합니다. (hotfix -> master, hotfix -> develop, develop -> feature)

브랜치가 오래 유지된다

Git Flow에서는 브랜치가 보통 오래 유지되고, 상대적으로 많은 변경사항을 한 번에 머지하는 것을 선호합니다. 이러한 선호는 여러가지 부작용을 같이 야기합니다.

먼저, 서로 다른 두 브랜치에서 독립적으로 작업을 하게 되면 동시에 수정한 부분이 생길 수 있는데, 이 때 두 브랜치를 머지하기 위해서는 Git 상에서 컨플릭트를 해결해야 합니다. 두 브랜치가 각각 독립적으로 오래 작업되었을수록, 컨플릭트는 보통 더 크고 해결하기 어려워집니다. 컨플릭트 해결 중 발생하는 휴먼 에러로 버그를 만들 확률 또한, 컨플릭트의 규모가 커질수록 같이 증가합니다.

image 오랫동안 작업한 두 브랜치를 머지하면… 💥

그 뿐만이 아니라, 브랜치가 오래 유지되어서 변경사항이 많을수록, 동료 개발자가 변경사항에 대해 코드리뷰를 진행하기가 더욱 어려워집니다. 동료 개발자가 코드 퀄리티나 버그에 대한 피드백을 주기 어려워지면, 전체적인 개발 문화에도 영향을 주게 됩니다.

image 코드 리뷰의 아이러니

마지막으로, 브랜치가 오래 유지된다는 것은 자연스레 배포의 주기가 길어진다는 것을 의미합니다. 이는 운영 배포를 통해 실제 환경에서 새롭게 만든 코드와 기능에 대한 피드백을 받는 것을 어렵게 합니다.

트렁크 기반 개발으로 해결해보자

트렁크 기반 개발은 Git Flow의 대안으로서 주로 사용되는 브랜치 전략입니다. 트렁크 기반 개발을 소개하는 trunkbaseddevelopment.com에서는 트렁크 기반 개발을 아래와 같이 한 문단으로 설명합니다.

A source-control branching model, where developers collaborate on code in a single branch called ‘trunk’, resist any pressure to create other long-lived development branches by employing documented techniques. They therefore avoid merge hell, do not break the build, and live happily ever after.

일종의 코드 관리 브랜칭 모델이며, 여기서는 개발자들이 ‘트렁크’라고 불리는 단일 브랜치 위에서 협력하고, 오래 유지되는 개발 브랜치를 만들게 하는 압력에 저항하기 위해 위해 설명된 테크닉을 사용합니다. 그러므로 그들은 머지 헬을 피하고, 빌드를 깨트리지 않고, 영원히 행복하게 살아갑니다.

image 트렁크 기반 개발

한 문단만으로는 설명이 쉽지 않네요. 조금 더 알아볼까요?

트렁크 기반 개발의 실천법

트렁크 기반 개발에서는 main(또는 trunk)이라는 주 브랜치 하나만 운영합니다. 신규 피쳐는 main에 바로 커밋하거나, 며칠 내로 main에 머지할 피쳐 브랜치에서 작업합니다.

main 브랜치에 코드가 머지되었다면, 먼저 자동화된 CI 시스템이 main브랜치에 대해 테스트 / 통합 과정을 통과하는지 확인합니다. 문제가 없다면, main 브랜치의 코드가 그 즉시 운영 환경에 배포됩니다.

(이는 큰 틀에서 Github Flow와의 실천법과도 일치하기 때문에, Github Flow 또한 트렁크 기반 개발의 일종이라고 할 수 있겠습니다.)

이러한 간단한 방식은 Git Flow의 많은 문제들을 해결합니다.

  • 브랜치 관리에 드는 리소스가 대폭 절약됩니다. 개발자가 각자 자신이 맡은 피쳐 브랜치를 main 브랜치와 싱크를 맞추는 것만으로도 충분합니다.
  • 며칠 단위로 main에 머지하기 때문에, 머지 시 발생하는 변경이 작아집니다. 따라서 컨플릭트는 보통 작거나 없고, 코드 리뷰도 용이합니다.
  • 배포하기 위해서는 main에 머지하는 것만으로 충분합니다. 배포 프로세스가 간단해져서, 더욱 자주 배포할 수 있게 됩니다.

트렁크 기반 개발의 어려움

이런 간단한 방식으로, 우리는 Git Flow의 어려움을 모두 해결하고 영원히 행복하게 살았답니다. 끝!

…처럼 전래동화 스타일로 끝낼 수 있으면 참 좋았겠지만, 아쉽게도 제 경험상 트렁크 기반 개발 또한 모든 것을 마법처럼 해결하지는 못합니다. 일반적으로 트렁크 기반 개발의 실천법을 실행하다 보면, 아래와 같이 크게 세 가지 상황에서 어려움을 맞닥트립니다.

큰 기능 문제

만들고자 하는 기능이 몇 주 내지는 몇 달의 개발 기간이 필요한 큰 기능일 수 있습니다. 이런 상황에서는 트렁크 기반 개발의 실천법대로 며칠마다 메인 브랜치에 머지하기 어려워집니다.

의존적인 기능 문제

만들고자 하는 기능 A가 다른 기능 B의 릴리즈에 의존적이라면, B 기능이 완료되기 전까지 A 기능도 배포하기 어렵습니다.

불안정한 기능 문제

기능을 별도의 스테이지 서버에서 검증하고 배포하는 것이 아니라 바로 실서버로 배포하기 때문에, 잘못된 기능이 메인 브랜치로 머지될 위험이 증가합니다.

그럼에도 불구하고..

트렁크 기반 개발에도 분명히 어려움이 존재하지만, 위의 어려움을 해결하는 나름의 방법 또한 존재합니다. 아래에서 트렁크 기반 개발을 용이하게 하는 세 가지 방법을 설명드리겠습니다. 이 방법들은 서로 상호 보완적이며, 더 많이, 더 잘 도입할수록 트렁크 기반 개발의 어려움을 더 잘 해소할 수 있습니다.

더 작은 단위의 배포

예를 들어서, 한 달이 걸릴 작업 하나를 일주일에 한번 배포하는 방식으로 쪼개서 배포할 수 있습니다. 물론 작업의 유형에 따라 그렇게 하기 상대적으로 더 어려운 경우도 존재하지만, 그렇게 했을 때 얻을 수 있는 이득 또한 분명합니다.

  • 여러 개의 작은 배포로 큰 배포 하나를 대체함으로서, 큰 기능 문제를 해결할 수 있습니다.
  • 의존적인 기능 문제 또한, 두 기능이 공통적으로 의존하는 부분을 추출해서 먼저 작업해 배포한다면, 두 기능을 실제로 구현하는 것은 그 후에 이어서 독립적으로 진행할 수 있게 됩니다.
  • 한 번의 배포에 추가되는 변경사항의 개수가 작을수록, 오류가 발생할 확률도 적어지고, 오류가 발생했을 때의 디버깅 또한 용이해집니다. 따라서 불안정한 기능 문제 의 영향을 최소화할 수 있습니다.

피쳐 토글

피쳐 토글(Feature Toggle)을 사용한다면, 작성한 코드를 운영 서버에 미리 배포해두고, 이 코드를 실제로 운영 환경에서 실행할지를 원격으로, 별도의 배포 없이 결정할 수 있습니다. 이는 코드의 배치 (deploy)와 기능의 릴리즈 (release)를 분리해서 다룰 수 있게 합니다.

  • 의존적인 기능 문제 가 발생했을 때, 먼저 내가 제어할 수 있는 부분만 작업해서 코드를 배치해 두고, 내가 의존해야 하는 부분이 완료된 후에 피처 토글을 통해 기능을 정식으로 릴리즈하는 방식으로 해결할 수 있습니다.
  • 정확히 구현된 것을 확신하기 어려운 작업 또한, 피쳐 토글로 보호된 상태로 코드만 먼저 배치해 둘 수 있습니다. 그 후 피쳐 토글을 통해 기능을 일시적으로 릴리즈하고, 기능에 문제가 없는 것을 확인한 후 영구적으로 릴리즈하는 방식을 사용할 수 있습니다. 이를 통해 불안정한 기능 문제 를 해결할 수 있습니다.

피쳐 토글에 대해 더 자세히 알고 싶으시다면, 제가 작성한 다른 글인 피쳐 토글 - 빠르고 안정적인 릴리즈를 향한 도약도 확인해 보세요!

테스트 코드와 통합 단계에서의 테스트 자동화

테스트 코드의 중요성은 트렁크 기반 개발에서도 그 빛을 발합니다.

  • 새로운 기능을 위한 테스트 코드를 작성해 두면, 새로운 기능이 의도와 다르게 동작할 확률을 최소화할 수 있습니다. 또한 그 후에 만약 새로운 기능이 추가되어서 기존 기능에 영향을 주더라도, 테스트 코드를 통해서 이를 감지할 수 있습니다. 이는 자연스럽게 불안정한 기능 문제를 해결합니다.

트렁크 기반 개발이 내포하는 근본적인 가치

여기까지가 트렁크 기반 개발에 대한 기본적인 설명이었습니다. 여기까지 읽었을 때 어떤 생각이 드셨나요? 트렁크 기반 개발이 좋아 보여서 적용해보고 싶을 수도 있고, 그래도 설득되지 않아서 Git Flow를 그대로 사용하고 싶을 수도 있습니다.

Git Flow와 트렁크 기반 개발 두 방식 중 어느 하나가 항상 더 좋다고 생각하기 쉽지만, 저는 두 방식 모두 각자의 장점을 취하기 위해 감수하는 단점이 있다고 생각합니다. 아래와 같이 Git Flow와 트렁크 기반 개발이 내포하는 근본적인 가치를 각각 한 문장으로 요약할 수 있습니다.

  • Git Flow는 정교하고 안정적이다
    • 검증 환경과 운영 환경을 명확히 구분합니다.
    • 큰 덩어리의 피쳐를 충분히 검증해 한번에 배포합니다.
  • 트렁크 기반 개발은 간단하고 빠르다
    • 검증 환경을 거치지 않고 실제 환경에 바로 배포합니다.
    • 작은 피쳐 여러개를 각각 최소한으로 검증하고 배포합니다.

즉, 트렁크 기반 개발은 간단하고 빠른 배포를 추구하기 위해 정교하고 안정적인 배포를 포기한 것이죠. 따라서 트렁크 기반 개발이 추구하는 간단하고 빠른 배포가 잘 들어맞는 환경을 가지고 있을 때, 트렁크 기반 개발을 도입하는 것을 적극적으로 고려해보면 좋을 것이라고 생각합니다. 예를 들어서..

  • 애자일 원칙을 따라 지속적으로 작은 변경사항을 고객에게 제공하고 싶을 때
  • 클라우드에 배포되는 웹 앱과 같이, 배포비용이 작고 롤백이 용이할 때
  • 페어 프로그래밍, 코드 리뷰, 테스트 코드와 같이 코드의 품질을 끌어올리는 개발 문화에 익숙할 때

그래도 두려워요

Git Flow에 한계를 느끼고 있고, 트렁크 기반 개발의 가치에 공감할지라도, 아마 트렁크 기반 개발 도입을 주저하게 되는 가장 큰 이유 중에 하나는 “운영배포하는 것이 두려워서” 일 것이라고 생각합니다. 검증도 안 한 기능을 바로 운영에 올리는 것은 당연히 그 위험성이 존재하고, 그게 두려운 건 어떻게 보면 당연한 반응이라고 생각합니다.

운영 배포가 두렵게 느껴질 때는, 마틴 파울러의 FrequencyReducesDifficulty 글을 읽어보는 것이 도움이 되었습니다. 이 글의 첫 문장에서는 아래와 같이 이야기합니다.

if it hurts, do it more often.

어떤 일이 고통스럽다면, 더 자주 해라.

이 글에서는, 통합은 고통스러운 작업이기 때문에 이를 미룰려고 시도하는 것은 사람이라면 당연한 반응이라고 얘기합니다. 하지만 통합은 미루면 미룰수록 그 난이도가 증가하기 때문에, 오히려 자주 통합해서 자주 고통을 맞이한다면 고통의 총합은 자연스레 줄어들 것이라고 이야기합니다.

image 통합이 늦어지면 늦어질수록, 통합의 난이도도 같이 빠르게 증가합니다.

만약 배포하는 것이 너무나 무섭고 고통스러운 일이라서 트렁크 기반 개발을 도입하기에 어려움을 느낀다면, 트렁크 기반 개발을 통해 더 자주, 더 작게 배포해서 배포의 고통을 줄여보는 것은 어떨까요?

맘시터 백엔드팀에서 적용해본 결과

여기서부터는 맘시터 백엔드팀에서 트렁크 기반 개발을 적용해보면서 얻은 성과를 자랑해보고자 합니다.

맘시터 백엔드팀은 트렁크 기반 개발의 실천법을 따라 main 이라는 주 브랜치 하나를 운용하고 있습니다. 새로운 기능을 만들기 위해서는 main에서 피쳐 브랜치를 만들어서 작업을 하고, 며칠 내로 PR(Pull Request)을 올려 코드리뷰를 받습니다.

PR이 올라감과 동시에, Github Action을 통해 PR에 대한 테스트와 코드 품질 분석을 자동으로 실행합니다.

image 테스트와 코드 품질 분석(SonarCloud)가 모두 성공한 모습

테스트가 성공하는 것을 확인 후, PR 작성자는 원하는 시점에 Merge버튼을 눌려 PR을 운영과 스테이지 환경에 동시에 배포할 수 있습니다. 날마다 다르지만, 일반적으로는 하루 평균 5건 내외의 배포가 발생합니다. (백엔드 개발자는 현재 11명입니다.)

image Github Action을 통해 최근 배포된 커밋들의 목록

트렁크 기반 개발을 적용하며, 백엔드팀에서 몸소 느낄 수 있었던 장점은 아래과 같습니다.

  • 다른 사람의 작업이 내 작업의 배포 가능 여부에 영향을 끼치는 경우가 거의 없습니다.
    • Git Flow를 사용할 때는 다른 사람이 작업한 내용이 현재 배포되어도 되는지 등의 커뮤니케이션이 빈번하게 발생했습니다.
    • 트렁크 기반 개발 시스템에서는 다른 사람의 작업을 신경쓰지 않고, 내 작업을 바로 머지해서 배포하여도 다른 사람의 작업물과 컨플릭트가 발생하는 경우가 아주 적었습니다.
    • 컨플릭트가 드물게 발생하더라도, 컨플릭트 범위가 아주 작아 주로 몇 분 내로 간단하게 해소할 수 있었습니다.
  • PR은 일반적으로 300줄 이하의 변경사항을 가져, 팀원들 간에 손쉽게 리뷰할 수 있었습니다.
  • 작업한 내용이 실제 운영환경에 배포되기까지의 평균 리드타임이 매우 짧아졌습니다.
    • 작업 -> PR 작성 -> 테스트 자동으로 실행 -> 테스트 성공 확인 후 머지 -> 배포 모니터링의 과정을 거치면 작업된 사항을 운영환경에서 바로 확인할 수 있는데, 간단한 수정사항이라면 이 모든 과정을 늦어도 30분 내외로 마무리할 수 있고, 일반적으로는 코드 리뷰를 거치고 PR이 올라간 날 오후나 다음날에 배포되는 것이 일반적이었습니다.
    • Git Flow에서도 긴급한 상황에서는 Hotfix 브랜치를 사용해 빠르게 배포할 수 있었겠지만, 차이점이라면 트렁크 기반 개발에서는 어떤 PR이라도 이렇게 빠르게 운영환경에 바로 적용할 수 있다는 점입니다.
  • 더 이상 운영과 스테이지에 어떤 버전이 배포되었는지 트래킹할 필요가 없습니다. 스테이지와 운영의 코드가 달라서 에러를 재현하기 힘든 일도 없고, 배포된 줄 알았던 브랜치가 알고 보니 수동 배포 버튼을 누르지 않아 배포되지 않았다는 걸 발견하는 일도 없습니다. 스테이지나 운영에서 문제가 발생했을 때, 따로 생각할 필요 없이 main에서만 디버깅하면 충분합니다.

마치며

이것으로 제 글을 마치겠습니다. 첨언하자면 Git Flow와 트렁크 기반 개발 두 방법 모두 오랜 기간동안 많은 변형이 있었던 전략이고, 위에서 설명한 내용이 모두 항상 들어맞는 것은 아닙니다.

이 글의 내용은 대략적인 소개로 바라봐주시고, 관심이 있으시다면 다른 글들을 더 읽어보시면서 더 알아보시는 것을 추천드립니다!

레퍼런스:

devops
git