다들 건강하시죠? LINE 메시징 서버를 개발하고 있는 Nao입니다. 지난 2014년에 LINE 서버 개발과 릴리스 프로세스(일본어)를 소개했습니다. 이후 시간이 흐르면서 개발 환경과 코드가 변화했고, 이번 글에서는 그 후 개선한 점을 소개하고자 합니다.
기존 개발 프로세스 소개
LINE 메시징 플랫폼은 여러 서버 애플리케이션으로 구성돼 있습니다. 메시징 서버는 그중에서 메시징 기능을 담당하는 플랫폼으로, 다른 팀 개발자들과 협력해서 개발하고 운영하며 여러 프로젝트가 병행으로 진행됩니다. 따라서 여러 프로젝트에서 동시에 작업을 진행하면서 혼란스러울 때가 있고, 각 작업에서 어떤 내용이 진행되고 있는지 제대로 파악하기 어려울 때도 있습니다. 이런 문제를 개선하면서 공동으로 개발하기 위해 들이는 수고를 최소화하기 위해 시행착오를 거쳐가며 개발자끼리 여러 가지 룰이나 정책을 정립해 왔으며, 그 결과가 LINE 메시징 서버의 기존 개발 프로세스입니다.
기존 개발 프로세스에서는 기능 개발은 develop 브랜치에서 feature 브랜치를 만들어 진행하고, 릴리스는 master 브랜치에서 release 브랜치를 만들어 Git 체리픽(cherry-pick) 명령을 이용해 반영해서 master 브랜치에 적용했습니다.
테스트 프로세스는 단위 테스트와 베타 QA(개발자 QA, QA 팀 테스트, 통합 테스트), RC(release candidate) QA 등 다양한 테스트 방법으로 구성했습니다.
테스트 프로세스는 다음과 같습니다.
- 개발자는 항상 단위 테스트를 세트로 구현합니다.
- develop 브랜치에 통합한 뒤 베타 환경에서 다른 컴포넌트와 연계해 테스트합니다.
- 매시간 베타 서버를 대상으로 통합 테스트가 자동으로 실행됩니다. 개발자는 기능을 개발할 때 통합 테스트도 함께 추가하며, 그 덕분에 다른 컴포넌트가 변경되거나 의도하지 않은 변경이 발생했을 때 보다 쉽게 소프트웨어 회귀를 감지할 수 있습니다.
- QA 팀에서 LINE 클라이언트를 사용한 회귀 테스트를 정기적으로 진행합니다.
- 기능 테스트를 완료하면 master 브랜치에 변경 사항을 통합하고 RC 환경에서 동시에 릴리스할 다른 변경과 함께 RC QA를 실시합니다.
- 릴리스 환경에 배포할 때는 카나리 그룹(canary group) 서버에 먼저 릴리스한 뒤 이후 전체 환경에 반영합니다.
개발 프로세스 개선 배경
메시징 서버 코드는 LINE에서 역사가 긴 편으로 시간이 흐르면서 개선해야 할 점이 하나씩 도출되고 있습니다. 그동안 마이크로 서비스 구조로 전환해 가면서 많은 코드를 다른 서버로 분리한 덕분에 동시 개발 작업이 줄어서 그동안 하기 어려웠던 개선 작업을 시도할 수 있었습니다.
아래 경우를 살펴보겠습니다. develop 브랜치에는 feature A를 먼저 적용한 뒤 feature B를 적용했지만, feature B를 먼저 출시하게 된 경우입니다. 이때 체리픽 명령어를 실행하면 충돌(conflict)이 발생해서 이를 해결한 후에 릴리스해야 하는 경우가 있습니다.
이런 경우 release 브랜치에서 테스트를 하는데 이때 아래와 같은 문제가 발생합니다.
- 체리픽 실행으로 발생한 충돌을 해결하는 과정에서 develop 브랜치가 변경될 가능성이 있기 때문에 일부 코드에서 develop과 master 브랜치 간 차이가 발생할 수 있습니다.
- 릴리스하지 않은 코드에서 개발하기 때문에 릴리스하지 않는 기능을 포함한 코드 기반으로 개발 및 테스트하게 됩니다.
develop 브랜치와 master 브랜치 간 차이를 없애기 위해서 정기적으로 develop 브랜치를 master 브랜치에서 새로 만드는 '브랜치 리셋'을 진행하고 있지만, 과정을 좀 더 가시화하고 자동화해 간편하게 테스트할 수 있도록 개선하기로 결정했습니다.
개선 미션 및 목표
개발 프로세스 개선 미션은 다음과 같습니다.
- 개발 단계부터 릴리스하지 않은 다른 코드와 격리해서 테스트를 실행
- develop 브랜치에서 릴리스한 기능과 그렇지 않은 기능을 파악할 수 있도록 개발 진척을 가시화
위와 같은 미션을 달성하기 위해서 구체적으로 다음과 같은 목표를 설정했습니다.
- feature 브랜치는 master 브랜치에서 생성
- master 브랜치와 develop 브랜치 간 Git 히스토리를 동기화하기 위해 체리픽 기반 플로(cherry-pick-based flow)에서 머지 기반 플로(merge-based flow)로 변경
- 컨테이너 기술을 활용해 격리된 테스트 환경 정비
아래는 최종 개선 결과를 표현한 그림입니다. 각 feature 브랜치는 master 브랜치에서 생성해 격리된 환경에서 테스트하고 릴리스합니다.
기존 개발 프로세스에서도 로컬 환경에서 어느 정도 테스트가 가능했지만 마이크로 서비스 구조로 전환하기 시작하면서 로컬 환경 테스트가 점점 어려워졌습니다.
로컬 환경에서 테스트할 때는 메시징 서버를 로컬 환경에서 실행하고, 다른 컴포넌트와의 연계가 필요하면 사내 네트워크의 알파 환경이나 베타 환경에 접속해 테스트를 실시합니다. 통합 테스트 역시 로컬 모드로 실행해서 테스트합니다.
새로운 개발 프로세스 소개
새로운 개발 프로세스를 소개하겠습니다. Git 브랜치 전략이자 개발 프로세스로 널리 알려진 Gitflow와 GitHub Flow도 함께 비교해 보겠습니다.
먼저 Gitflow에서는 develop 브랜치에서 release 브랜치를 만들어 release 브랜치를 master 브랜치에 머지하고 릴리스합니다. GitHub Flow는 develop 브랜치 없이 master 브랜치만 관리하는 심플한 전략입니다. 현재 LINE에서도 GitHub Flow 전략으로 개발하고 있는 프로젝트가 꽤 있습니다.
메시징 서버에서는 여러 기능 개발이 독립적으로 추진되고 있으며, 그 과정에서 다른 컴포넌트와 연계하는 테스트를 위해 베타 환경을 준비해 놓습니다. 이 베타 환경을 위해 develop 브랜치가 존재합니다. 또한 메시징 서버는 릴리스 주기가 빠릅니다. 매일 릴리스가 진행됩니다. 따라서 develop 브랜치를 master 브랜치에 머지하고 릴리스할 수는 없습니다. 그렇기 때문에 Gitflow는 적합하지 않았습니다.
메시징 서버에서는 develop 브랜치를 남긴 상태에서 GitHub Flow처럼 master 브랜치에서 개발함으로써 다른 기능과 독립된 개발과 테스트, 릴리스를 가능하게 만들었습니다. 기본적인 개념은 아래 그림과 같습니다. 개발과 테스트는 master 브랜치에서 진행하고, 이전과 마찬가지로 베타 QA와 RC QA를 실시하며, 릴리스할 때는 feature 브랜치에서 master 브랜치로 머지합니다(이때 '백 머지(back merge)'라는 게 등장하는데 이는 아래에서 별도로 설명하겠습니다).
조금 더 복잡한 예시를 살펴보겠습니다. 아래는 feature 브랜치를 develop 브랜치로 머지할 때 충돌이 발생한 경우입니다. 이런 경우에는 'conflict-resolution'이라고 부르는 브랜치를 develop 브랜치에서 생성하고, feature 브랜치를 conflict-resolution 브랜치에 머지하면서 충돌을 해결합니다. 그런 다음 conflict-resolution 브랜치를 develop 브랜치로 머지합니다. 릴리스는 기본 전략과 동일하며 feature 브랜치를 master 브랜치로 머지합니다.
도입 후 좋아진 점
개발 프로세스를 개선하면서 다음과 같은 이점을 얻었습니다.
- Git 명령어를 이용해 많은 것들을 쉽게 자동화하고 가시화할 수 있습니다.
- develop 브랜치의 어느 feature 브랜치가 릴리스됐는지 개발 진척을 가시화할 수 있습니다.
- feature 브랜치 간 의존성도 가시화할 수 있습니다.
- 릴리스되지 않은 코드에서 테스트하면 자신의 기능을 먼저 릴리스할 경우 테스트를 통과한 코드 경로가 변경돼 버립니다. 릴리스 환경에서 테스트하면 이를 방지해서 개발 단계 테스트의 신뢰성을 높일 수 있습니다.
도입하면서 어려웠던 점
새로운 개발 프로세스로 넘어가는 과도기에는 다양한 팀원 모두에게 전략을 설명하고 이해시키는 것이 쉽지 않습니다. 따라서 누군가는 잘못된 개발 프로세스로 진행해 버릴 수도 있습니다. 또한 어느 정도 마이크로 서비스화해서 여러 서버로 나누었다고 해도 많은 개발자가 종사하고 있기 때문에 쉽게 충돌이 발생할 수 있습니다. 이 때문에 conflict-resolution 브랜치 생성 비용이 페인 포인트가 됐습니다. 또한 브랜치를 잘못된 개발 프로세스에 따라 만드는 실수를 예방하거나 충돌이 발생하는 경우를 줄이기 위해서 테스트 환경을 개선하고 여러 지원 도구를 제작하면서 유지 보수 비용이 늘어났습니다.
새로운 개발 프로세스를 지원하기 위한 도구와 기법
새로운 개발 프로세스를 지원하기 위해 PR Checker와 Weekly Reporter라는 도구를 제작했고, 백 머지라는 머지 방법을 사용하고 있습니다. 각 도구와 기법을 소개하겠습니다.
PR Checker
PR(pull request)에서 잘못된 개발 프로세스에 따라 브랜치가 생성되는 것을 막기 위해 직접 제작한 도구입니다. PR이 생성되거나 갱신될 때 자동으로 실행돼 브랜치가 어떤 브랜치인지 검출한 결과를 보여줍니다. PR Checker를 도입하면서 잘못 생성된 브랜치가 develop 브랜치에 머지되는 일이 없어졌습니다.
PR Checker 실행 결과 예시를 살펴보겠습니다.
feature 브랜치
feature 브랜치가 master 브랜치에서 생성됐는지 확인합니다.
conflict-resolution 브랜치
develop 브랜치에서 새로운 브랜치를 만들고 feature 브랜치를 머지한 경우 conflict-resolution 브랜치로 감지됩니다.
wrong 브랜치
feature 브랜치가 develop 브랜치에서 개발된 경우 경고를 표시합니다.
백 머지(back merge)
앞서 잠시 등장했던 백 머지라는 머지 방법을 소개하겠습니다.
백 머지가 필요한 이유를 이해하려면 커밋 유형을 이해해야 합니다. 커밋에는 머지 커밋과 논머지(non-merge) 커밋, 두 가지 유형이 있으며 이는 부모 커밋 수로 결정됩니다. 예를 들어 아래 그림에서 c1, c2, c3, f1, f2는 부모 커밋이 하나인 논머지 커밋입니다. 반면 m1은 여러 브랜치를 머지할 때 생성되는 커밋으로 부모 커밋이 두 개 이상인 머지 커밋입니다.
master 브랜치에 릴리스할 때 생성되는 머지 커밋은 develop 브랜치에 동기화해야 합니다. master 브랜치에 머지할 때 develop 브랜치에 차이가 발생하지 않도록 머지 커밋을 develop 브랜치에 머지하는 작업을 백 머지라고 합니다.
Weekly Reporter
Weekly Reporter는 develop 브랜치의 개발 진척을 확인할 수 있는 도구로 원래 충돌을 줄이기 위해 개발됐습니다. 충돌이 발생하는 원인은 develop 브랜치에 머지됐지만 오랫동안 릴리스되지 않은 브랜치인데요. 오랫동안 릴리스되지 않는 이유로는 릴리스 예정이 없거나 로컬 환경에서 테스트할 수 없는 경우 등이 있습니다. Weekly Reporter는 GitHub pages를 사용해서 개발 진척과 충돌 원인을 시각화해 다음과 같은 사항을 확인할 수 있도록 제공합니다.
- 릴리스되지 않는 브랜치 목록
- 잘못된 프로세스에 따라 생성된 브랜치 목록
- conflict-resolution 브랜치 목록
- conflict-resolution 브랜치에서 충돌을 야기한 브랜치 목록
릴리스되지 않은 브랜치 목록
develop 브랜치에 머지됐지만 릴리스되지 않은 feature 브랜치를 오래된 순서대로 표시합니다. 또한 master 브랜치에서 새로 develop 브랜치를 재작성하는 브랜치 리셋에도 이용합니다. 다시 만들 때 오래된 feature 순서대로 머지를 시도하며 충돌없이 머지할 수 있는지 표시하고, 충돌이 발생하면 의존성 브랜치도 표시합니다. 브랜치 리셋은 자동으로 머지할 수 있는 것은 자동으로 머지하기 때문에 지금까지 수동으로 실시해야 했던 브랜치 리셋을 어느 정도 자동화할 수 있게 됐습니다.
잘못된 프로세스에 따라 생성된 브랜치 목록
develop 브랜치에 머지된 브랜치 중 잘못 생성된 브랜치를 표시합니다. 잘못 생성된 브랜치가 발견되면 리버트(revert)해야 하는데 현재 PR Checker 덕분에 잘못 생성된 브랜치가 발생하지 않고 있습니다.
conflict-resolution 브랜치 목록
conflict-resolution 브랜치 목록을 표시합니다. 충돌이 얼마나 자주 발생하는지 확인할 수 있습니다.
conflict-resolution 브랜치 생성을 야기한 브랜치 목록
conflict-resolution 브랜치를 만들게 된 원인 브랜치를 영향도가 높은 순으로 표시합니다. 왜 릴리스되지 않고 있는지 확인하며 개선 방법을 모색할 수 있습니다.
새로운 개발 프로세스 도입 후기
익숙하지 않은 개발 프로세스였기에 학습 비용이 높았지만, 학습 후에는 릴리스되지 않고 잊힌 브랜치의 진척 사항을 쉽게 확인할 수 있게 됐습니다. 현재 충돌이 발생하면 개발자는 다음과 같이 대응합니다.
- 코드 충돌이 발생한 사람끼리 언제 릴리스하는지, 어떻게 테스트하는지 미리 논의해 결정합니다. 이를 통해 릴리스 주기를 단축할 수 있습니다.
- 기능 플래그(feature flag)를 이용해 기능을 끈 상태에서 점진적으로 릴리스합니다.
- 충돌이 발생하기 어려운 구조로 코드를 리팩토링합니다.
그 외에 충돌이 많이 발생해서 추가로 테스트 환경을 개선할 필요가 있었습니다. 로컬 환경에서는 환경 스펙과 같은 여러 제약 때문에 테스트할 수 없는 경우가 있어서 베타 환경에서 테스트해야 하는데요. 베타 환경에 머지하지 않고 베타 테스트가 가능하게 만들기 위해 다양한 프로젝트 개발자들과 태스크 포스를 조직, 테스트 환경을 개선할 수 있었습니다. 컨테이너 기술을 이용해 간단하게 만든 컨테이너에 베타 서버와 같은 서버를 만들었고, 이를 베타 컨테이너라고 부르고 있습니다. 이와 관련해서는 추후 다른 글에서 자세히 소개할 예정입니다.
마치며
LINE에는 많은 서버 애플리케이션이 있고 브랜치 관리 방법은 각 프로젝트와 목적에 따라 다릅니다. 그중에서 이번 글에서는 메시징 서버의 개발 프로세스를 개선한 사례를 공유했습니다.
아직 과제가 남아 있습니다. 먼저 develop 브랜치의 머지 커밋을 master 브랜치에 동기화해야 합니다. 이를 위해 develop 브랜치의 머지 커밋을 master에 동기화하는 포워드 머지(foward-merge) 개념 도입과, 모두 자동으로 머지할 수 있는 경우 정기적으로 자동으로 리셋하는 방법 등 여러 가지 방법을 검토하고 있습니다.
또한 사내에는 여러 베타 서버가 있으며 다른 팀의 서버 애플리케이션 콜백은 여러 베타 서버 중 미리 지정한 서버를 고정으로 호출하는 경우가 있는데요. 이때 호출하는 서버를 동적으로 변경할 수 있게 만들어 테스트의 폭을 넓히는 등의 개선 작업을 지속적으로 진행하고 있습니다.
앞으로도 꾸준히 개발 프로세스를 개선해 나갈 예정입니다. 이 자리를 빌려 개발 프로세스 개선 작업과 관련된 모든 팀에게 감사의 말을 전하고 싶습니다. 감사합니다.
참고자료
- LINE 서버 개발 및 릴리스 프로세스: https://engineering.linecorp.com/ja/blog/line-server-dev-and-release-process/
- Gitflow: http://nvie.com/posts/a-successful-git-branching-model/
- GitHub flow: http://scottchacon.com/2011/08/31/github-flow.html