3학년을 마무리하고 휴학을 결정하면서 고민이 생겼습니다.
"앞으로 1년 동안 뭘 하고 살지?"
작년의 저는 멀쩡하게 다니던 회사도 그만두고 싶지는 않았지만, 그렇다고 휴학까지 하고 싶지는 않았습니다. 그래서 학업과 회사를 병행한다는 아주 멋진 생각을 몸소 실천하다가 학점(망함) 회사(퇴사함) 모두 놓치고 말았죠.
종강한 이후로는 한가롭게(?) 디프만 프로젝트와 스터디를 하는 일상을 보내고 있었습니다.
다만 문제는 종강한 후로도 그렇게 일정이 널널하지는 않았던 터라, 2월 이후에 디프만이 끝나면 무엇을 할지 딱히 생각해볼 기회가 없었다는 겁니다.
그러던 와중 학기 중에 다니던 회사에서 재입사 요청이 와서 고민을 하다가, 이런저런 고민과 협의 절차를 거치고 다시 익숙한 책상에 앉게 되었습니다.
아아... 이 서늘하고도 묵직한 감각...
다시 "백엔드 엔지니어"로 돌아갈 때다.
그런데 우리 팀이 무너진 거예요
보자마자 눈물이 났어요
그런데 아 뿔 싸 !
다시 돌아온 개발팀은 타노스가 다녀간듯 정확히 반토막이 나있었습니다.
그보다 더 충격적인 것은 핑거스냅 당한 인원이 전부 백엔드였다는 것이고, 그 말은 이 팀에 백엔드와 인프라 담당은 저 혼자라는 겁니다...
투입된 저는 제가 없던 사이 발생한 변화를 팔로업 하는 데 집중했습니다.
먼저 저희 개발팀에서는 크게 1) 앱 서비스와 2) 이와 연계되는 백오피스 관련 서비스를 담당하고 있습니다.
저는 퇴사 전에는 백오피스 서비스만을 담당했었는데요, 백엔드 인력이 증발한 지금부터는 팀에서 운영하고 있는 모든 서비스를 맡게 되었습니다.
헉 그러면 일이 두 배로 늘어난 건가요? 하면 또 그건 아니었습니다.
당시에는 런칭이 임박한 시점이라 팀적으로 굉장히 바쁜 시기였고, 지금은 비수기 시즌이라 신규 기획이나 피쳐 요청이 들어오고 있지는 않았거든요.
현재 팀 목표는 상반기 내 앱 서비스 리뉴얼 작업입니다.
하지만 기존 코드베이스와 인프라를 살펴보면서 시작부터 뭐가 단단히 꼬였다는 생각이 들기 시작했습니다.
이 사진을 한번 봐주세요.
무슨 생각이 드나요?
??: 정답! 집에 가고 싶다!
이런저런 루트를 거쳐서 파악을 해본 결과 사건의 전말은 이랬습니다.
먼저 저희 팀에서 운영하고 있는 I 서비스의 경우 담당 인력에 크게 세 번의 변화가 있었습니다. 편의상 퇴사자 그룹 A, 퇴사자 그룹 B라 합시다. 대략 이런 느낌입니다.
퇴사자 그룹 A -> 퇴사자 그룹 B --(끊어진 인수인계)--> 본인
A에서 B로 전환되는 과정에서는 어느 정도 그룹 간 연결고리가 있었고 인수인계도 원할하게 이루어졌습니다.
하지만 B에서 저로 전환되는 과정에서는 어떠한 연결고리도 없었고, B 그룹 사람들과 같이 일을 하긴 했었지만 (재입사 전에는) I 서비스가 아니라 백오피스 + 신규 프로젝트를 담당하고 있었기 때문에 이런 끔찍한 일이 일어난다는 사실은 모르고 있었죠.
퇴사자 그룹 A 시절의 코드베이스는 그야말로 레거시 중의 상레거시라고 할 수 있을 정도로 처참한 상황이었습니다. 제가 봐도 정말 어디서부터 손을 대야 할지 감도 안잡히긴 했어요. 심지어 런칭 이후로는 리팩토링을 시도하기가 더 어려워졌습니다.
멈춰있는 자동차보다, 달리는 자동차의 바퀴를 바꾸는 것이 더 까다롭기 때문이죠.
그래서 B 그룹이 선택한 방법은 "Big Bang Rewrite" 입니다.
"달리는 자동차의 바퀴를 갈아끼는 것이 어렵다면,
새로운 자동차를 만들어버리면 되지 않을까?"
의외로 좋은 생각이었을지도 모릅니다.
이미 달리고 있는 자동차를 폐차장에 던져넣고 새로운 자동차까지 굴러가도록 만드는 게 제 몫이라는 것만 빼면요...
I 서비스의 시작부터 정식 릴리즈까지 걸린 시간은 최소 2년 이상입니다. 규모에 비해서 걸린 시간이 심상치 않아보이지만 그건 그러려니 합시다. 어쨌거나, B 그룹은 master 브랜치라는 추악한 과거는 잊고 develop 브랜치에서 완전히 새로운 시작을 하기로 마음먹었습니다.
아니 그러면 master와 연결된 상용서버는요? 그건 그거고 이건 이거죠. master는 master대로 유지보수가 이루어졌습니다. 그리고 develop에서는 완전한 마개조가 이루어집니다.
Java 11과 Spring Boot 2.x는 어느샌가 Java 17과 Boot 3.x 버전으로 변경되었습니다. 어떤 의존성은 변경되거나 사라졌습니다. 엔티티부터 컨트롤러까지 전방위적인 수정이 이루어졌습니다.
더 이상 master와 develop은 겉보기엔 비슷해보이지만 같은 프로젝트라고 부르기 민망한 수준이 되어버렸습니다. 그 결과가 develop - relase 사이의 670개의 파일 변경, 21,781개의 추가 그리고 14,394개의 삭제내역입니다.
시간이 흘러흘러 바퀴 한 쪽이 펑크난 채 데굴데굴 굴러가던 V1 자동차는 그 수명을 다할 시간이 다가왔고...
400개의 커밋을 통해 완전히 달라진 모습을 가지게 된 V2 자동차는 어느새 릴리즈를 목전에 두고 있습니다. (V2 = B 그룹의 develop 브랜치).
원래 계획은 달리는 V1의 바퀴를 갈아끼는 것이었습니다. 하지만 계획이 틀어지면서 V1은 그대로 펑크난 채로 달리게 내버려두고 새롭게 V2를 굴러가게 만드는 것이 새로운 목표가 되었습니다.
이 상황이 마냥 답이 없는 것은 아닌게, V2의 경우 V1의 API 스펙을 동일하게 지원합니다. 갑자기 수동 변속기에서 자동 변속기로 변한 것은 아니라는 것이죠.
단 API 스펙만 같다는 것이지 내부 구현은 다르기 때문에 동작이 동일할 것이라는 보장은 없습니다. 겉보기엔 멀쩡해도 언제 어디서 호환성 문제가 발목을 잡을지 모릅니다.
그리고 솔직히 말해서 V2의 로직도 그닥 보기 좋은 편은 아닙니다. V1이니 V2이니 해봤자 수동 변속기일 뿐이고 자동 변속기가 더 편한 건 맞잖아요? 오늘의 피쳐는 내일의 레거시를 의미하니...
그래도 API 스펙이 맞는다는 보장만 있으면 master와 develop의 차이가 크다는 것은 원래였으면 문제가 없었어야 합니다. 그냥 re-write 해버리고, 문제 생기는 부분만 맞춰가면 되니까요. 하지만 세상 일이 그렇게 간단한 것은 아닙니다.
여기서 몇 가지 요구사항이 추가되었습니다.
V1에서 V2로 마이그레이션하면서 V1을 없애버리는 것이 아니라,V1 역시 V2 릴리즈 시점으로부터 4~5개월 간 지원되어야 한다는 것입니다.
이러한 선택을 내린 이유는, 현재 I 서비스의 프로덕션 환경은 버전업에 대한 대응이 되어있지 않기 때문에, V1을 내리게 되면 기존에 V1을 사용하고 있는 유저들은 서비스를 사용할 수 없게 됩니다. 왜 사용할 수 없지? 에 대해서 알려주지 못하고 그냥 앱이 뻗어버리는 상황에 마주하게 되는 것입니다.
물론 스토어에서 업데이트를 하면 되겠지요. 하지만 유저들이 크래시 이슈를 겪고 "아 이건 업데이트가 필요한 상황이구나!" 라고 생각할까요, 아니면 "아 이 앱은 더 이상 운영을 안 하는구나!" 라고 생각할까요, 그것도 아니면 "아 그냥 귀찮은데 안 써야지" 할까요? 아무래도 마지막이 제일 가능성이 높지 않겠습니까?
언젠가는 V1 없이 V2만 올라가야 하는 시기가 오겠죠. 하지만 그 전에 유저들에게 버전업에 대응할 수 있는 기간을 주어야 합니다. 저희는 그 기간을 4~5개월로 산정했고, 그때까지 V1와 V2를 동시에 운영할 수 있어야 합니다.
The only thing a Big Bang rewrite guarantees is a Big Bang! - Martin Fowler
저 문구를 클린코드에서 봤을 때만 하더라도 "에이 ㅋㅋ 요즘 세상에 누가 저런 식으로 작업을 함 ㅋㅋ" 이라는 생각이었지만 직접 겪어보니 느낌이 새롭습니다. 역시 세상에 '절대' 라는 건 없는 법이죠. 아무튼 저는 저희 서비스를 펑 하고 터트리고 싶지는 않습니다. 어떻게 잘 해결할 방법을 찾아야 합니다.
현재 프로젝트에서는 master - release - develop 형태의 git-flow (였던 것) 을 사용하고 있습니다. 왜 과거형이냐면 V1과 V2가 서로 결별하면서 우리가 생각하는 git-flow와는 조금 다른 형태가 되어버렸기 때문이거든요.
프로덕션 환경에 반영해야 하는 기능이나 수정사항이 생기면 어떻게 하나요? 먼저 develop 브랜치에서 feature 브랜치를 판 다음 develop으로 머지하고, 그렇게 develop에 쌓인 기능들을 릴리즈 할 수 있도록 만들기 위해 release 브랜치로 내보냅니다. release 브랜치에서 프로덕션 환경에 나갈 준비가 완료된다면 최종적으로 master에 해당 내용이 반영됩니다.
하지만 지금 V2(develop)은 V1(master)에 들어갈 수 있는 상태가 아닙니다. 하지만 B 그룹은 V2에 빅뱅 커밋기록들을 쌓아가면서도, 프로덕션 환경인 V1에 필요한 기능들이나 수정사항들을 반영해야만 했습니다. 그렇다면 V1에 필요한 기능들은 어떻게 추가했을까요?
맞습니다. release 브랜치에서 새롭게 feature 브랜치를 파서 작업했습니다.
즉 일반적으로 생각하는 develop - feature 구조가 release - feature 사이에 적용되었습니다. 당연히 V1의 master - release - feature 에서의 변경사항과 V2의 develop - feature 변경사항은 동기화되지 않았고 그 차이는 점점 벌어지다 못해 요지경에 이르게 된 것입니다.
현재 각 브랜치는 CI/CD 워크플로우를 통해 다음과 같이 연결되어 있습니다.
master -> PROD 서버
release -> 사내 온프레미스 서버
develop -> DEV 서버
V1은 그동안 신규 기능 개발 없이 버그 픽스만 이루어지고 있었기 때문에 release 브랜치와 연결된 온프레미스 환경은 사실상 사용하지 않고 있습니다.
이번에 저희 팀의 인프라를 AWS CodeDeploy + S3 기반 배포에서 AWS CodeDeploy + ECR 기반 컨테이너 환경으로 마이그레이션하고자 합니다 (이 부분에 대해서는 나중에 다루겠습니다).
이때 각 빌드될 이미지의 태그를 지정할 수 있는데요, develop 브랜치의 경우 {develop_image}:{GITHUB_SHA::6}
과 같이 커밋 해시 6자리를 사용합니다. master 브랜치의 경우 production_image}:{x.y.z}
와 같이 시멘틱 버저닝을 사용합니다.
이를 위해서 Git 브랜치 전략을 재수립하고, V1과 V2에 대한 대응방침을 정리했습니다.
release 브랜치를 제거했습니다. 본래 release 브랜치의 목적은 develop에서 개발된 기능들을 릴리즈 단위로 묶고, 이 기능 묶음이 정상적으로 동작하는지 테스트하기 위함입니다. 하지만 저희 팀에는 release 브랜치 테스트를 위한 인력이 존재하지 않는 상황이고, 이를 테스트할 방법도 존재하지 않습니다 (별도 서버 없음). 릴리즈 기능 묶음은 오로지 master를 통해 prod 서버에 올라간 시점부터 테스트가 가능합니다.
따라서 master, develop 두 메인 브랜치와 feature, hotfix 나머지 두 개의 보조 브랜치로만 프로젝트를 관리하도록 지정했습니다.
새로운 브랜치 전략을 도입하기 위해서는 선결조건이 필요합니다. V1이 V2 릴리즈 후에도 4~5개월동안 배포되어야 한다는 요구사항을 충족시킬 수 있어야 한다는 겁니다. 이를 해결하기 위해 다음과 같이 결정했습니다.
먼저 현재 master의 head commit을 기반으로 v1.0.0
을 릴리즈합니다. 해당 릴리즈를 태그로 가지는 이미지를 빌드하여 ECR에 푸시합니다.
아직 release에 존재하는 변경사항을 master로 푸시하여 v1.1.0
으로 릴리즈합니다. 해당 릴리즈를 태그로 가지는 이미지를 빌드하여 ECR에 푸시합니다. 이후 release 브랜치를 삭제합니다. (v1.0.0
과 v1.1.0
사이의 호환성 문제 해결 위함)
이제 해당 상태에서 현재 레포지터리를 클론합니다. 클론된 V1 레포지터리, 원본을 V2 레포지터리라 하겠습니다.
master
브랜치에서는 v1.1.0
~ v1.1.x
의 릴리즈를 관리합니다. 이제 develop
브랜치의 내용을 master
로 머지합니다. conflict가 발생하더라도 develop
브랜치의 내용을 우선합니다.
V2 레포의 master
의 CI/CD 워크플로우는 V2 EC2 인스턴스를 대상으로 수행됩니다.
이번 글에서는 기존 레거시 환경의 브랜치 전략을 파악하고, V2 릴리즈를 준비하면서 V1에 대한 상호운영이 가능하도록 해당 전략을 수정했습니다. 다음 시리즈에서는 V2를 운영할 EC2 인스턴스를 생성하고, AMI 및 인스턴스 유형 등에 대한 의사결정 과정을 정리해보도록 하겠습니다.