들어가며
안녕하세요, 당근페이 머니서비스팀 백엔드 엔지니어 제레미예요. 당근페이팀에 합류한 지 2년이 다 되어 가는데, 합류 당시 작성된 코드 베이스가 한 줄도 없는 상황에서 새로운 제품을 만드는 것과 별개로 개발팀 문화와 프로세스도 함께 만들어 나갔어요. 그 중에서도 서비스 배포를 하는 과정에서 겪었던, 우리 팀에 더 적합한 브랜치 전략을 찾아 나갔던 여정에 대한 이야기를 공유하려고 해요.
Git Flow
브랜치 전략하면 가장 먼저 떠오르기도 하고 많은 회사와 팀에서 기본으로 사용하고 있는게 Git Flow 라고 생각해요. Git Flow의 특징 몇 가지만 살펴보면,
- 각 용도에 맞게 main(master), develop, feature, release, hotfix 브랜치를 분리해서 사용
- 명확한 릴리즈 기간과 주기적인 버전이 정해진 프로덕트를 개발하는 환경에 적합
- 릴리즈 버전 관리를 위한 release 브랜치를 따로 관리하기 때문에 특정 버전에 대한 유지보수 기간이 길고 여러 버전을 동시에 관리해야 할 필요가 있을때 유용함
- 2, 3과 같은 장점 때문에 소규모 팀보다는 규모가 있는 팀에 더 어울림
이렇게 4가지 정도로 특징을 정리할 수 있어요.
저희 팀도 여느 회사와 마찬가지로 브랜치 전략에 Git Flow를 사용하기로 했어요. 맨 처음에 어떤 이유 때문에 Git Flow를 사용하기로 했는지 정확히 기억은 안 나지만, 위에 나열한 특징 외에도 아래 항목들을 추가로 고려했던 거 같아요.
- 운영 환경(Production) 배포 브랜치와 개발 환경(Develop) 배포 브랜치를 명시적으로 분리해서, 운영 환경에 어떤 코드 베이스가 배포 되어 있는지 한 눈에 확인할 수 있다는 점
- 운영 환경에 언제 배포해도 상관없는 Stable 브랜치(main)를 별도로 관리해서, 장애 혹은 버그 수정이 필요한 경우 main 브랜치를 기준으로 빠르게 수정
- 아직 테스트가 완료되지 않은 피쳐가 운영 환경에 배포되는걸 방지하기 위함(2번과 관련)
- 그리고 마지막, 모두에게 가장 익숙한 브랜치 전략
역사엔 다양한 이유가 있듯이, 이 과정에도 더 많은 이유와 외부 환경 때문에 어쩔 수 없이 선택하게 된 것들도 있었을 텐데 당장 떠오르는건 이렇게 4가지 정도예요. 아직 릴리즈 준비중인 서비스의 경우엔 자유롭게 단일 브랜치를 사용했던 반면, 이미 사용자에게 릴리즈된 서비스의 경우에는 모든 팀이 Git Flow를 바탕으로 개발하고 안전하게 배포까지 했어요.
배포 열차
당근페이 팀이 만들고 있는 제품과 업의 특성상 운영 환경(Production)에 자유로운 배포가 불가능해요. 그리고 배포를 하기 전에는 항상 배포 승인자의 별도 승인이 필요해요.
배포가 많아지면 많아질수록 배포 승인자는 리뷰와 승인에 할애하는 시간이 많아지고 하루의 대부분 시간을 배포 승인에 쏟아야 하는 상황까지 생길 수 있었어요. 게다가 전체 팀의 규모는 작지만 다양한 서비스들이 동시에 만들어지고 있다보니 배포 대상 서비스가 규모에 비해 많은 편이었어요. 그래서 서비스 배포에서 발생하는 컨텍스트를 줄이기 위해 매주 수요일이면 수요일, 목요일이면 목요일에 정기 배포 요일을 정하고 그 날에는 배포를 집중적으로 하기로 했어요. 리뷰, 승인, 배포, 모니터링 등 어느 요일 오후에 집중해서 끝내기로요. 그리고 정기 배포 요일과 별개로 Hotfix 등 다른날에도 배포가 필요하면 비정기 배포를 하곤 했어요. 그래서 매주 정기 배포 요일의 오후가 되면 배포 열차가 떠나고 탑승한 팀원들은 서비스 모니터링을 시작해요.
지옥철
처음엔 아무런 문제나 불편한 점이 없었는데 정기 배포를 1년 이상 하다보니 배포 과정에서 생기는 비효율적인 면과 불안함도 생겼어요. 배포 열차니까 굳이(실제론 여유로웠어요) 비유하자면 지옥철이라고 표현할 수 있을 것 같아요. 일단 탑승해야 하니까 뒤죽박죽 낑겨서라도 탑승했어요. 불편함이 상당하지만 어쩔 수 없었죠. 열차에 사람이 많아질수록 통제가 어려워지는 것과 마찬가지로, 배포 대상 서비스(마이크로서비스)가 늘어나면서 배포 진행중 혹은 그 이후에 발생한 이슈가 어떤 서비스의 영향 때문인지 추적하기가 어려워지고, 릴리즈에 포함된 변경 사항이 너무 많아서 확인에 어려움이 생겼어요.
그리고 배포 과정에서 발생한 문제를 파악하기 어려운 것과 별개로, 우리 팀의 일하는 방식이 배포 프로세스와 맞지 않아서 생기는 불편함도 생기기 시작했어요. 우리 팀은 작고 빠르게 변화를 만들어 나가는 속도가 빠른 팀인데, 속도가 빠르다는건 그만큼 릴리즈도 많이 하고 릴리즈에 포함된 변경 사항이 상당하다는걸 의미해요. 사소한 버그라도 사용자에게 치명적인 금융 서비스를 만들고 있다보니, 자동화된 테스트 뿐만 아니라 꼼꼼한 리뷰도 중요하고요. 모든 피쳐들이 개발 환경이나 테스트 단계에서 검증되어서 실제로 문제는 없었지만, 배포 열차가 출발하는 날이면 Release 브랜치에 있는 탑승 대기중인 코드들을 매번, 모두 한 줄 한 줄 살펴보았어요. 정기 배포 요일이 되면 한 주 동안 새로 추가되고 수정되고 삭제되는 코드 라인이 정말 많았는데, 마음 편하게 열차에 탑승하고 싶었어요. 겉으론 아무렇지 않아보였지만, Deploy 버튼을 누를 때면 심장 박동수가 조금씩 올라갔어요. 지금 생각해보면 이미 검증된 코드를 한 번 더 사서 고생했다는 생각도 드는 한편, 그 때의 꼼꼼함이 성장에 많은 도움이 된거 같기도 해요.
잠깐 배포의 불안함(+불편함)에 대해서 이야기 했는데 다시 돌아와서, 오랫동안 정기 배포를 하다보니 매주 Git Flow 때문에 발생하는 비효율적인 면도 보이기 시작했어요. Git Flow 브랜치 전략은 코드 릴리즈를 위해 Release 브랜치를 생성하고 생성된 Release 브랜치에서 테스트(QA)를 마친 후에 Stable 브랜치(main)로 합쳐요(Merge). 우리팀도 마찬가지로 매주 정기 배포 요일이 되면 아래와 같은 일을 하고 있었어요.
- develop 브랜치에서 release branch 생성
- release 브랜치를 main 브랜치로 merge 하는 PR 생성(CI, Build)
- main 브랜치에 merge 후 release tag 생성
Git Flow의 장점 중 하나는 Release 브랜치를 별도로 관리해서 배포 전에 테스트할 수 있는 환경을 만들 수 있다는 것인데, 당시에 이런 환경(Beta/Staging과 별개로 흔히 얘기하는 QA 환경)이 갖춰져 있지 않아서 별도의 테스트를 하지 않고 바로 main branch로 코드를 합쳤어요. 그리고 정기 배포 요일에는 모든 서비스들의 배포 준비 때문에 CI/CD 작업이 몰리면서 완료되기까지 많게는 30분 이상도 기다려야 했어요. release branch 만들고, main branch 머지를 위한 PR을 생성하고, CI 기다리고, 머지한 뒤에 release tag 생성하고. 매주 이렇게 기계적인 과정을 반복했어요. 빌드를 기다리며 다른 작업을 하면서 발생하는 컨텍스트 스위칭도 생겼구요. 배포 열차에 탑승하기 위해서는 적지 않은 리소스가 매주 소요되었어요.
Github Flow
문제라고 하면 문제일 수 있는 이런 불편함과 불안함들을 해소하기 위해 스터디를 시작했어요. Git Flow, Github Flow, Gitlab Flow 등 다양한 Feature Branching 전략 뿐만 아니라, Mainline 기반으로 개발하는 Trunk-Based Development 방법론도 살펴보았어요.
- Patterns for Managing Source Code Branches: 이것만 정독하고 이해한다면 Git Branch 전략은 마스터할 수 있다고 생각해요. 다양한 방법론에 대한 자세한 설명 뿐만 아니라, 적절한 브랜치 전략을 고를 수 있는 방법까지 소개해주고 있어요.
- Git Branching Strategies vs. Trunk-Based Development: Feature Branching 전략에 비해 trunk-based 개발 방법론이 가지고 있는 장점과 Trunk 기반의 개발을 위해서 반드시 필요한 Feature Flag에 대해서 잘 소개해주고 있어요.
- Trunk-Based Development: Trunk 기반 개발의 정의와 필요한 사항들에 대한 가이드 문서예요. 이 사이트만 제대로 이해하고 있다면 Trunk 기반 개발에 관한 다른 문서는 안봐도 된다고 생각해요.
Github Flow는 Git Flow에서 develop, release, hotfix 브랜치를 제거한 형태예요. Git Flow가 Production 환경에 여러 버전을 관리하고 있는 제품을 위한 전략이라고 하면, Github Flow는 Production 환경에 단일 버전이 있는 제품을 위한 전략이라고 볼 수 있어요. 그래서 배포를 위한 Release 브랜치를 따로 관리하지 않고 Hotfix도 마찬가지예요. Github Flow에서는 Mainline을 main(master)라고 부르고, 개발자는 작업을 feature 브랜치에서 해요.
(이론적으로) 작업도 마찬가지로 Feature 브랜치에서 하니까 Git Flow에서 develop/release/hotfix 브랜치만 제거하면 Github Flow와 똑같다고 생각했는데, 인지해야 할 중요한 점이 하나 더 있다고 생각해요. Git Flow 뿐만 아니라 모든 브랜치 전략에는 Mainline이 존재하는데, Mainline은 모든 작업(Feature)의 시작점을 의미해요. Git Flow는 develop 브랜치가 Mainline이고, Github Flow는 main 브랜치가 Mainline이에요. 그리고 언제 배포해도 상관없는 Stable 브랜치가 release 형태로 분리되어 있는 Git Flow와 다르게, Github Flow는 배포 브랜치와 작업을 시작하는 Mainline이 동일한 main 브랜치를 사용하고 있어요. 즉, Github Flow 전략에서 main 브랜치는 mainline의 역할과 동시에 Stable함을 의미해요. 따라서, Github Flow를 사용하는 팀원 모두는 main 브랜치가 항상 Stable 해야 한다는 명시적 혹은 암묵적 합의가 필요해요. Github Flow 전략을 사용하고 있는데 릴리즈에 포함되면 안되는 피쳐가 main 브랜치에 존재한다면 의도치 않은 장애로 이어질 수 있어요.
Github Flow는 Git flow에 비해서 Release를 위한 절차가 굉장히 줄어들기 때문에 잦은 기능 수정과 배포가 있는 애자일 조직에 적합한 전략이라고 볼 수 있어요. 그리고 여러 버전을 동시에 관리할 필요가 없는 Middle급 조직에 어울리는 전략이기도 해요.
Trunk-Based Development
Github Flow 외에 Trunk-Based 방법론도 살펴보았는데, https://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.
소스 제어 브랜치 모델에서 개발자들은 트렁크(trunk)라는 단일 브랜치에서 코드 작업을 함께 하고, 문서화된 기술을 사용해서 수명이 긴 개발 브랜치를 만들지 않습니다. 따라서 병합(merge) 지옥을 피하고, 빌드를 깨뜨리지 않으며, 행복하게 살아갑니다.
처음엔 trunk가 뭐지 했는데 Trunk-Based 방법론에서 Maineline을 부르는 용어예요. Github Flow로 치면 main 브랜치와 똑같은 의미예요. Feature Branch 기반이 아닌, Trunk 기반으로 작업을 하기 때문에 Mainline으로 변경사항을 바로 바로 추가할 수 있고 수명이 긴 Feature Branch를 관리하지 않아서 Merge Hell에 빠지거나 빌드가 깨지는 경험에서 벗어날 수 있어요. 물론 빌드가 깨지는 결과물을 Mainline에 추가하는건 사전에 빌드 시스템의 개선과 도움이 필요한 영역이에요.
Trunk 기반 개발을 더 살펴보다 보면 여기에도 Feature 브랜치를 사용하는 부분이 나오는데 Github Flow의 Feature 브랜치와는 다르게 지켜야 할 규칙이 있어요. Trunk 기반 개발은 단일 브랜치를 지향하기 때문에, 오랫동안 지속되는 브랜치를 지양하고 있어요(Short-Lived Feature Branches). 브랜치가 Merge, Delete 되기 전에는 며칠 동안만 지속되어야 하고 오랜 기간 남아있는 브랜치가 있다면 Trunk 기반 개발의 철학과 정반대의 기능을 하고 있다고 볼 수 있어요.
이처럼 Trunk 기반으로 작업하는 것과 Feature Branch 수명이 굉장히 짧은 특징은 잦은 기능 수정과 빠른 릴리즈가 필요한 조직에 적합해요. 하지만 검증되지 않은 기능이 사용자에게 노출되는건 위험하기 때문에 단순히 빠른 속도를 위해서 Trunk 기반을 채택해서는 안되고, 몇 가지 약속과 준비해야 할 것들이 있어요.
- Quick rhythm to deliver code to production: 프로덕션에 코드를 속도감 있게 전달
- Small Changes: 작은 변경 사항
- Merge branches to the trunk at least once a day: 하루에 최소 한 번은 브랜치를 트렁크(trunk)에 병합
- Continuous Integration; Automated testing: 지속적인 통합; 자동화된 테스트
- Continuous Delivery: 지속적인 배포
- Feature flags: 피쳐 플래그
Trunk 기반 개발을 위해서는 갖추어야 할 기술 기반 뿐만 아니라 코드 리뷰, 페어프로그래밍, 코드 스타일 등 개발팀의 문화와 더불어서 Trunk를 안전하게 운영할 수 있는 경험과 노하우도 필요해보였어요. 하지만 우리팀에는 이 전략을 경험했거나 시도해 본 사람이 아무도 없었고 상대적으로 익숙한 Github Flow를 기본 브랜치 전략으로 가져가보기로 했어요.
매일 배포하기
배포에 포함되지 말아야 할 피쳐가 mainline에 포함되어 있으면 어떡하냐, 여러 명이 작업하면 Merge Conflict는 어차피 발생하는 거 아니냐 등 Github Flow를 사용한다고 했을때 우려되는 점이 많았어요. 전자는 각 프로젝트에 Feature Flag를 도입해서 해결할 수 있고, 후자는 어느 브랜칭 전략을 사용하던지 간에 발생하는건데 Trunk 기반 개발에서 Feature 브랜치의 수명을 짧게 가져 가는 것처럼 피쳐의 수명을 줄이면 고통이 많이 줄어들거라 생각해요. 마틴 파울러는 고통스러우면 더 자주하라라는 글을 작성했는데, 코드 충돌에서 발생하는 고통 또한 PR과 커밋의 단위를 작게 가져가면 많은 부분을 해소할 수 있을거라 생각해요. 그리고 새로운 전략을 사용해보면서 우리팀 전체의 역량과 경험도 쌓아가고 팀에 맞게 개선할 수 있는 부분도 있을거라 믿었어요.
브랜치 전략에 Github Flow를 사용한 지 어느덧 반년 정도가 되었어요. 그 전에는(Git Flow) 배포를 하기 위해 1시간은 넉넉히 생각하고 있었는데 지금은 20분도 걸리지 않아요. 지금도 배포 프로세스 과정에서 개선해야 할 부분이 많이 있는데 불필요한 절차를 자동화하고 불안함이 있다면 해소하고 싶어요. 결과적으로, 배포에 쏟는 시간은 절반 이상으로 줄어들었고 심적 부담도 많이 나아졌어요. 이제 반년 정도 지났는데 팀에 새로운 경험치도 많이 쌓였다고 생각해요. 더 빠른 배포와 수명이 짧은 피쳐 브랜치 관리를 위해 빌드 속도 개선에도 신경을 많이 쓰게 되었고(개발 생산성도 덩달아 올라갔어요), 새로 작성한 코드나 수정한 코드를 안전하게 관리하기 위해 피쳐 플래그도 적극적으로 사용하고 있어요.
마치며
안정적인 개발과 운영이 중요한 프로젝트를 하고 있다보니 불안함도 많았지만 중요한 교훈도 하나 배웠어요. 문제 없이 잘 동작하는 레거시 코드가 많은 이유이기도 한데, 개인적으로 안정적인 코드는 가장 많이 실행되는 코드라고 생각해요. 그리고 코드 리뷰어와 배포 승인자의 피로를 덜기 위해 배포에 포함되는 변경사항이 많지 않도록(Maineline에 새로운 코드가 많이 쌓이지 않도록) 신경쓰고 있고, 더 자주 배포하고 더 자주 실행하기 위해 노력하고 있어요. 그래서 지금은 매일 배포하는 팀이 되었어요. 이전처럼 배포할 때 긴장되는 느낌과 불안함도 사라졌구요. 지금 팀에서 배포 1000번 하는게 목표인데 생각보다 금방 달성할거 같기도 하고.. 아무튼, Github Flow(with Short-Lived Feature Branches)를 적용하고 나서 우리 팀의 특성에 맞게 빠른 속도와 안정적인 운영을 동시에 가져갈 수 있어서 만족하고 있어요. 개선해야 할 점들을 꾸준히 보완하면서 새로운 전략들에 대한 스터디도 멈추지 않고 안정적이고 효율적인 배포를 위해 멈추지 않는 팀이 될 거예요.
👉 여기를 눌러 매일 배포하는 팀이 되는 여정의 두번째 이야기를 마저 읽어보세요!
누구나 알듯이 이 글의 진짜 목적은 채용입니다!
당근페이는 ‘동네생활을 편하게, 이웃을 더 가깝게 하는 금융 서비스’ 라는 비전 아래 동네 금융 경험을 만들어 가고 있어요. 따뜻한 연결을 위한 경험을 함께 만들어 나가실 엔지니어분을 찾고 있어요. 아래 채용 공고에 많은 관심 가져주세요! 읽어주셔서 감사합니다.
당근페이팀에 더 궁금한 게 있으시다면 jeremy.kim@daangnpay.com 으로 연락해주셔도 좋아요.