Tech

생산성 향상을 위한 새로운 브랜치 전략 도입, byebye Git-flow

2024. 04. 19

 

 

 

안녕하세요! 화해 백엔드 플랫폼 엔지니어 지찬규입니다.

 

개발자라면 한 번쯤 브랜치 전략을 고민할 시기가 다가옵니다. 대개 협업이 기본인 대부분 조직은 이미 각자 상황에 어울리는 브랜치 전략을 도입해서 사용하겠지만, 여건은 변하기 마련이죠. 이 때 브랜치 전략 설계 방법론을 모른다면 잘못된 방향으로 빠지기 쉽습니다. 지난번 화해에서 「브랜치 전략 수립을 위한 전문가의 조언들」이라는 제목으로 브랜치 전략 설계 방법론을 소개해드린 이유이기도 하죠.

 

이번에는 지난 글에서 약속드렸듯이 화해 브랜치 전략을 소개하는 자리로 마련하였습니다.  이전 글을 아직 안 보셨다면 다음 내용으로 넘어가기 전에 읽어보는 걸 권해드립니다.

 

자, 그럼 화해 백엔드 플랫폼에서 브랜치 전략을 변경하게 된 배경부터 시작하여 그 과정 속에서 겪은 의사결정 사안을 소개해드릴게요.

 

 


 

브랜치 전략 개선 전에 필요한 배포 프로세스 현황 파악

 

브랜치 전략을 개선하겠다고 마음 먹었다면 가장 먼저 살펴봐야 하는 대상이 바로 배포 프로세스 현황입니다. 배포 여건에 따라 브랜치 전략이 달라지는 만큼 필수적인 과정이죠. 화해는 어떤지 함께 살펴볼까요?

 

우선 화해는 정기 배포를 기반으로 둡니다. 물론 배포 프로세스 논의 당시, 주 단위로 배포하면 불편한 상황이 생길 수 있다는 의견도 존재했습니다.

  • 만약 상시 배포해야 할 일이 생기면 어떡하지?
  • 예상 배포 일정이 미뤄지면 일주일을 더 기다려야 하는데 괜찮을까?
  • 촉박한 일정으로 인하여 QA 기간이 부족하면 어떡하지?

이 외에도 다양한 의견이 나왔습니다. 그럼에도 불구하고 소프트웨어 품질을 지키고 유지하기 위해 정기배포를 하지 않을 이유가 되진 않았습니다. 다양한 의견을 모아 각 안건들을 논의하고 대책을 강구하였습니다.

 

또한 화해는 메인 서비스가 앱 기반인 만큼 상시배포가 적합하지도 않았죠. 백엔드 서버를 상시배포하더라도 네이티브로 구현된 기능에 미치는 영향은 적습니다. 따라서 핫픽스처럼 긴급 대응이 필요로 하지 않는 이상 상시배포가 미치는 서비스 영향도가 적어 정기배포 기반을 유지하게 되었습니다. 실제로 특별한 경우가 아니라면 상시배포를 하는 경우가 많지 않았으며, 불가피하게 핫픽스가 필요한 상황이라면 도메인 시니어와 논의 후 상시배포로 운영 환경에 반영하였습니다.

 

 

기존 프로세스에서 겪었던 문제점

 

배포 프로세스를 파악하였다면 그 다음은 당연히 기존 브랜치 전략이 가진 문제점이 무엇인지 파악하는 겁니다. 기존에 백엔드 플랫폼에서 관리하는 모든 리포지토리는 Git-flow를 기반으로 관리되었습니다. 입사 전 결정된 사안이라 Git-flow을 도입했던 배경은 알 수 없었지만, 새로운 분들이 입사하시면서 develop이라는 불필요한 단계로 인해 리소스가 낭비된다는 의견이 나왔습니다. 기존에는 다음과 같은 방식으로 일했거든요.

  • develop 브랜치를 기반으로 feature 브랜치 생성
  • feature 브랜치에서 개발 후 QA 진행
  • feature 브랜치를 develop 브랜치로 병합
  • develop 브랜치에서 통합검증 진행
  • release 브랜치 생성 후 develop 브랜치 병합
  • release 브랜치를 main 브랜치로 병합
  • 운영 환경 배포

이 과정 중에서 release 브랜치는 develop 브랜치를 main 브랜치로 병합하는 중간 다리 역할 그 이상 그 이하도 하지 않았습니다. 굳이 release 브랜치가 없더라도 문제되는 건 존재하지 않았던 거죠.

 

또한 배포하기 전에 통합검증을 진행하였지만, stage 환경이 별도로 구성되지 않았습니다. 그로 인하여 통합검증시 운영 환경과 상이한 데이터를 기반으로 QA를 진행하였습니다. 실제 운영 환경과 다른 더미 데이터로 QA하다보니 운영 배포 후 예기치 않은 이슈가 간혹 등장하였습니다.

 

 

기존 배포 프로세스에서 바뀐 부분들

 

기존에 사용하던 Git-flow를 화해 정기배포 브랜치 전략으로 사용하기에는 부적합하였습니다. 앞서 소개드렸던  「브랜치 전략 수립을 위한 전문가의 조언들」에 작성한 내용을 토대로 저희에게 필요한 정책들을 가다듬어 정기배포 브랜치 전략을 새롭게 구성하였습니다.

 

첫 째, develop 브랜치를 제거했습니다. 이력을 관리하는 고정 브랜치가 develop과 main, 2개 존재하다보니 브랜치 관리 복잡성이 증가한다고 판단했습니다.

 

둘 째, release 브랜치를 ‘정말’ release 브랜치로 사용하기로 했습니다. 위에서 잠깐 언급했듯이 release 브랜치를 단순히 develop 브랜치에서 main 브랜치로 병합하는 중간 통로로 사용했었습니다. release 브랜치를 통합검증을 위한 브랜치로 정의하였습니다. 이를 정리하면 다음과 같습니다.

  • 고정 브랜치는 main 브랜치 하나만을 유지한다.
  • 모든 브랜치는 main 브랜치에서 분기된다.
  • feature 브랜치는 개발자 필요 시 하위 feature 브랜치를 가질 수 있다.
  • feature 브랜치를 기반으로 기능 QA를 진행한다.
  • 개발 환경으로 검증을 마친 feature 브랜치만 release 브랜치로 병합한다.
  • release 브랜치에 모인 feature에 대하여 통합검증을 진행한다.
  • 통합검증 후 문제가 없다면 main 브랜치에 병합하며, 언제든지 운영 환경에 배포할 수 있다.

GitHub-flow를 기반으로 정기배포 브랜치 전략을 세웠다는 걸 바로 눈치 채실 수 있을 겁니다. 앞선 글에서 설명한 정기배포 웹 브랜치 전략과 다르지 않습니다.

 

셋 째, 명확하게 stage 환경을 구성하였습니다. 개인 정보를 제외하면 사실상 운영 환경과 거의 차이나지 않도록 구성하여 운영 배포 후 예기치 않은 상황이 발생하는 걸 미연에 방지하고자 체계를 다듬었습니다.

 

 

화해 정기배포 브랜치 전략, 자세히 훑어보기

 

앞서 말한 내용들을 바탕으로 화해 백엔드 플랫폼에서 정의한 정기배포 브랜치 전략 파이프라인을 그려봤습니다.

 

  • version: 기존에는 시멘틱 버저닝을 사용하였으나, 정기배포 브랜치 전략에서 사용하기에는 이해도가 서로 달라 조금 더 단순한 버저닝을 채택했습니다.
    • 정기배포 시 앞 자리 증가
    • 상시배포 시 뒷 자리 증가
  • main : 언제든 운영 환경에 배포 가능한 상태로 유지합니다.
  • release : 통합검증을 위한 브랜치입니다. feature 브랜치 간 충돌이 발생하면 release에 merge할 때 해결합니다. 동시에 여러 release 브랜치가 운용 가능합니다. ex.상시배포 + 정기배포
  • feature : 개별 기능 개발을 위한 브랜치입니다. 기능 개발이 완료되면 Pull Request를 거쳐 release 브랜치로 병합합니다.
  • hotfix : 긴급한 운영환경 버그 수정을 위한 브랜치로 main에서 분기하여 main으로 병합합니다.

 

💡정기배포를 제외한 hotfix는 상시배포로 간주합니다

 

 

📌브랜치 별 머지 전략

  • featurerelease : Squash and Merge
  • releasemain : Merge Commit
  • hotfixmain : Merge Commit

 

feature 브랜치에서 release 브랜치로 merge할 때 Squash and Merge를 사용하는 이유부터 살펴볼까요?

 

만약 feature 브랜치 내 커밋(commit)이 여럿 쌓인다면 커밋 히스토리가 지저분해져 브랜치 그래프 가독성이 떨어질 수밖에 없습니다. 작업 히스토리 가독성을 높이고자 Squash and Merge를 활용했습니다. 그 이면에는 업무 단위를 관장하는 Jira 티켓이 존재합니다. 화해는 모든 작업이 티켓 기반으로 이루어지며, 커밋 메세지 첫 문단에 티켓 번호를 기록하도록 컨벤션 구성 상태입니다. Git 히스토리가 정제되면 정제될수록 보다 수월하게 코드 변경 사유를 추적하기 용이합니다. 위와 같은 이유로 feature에서 release로 병합할 때 Squash and Merge를 사용합니다.

 

💡Squash는 여러 commit을 하나로 합치는 행위를 의미합니다. 
  Squash and Merge는 병합할 브랜치에 존재하는 모든 작업 내역을 하나로 모아 
  단일 commit으로 만들고 병합합니다.

 

컨벤션으로 정의하더라도 개발자도 사람인지라 실수가 발생할 가능성을 배제할 수 없습니다. 브랜치에 따라 병합 방식이 달라 생기는 실수를 미연에 방지하고자 GitHub Actions를 활용했습니다. 개발자에게 PR 기준으로 base 브랜치에 어울리는 브랜치 병합 방식을 안내드립니다.😊 처음에는 merge 방식을 GitHub Actions로 제어할 수 없을지 고민하였으나, 방법을 찾지 못해 PR마다 개발자에게 안내드리는 방식을 채택했습니다.

 

 

▶️ 깃헙액션 코드 공유

name: PR merge Reminder
on:
  pull_request:
    branches: [main, master, 'release/**']
    types: [opened, reopened]
  pull_request_review:
    branches: [main, master, 'release/**']
    types: [ submitted ]

jobs:
  pr-on:
    runs-on: ubuntu-latest
    steps:
      - name: Open release branch
      if: ${{ startsWith(github.base_ref, 'release/') }}
      uses: peter-evans/create-or-update-comment@v3
      with:
        issue-number: ${{ github.event.pull_request.number }}
        body: |
          개발자 친구들~ release 브랜치에는 `Squsah and Merge` 해야쥬 🚀 <br>
          <img alt="main-merge.png" src="이미지 URL">

      - name: Open main(master) branch
        if: ${{ contains(fromJSON('["main", "master"]'), github.base_ref) }}
        uses: peter-evans/create-or-update-comment@v3
        with:
          issue-number: ${{ github.event.pull_request.number }}
          body: |
            개발자 친구들~ master(main) 브랜치에는 `Merge commit`을 해야쥬 🚀 <br>
            <img alt="main-merge.png" src="이미지 URL">

check-main-approve:
  if: github.event.review.state == 'approved' && contains(fromJSON('["main", "master"]'), github.event.pull_request.base.ref)
  runs-on: ubuntu-latest
  steps:
    - name: Approve main(master) breanch
    uses: peter-evans/create-or-update-comment@v3
    with:
      issue-number: ${{ github.event.pull_request.number }}
      body: |
        ${{ github.actor }} 님이 approve 하셨네요!! 우린 이제 한배를 탄겨!
        아참 그리고!! master(main) 브랜치에는 `Merge commit`을 해야쥬 🚀 <br>
        <img alt="main-merge.png" src="이미지 URL">

check-release-approve:
  if: github.event.review.state == 'approved' && startsWith(github.event.pull_request.base.ref, 'release/')
  runs-on: ubuntu-latest
  steps:
    - name: Approve release breanch
      uses: peter-evans/create-or-update-comment@v3
      with:
        issue-number: ${{ github.event.pull_request.number }}
        body: |
          ${{ github.actor }} 님이 approve 하셨네요!! 우린 이제 한배를 탄겨!
          아참 그리고!! release 브랜치에는 `Squsah and Merge` 해야쥬 🚀 <br>
          <img alt="main-merge.png" src="이미지 URL">

 

정기배포 브랜치 전략에서 일어날 수 있는 상황을 정리해서 설명드리겠습니다.

 

 

📌feature 개발 후 정기배포 상황

모두가 행복한 feature 개발 진행

 

feature 브랜치는 개발시점 가장 최신 main 기준으로 checkout을 합니다. 각 feature들은 개발서버에서 테스트를 한 후 문제가 없으면 PR을 통해 정기배포 release 브랜치에  Squash and Merge 합니다. 정기배포에 포함되는 feature 브랜치들이  Squash and Merge되고 최종 코드 프리징 이후 통합 QA를 진행합니다. 코드 프리징 이후에는 release 브랜치를 직접 수정하거나 신규 feature 브랜치들을 merge할 수 없습니다.

 

 

release 브랜치 merge 과정에서 충돌conflict 해결

 

release 브랜치에 feature 브랜치에   Squash and Merge 하는 과정에서 충돌이 발생하면  Merge from parent(back merge) 를 통해 코드 충돌을 해결한 뒤   Squash and Merge 를 진행합니다.

 

 

통합검증 과정에서 결함이 발견되거나 이슈를 수정해야 하는 경우

 

코드 프리징 이후 신규 feature 브랜치들은 정기 배포 release 브랜치에 Squash and Merge 될 수 없으나, 이미 Squash and Merge 된 feature에서 결함 혹은 이슈가 있을 경우 수정사항을 release 브랜치에 반영합니다. 수정사항이 반영 되면 통합검증을 진행하는 관계자들에게 공유합니다. 그 후 다시 코드프리징하여 통합검증을 마저 진행합니다. 통합검증이 진행될 때 수정사항이 반영된 버전을 배포하면 안됩니다.

 

 

📌feature 브랜치 checkout 이후 main 브랜치 변경점이 생긴 상황

Merge from parent(back merge)를 통해 해결

 

rebase를 통해 해결

 

main 브랜치에서 feature 브랜치를 checkout 한 뒤 main 브랜치에 변경점이 생기는 경우에는 Merge from parent(back merge) 혹은 rebase로 conflict를 해결합니다. 두 가지 방법 중 개발자가 편한 형태로 진행합니다. release로 merge가 될 때는 Squash and Merge 하기에 최종 그래프는 깔끔하게 정리 됩니다.

 

 

📌release 브랜치에 이미 병합된 기능을 제외하는 경우

revert 이후 다음 정기배포 브랜치에 Squash and Merge

 

revert 이후 rebase로 한번 정리후 다음 정기배포 브랜치에 Squash and Merge

 

신규 feature 브랜치가 이미 release 브랜치에 Squash and Merge 된 기능을 제외하고 싶은 경우 revert commit을 사용합니다. 다음 정기배포에 revert한 feature를 포함하고 싶은 경우 다시 PR을 올려서 다음 정기 배포 브랜치에 Squash and Merge 하거나 rebase로 한 번 정리한 뒤 Squash and Merge 합니다. 두 가지 중 자유롭게 개발자의 편의에 따라 진행합니다.

 

 

📌release 브랜치 생긴 이후 상시배포 release 브랜치가 생기는 상황

상시배포, 정기배포가 동시에 일어나는 상황

 

정기배포 release 브랜치가 checkout 된 이후에 상시배포 release 브랜치가 추가로 생긴 다면 상시 배포 release 브랜치가 main에 Merge Commit한 뒤 정기배포 브랜치에서 merge Commit를 진행합니다. 그 과정에서 코드 충돌이 발생하면 해결해야 합니다.

 

 

📌정기배포, 상시배포, 핫픽스 동시에 진행되는 상황

혼돈 그자체인 핫픽스, 정기배포, 상시배포 모두 존재하는 경우

 

이런 경우는 거의 없겠지만😅 최악의 시나리오를 가정해보겠습니다. 정기배포, 상시배포, 핫픽스 모두 동시에 진행되는 상황도 지금까지 살펴본 상황과 크게 달라 지는건 없습니다. 정기배포 이전에 핫픽스, 상시배포 브랜치들이 각각 main에 Merge Commit되고 최신 main 브랜치를 merge from parent로 싱크를 맞춥니다.

 

 

맺음말

 

주니어 시절 큰 고민 없이 GitHub-flow나 Git-flow처럼 대중에게 널리 알려진 유명 브랜치 전략을 사용했습니다. 이미 알려질대로 알려진 브랜치 전략을 사용하는 게 나쁜 건 아니지만, 조직 상황에 맞게 브랜치 전략을 고민하고 개선해볼 생각하지 못 했습니다. 지금 계신 조직에서 만약 브랜치 전략을 고민하고 계신다면 구성원들과 함께 논의해보는 건 어떨까요? 지금 머무는 조직 여건에 어울리는 브랜치 전략을 채택하길 바랍니다.

 

앞으로도 화해 개발팀은 통합검증, 정기배포 브랜치 전략처럼 개발 리소스를 줄이면서 “소프트웨어 품질”을 올리기 위한 방안을 찾아 나갈 예정입니다. 지금껏 소개드린 브랜치 전략 외에도 외부에 드러나지 않지만, 이미 진행 중인 프로젝트도 존재합니다.  모든 개발자가 조직에 걸맞는 프로세스 안에서 일하기를 바라겠습니다. 긴 글 읽어주셔서 감사해요!

 

 


이 글이 마음에 드셨다면 다른 콘텐츠도 확인해 보세요!

캐시 스탬피드를 대응하는 성능 향상 전략, PER 알고리즘 구현

화해 검색 개선의 첫걸음

 

  • 정기배포
  • github actions
  • 브랜치 전략
  • git-flow
avatar image

지찬규 | Software Engineer

함께 자라기를 추구합니다!

연관 아티클