LY Corporation Tech Blog

LY Corporation과 LY Corporation Group(LINE Plus, LINE Taiwan and LINE Vietnam)의 기술과 개발 문화를 알립니다.

HeadVer - 기민한 프로덕트 팀을 위한 새로운 버저닝 시스템

안녕하세요. ABC Studio 김영재입니다. 저는 LINE이 2020년에 인수한 일본 최대 규모 배달 서비스 데마에칸(出前館, Demaecan) 프로덕트를 담당하고 있습니다.

HeadVer는 제가 2019년에 고안하고 30여 개 프로덕트에 적용한 후 2021년에 LINE GitHub 공간에 공표한 버저닝 규칙입니다. 현재 HeadVer를 사용하는 프로덕트는 제가 이름을 아는 프로덕트만 70여 개에 이르며, 전 세계적으로도 많은 프로덕트에서 사용하고 있을 것으로 예상합니다.

데마에칸에도 물론 적용했으며, 현재 데마에칸의 5개 앱에서 모두 통일된 버저닝 규칙을 사용하기에 모두가 앱의 현황을 더 쉽고 빠르게 파악하고 있습니다. 이번 글에서는 5년간 HeadVer를 여러 프로덕트에 적용해 보고 피드백을 받으면서 느낀 점을 공유하고자 합니다.

도대체 버전이 뭐가 문제야?

문헌정보학 자료에서는 돌에 글자를 새기던 시절에도 같은 내용을 누가 받아썼냐에 따라 구분하는 버전 개념이 있었다고 합니다. 버전을 구분하기 위해 사용하는 버전 형식의 조건으로는 여러 가지가 있겠지만 대표적으로 아래와 같은 조건을 꼽을 수 있습니다.

  1. 시간에 따라 순차적이고 고유한 버전을 사용해야 한다.
  2. 버전 간의 관계나 차이를 추적할 수 있는 형식을 사용해야 한다.
  3. 한 번 새긴 버전은 바뀌거나 변하지 않아야 한다.
  4. 당연하겠지만, 사람이 읽을 수 있는 형식을 사용해야 한다.

그러므로 명확히 정해 놓은 표기 규칙이 없던 시절일지라도 글을 작성한 날짜를 함께 적어 놓았다면 그것이 곧 버전이라고 볼 수 있습니다.

소프트웨어 엔지니어링과 프로덕트를 만드는 프로세스에서 버저닝이 미치는 영향이 큰지 작은지는 모르겠습니다. 팀마다 프로세스는 다르니까요. 다만, 버전이 프로덕트를 패키징할 때 가장 먼저 보이는 식별자이자 현재 사용 중인 소프트웨어가 최신인지 아닌지를 구분할 수 있는 유일한 커뮤니케이션 수단이라는 것은 분명합니다.

버전이 없다고 생각해 봅시다. 그렇다면 버전 대신 MD5와 같은 해시 알고리즘의 결괏값으로 패키징한 프로덕트를 불러야 할 수도 있습니다. 대표적인 예로 Git의 서브 모듈 기능을 버전처럼 활용하는 경우가 있는데요. 명확한 태그를 지정하지 않으면 그저 커밋 해시값 앞의 16진수 몇 자리로 구분할 수밖에 없습니다. 아래는 PayPal의 bootstrap-accessibility-plugin 저장소를 캡처한 것입니다. Git 서브모듈 기능을 활용해 bootstrap 원본 저장소의 ab6f79c라는 커밋 지점을 참조하고 있고, 커밋 메시지로는 3.3.0 버전으로 업데이트했다고 쓰여있습니다.

하지만 아래에서 볼 수 있듯, bootstrap v3.3.0의 커밋 해시는 16dbdbd입니다.

커밋 메시지가 잘못된 것인지 참조 지점이 잘못된 것인지 알 수 없습니다. 버그가 발생하면 정확한 버전이 무엇인지 확신할 수 없기에 재현하기가 어렵습니다.

이처럼 버그가 발생했을 때 정확한 바이너리를 선택하고 재현하는 일 또한 버전 정보을 정확히 인식하는 단계부터 시작합니다. CS(customer service)에서 문제가 발생한 버전을 어떻게 수집하는지, 그 버전을 메신저나 인트라넷에서 어떻게 지칭하는지, 버그를 해결한 버전을 다시 사용자와 CS에서 어떻게 인지하고 구분하는지 살펴볼 필요가 있습니다.

다시 말해 버전이란 기능적으로는 하나의 릴리스 단위를 지칭하는 표시일 뿐이지만, 시스템을 둘러싼 사람들의 커뮤니케이션 수단이기도 합니다. 따라서 버저닝 규칙과 버전 형식을 어떻게 정하는지에 따라 커뮤니케이션 효율이 달라질 수도 있고, 프로덕트를 릴리스할 때까지의 프로세스가 달라질 수도 있습니다.

프로덕트와 프로세스를 관찰해 보니

바람직한 버저닝은 무엇일까?

그렇다면 소프트웨어 개발에서 바람직한 버저닝은 무엇일까요? 프로덕트를 만드는 프로세스가 진행되는 과정에서 버전과 관련된 질문으로 어떤 게 있었는지 돌아보면, 아래와 같은 질문들이 참 많았습니다.

"언제 릴리스된 거예요?"
"이 버그는 언제 수정된 거예요?"
"버그 신고가 왔던데 버전이 몇이에요?"
"이번에 릴리스될 버전은 뭐예요?"
"그 버전이 최신이에요?"

모두 한 번쯤은 들어본 질문이라고 생각합니다. 제 경험상 시기와 연관된 질문이 많았고, 다음 릴리스 버전이 무엇인지 묻는 경우나 디버깅하기 위해 정확한 버전을 알고 싶어 하는 경우도 많았습니다. 여기서 유추할 수 있는 바람직한 버저닝의 답은, 역설적으로 버전에 관한 대화를 줄일 수 있는 버저닝이라고 말할 수 있겠습니다. 그런데 고작 숫자 세 개만으로 어떻게 하면 이런 질문을 대폭 줄일 수 있을까요? 

SemVer를 사용하면 되나요?

SemVer는 major.minor.patch로 의미(semantic)를 부여한 버저닝 시스템으로, 오늘날 소프트웨어 버저닝에서 가장 광범위하게 쓰이는 방식입니다. SemVer 링크를 열면 아래 소개말이 나오는데요. 소개말을 읽어보면 이 버전 규칙의 목적은 API와 의존성 관리에 있다는 것을 알 수 있습니다.

이 시스템이 동작하려면, 먼저 공개(public) API를 선언해야 한다. 문서와 소스 코드 자체로 드러낼 수 있다. 어떤 방식이든 API가 명확해야 한다. 한번 공개 API를 정의하고 나면, 버전 번호를 올리는 방식을 통해 API가 어떻게 바뀌는지 표현한다.

그렇습니다. 만일 여러분이 API가 아니라 일반 사용자를 위한 앱이나 웹 서비스에서 SemVer를 사용하고 있다면, SemVer는 사실 그런 프로덕트를 위한 버저닝은 아닙니다. 그렇다고 쓰지 말라는 것은 아닙니다. 그저 SemVer는 명세서의 1번부터 'API 관리'라는 목적만 집요하게 생각했을 뿐, 최종 사용자(end-user)가 사용하는 프로덕트를 고려한 적도 없고 그런 프로덕트에 사용하기에 적합하다고 한 적도 없었다는 점입니다.

그렇다면 최종 사용자를 염두에 두고 버저닝을 한다면 무엇을 더 고려해야 하고, 어떤 차이가 있을까요? 아래 스크린숏은 저희 사내 위키에 있는 어느 웹 서비스의 릴리스 이력입니다. 흔히 볼 수 있는 모습일 것입니다. 기록을 보니 제일 위의 1.8.8 버전은 2021년 1월에, 1.9.8은 2023년 8월의 버전입니다. 10여 번의 릴리스가 약 30개월에 걸쳐 진행된 셈이니 대략 3개월 단위로 꾸준히 릴리스하는 프로덕트로 보입니다.

이 숫자로 유일하게 알 수 있는 것은 그저 숫자가 꾸준히 증가했다는 사실이며, 문제가 있다면 그것 외에는 알 수 없다는 점입니다. 물론 만든 사람들은 나름의 의미를 부여하며 숫자를 올렸을 것이라고 생각합니다. 하지만 사용자에게는 이 숫자가 어떤 의미가 있을까요? 또한 팀 안에서는 10여 개의 버전 사이를 어떤 의미로 구분 짓고 있는 것일까요? 설마 1.9 버전을 9번 내놓는 동안 새로운 기능 추가 없이 패치만 9번 했다고 생각되진 않는데 말이죠.

그렇게 저는 버전을 둘러싼 질문을 최소화하면서 숫자만으로도 충분히 정보를 전달할 수 있는 새로운 버전 규칙을 만들 필요가 있겠다고 생각했습니다.

HeadVer 설계하기

수학에서 제가 좋아하는 표현이 있습니다. - "모든 숫자에는 의미가 있다"

새 버전 규칙을 생각하면서 이 말마따나 숫자 하나하나에 명확한 기능과 의미를 부여하고 싶었습니다. 첫 번째 숫자를 사람들은 어떤 일을 할 때 입에 올릴까요? 각 자리마다 숫자가 하나씩 올라가는 것은 어떤 의미가 있으며, 어떤 상황에서 바뀌는 게 좋을까요? 그렇게 각 자리 숫자를 하나씩 매만져가며 생각했습니다.

첫째 자리(Head, 수동 설정)

수많은 프로덕트의 버전을 보며 한 가지 의문이 들었습니다. 앞서 살펴본 예에서도 볼 수 있듯 대부분의 프로덕트는 버전의 첫째 자리가 오랜 시간 1에 멈춰 있습니다. 도대체 언제 2가 되는 것일까요?

SemVer에서는 첫째 자리(major)의 변화를 '당신의 경험이 박살 날 수 있다(breaking change)'라는 경고성 의미로 설정하고 있습니다. 하지만 평범한 서비스 프로덕트에서 사용자의 경험을 박살 내려고 했다가는 회사가 박살 날 가능성이 크므로 거의 발생하지 않는 일입니다. 그렇다 보니 첫째 자리를 올릴 만한 경우를 거의 찾기 어렵다는 게 저의 해석입니다. 그 결과, 첫째 자리는 수년간 변화 없이 고정값으로 낭비되는 게 오늘날 흔히 보이는 프로덕트 버전의 모습입니다. 구조적으로 크게 변경했거나 큰 기능을 릴리스했을 때 세리머니의 의미로 올리는 모습도 종종 볼 수 있습니다.

HeadVer에서는 첫째 자리를 빠르게 증가시키는 것을 권합니다. 권장하기로는 사용자에게 도달하는 횟수마다 증가하면 좋습니다. 처음에 이 아이디어를 들은 동료들은 걱정했습니다. 2주에 1씩 올라가면 1년 만에 24가 넘을 수 있다고 말했습니다.

"음... 그게 뭐가 문제죠?"

그렇습니다. 첫째 자리가 1000을 넘어도 아무런 문제가 되지 않고 이상할 일도 없습니다. 어쩌면 우리는 첫째 자리가 작아야 한다는 고정관념에 갇혀 있었는지도 모릅니다.

저 역시 처음에는 어색한 느낌이 있었습니다. 하지만 첫째 자리가 커도 괜찮다는 의식을 심어준 것은 엉뚱하지만 어릴 적 NVidia GeForce 그래픽카드 드라이버를 설치할 때였습니다. 그 당시 드라이버 버전은 41.몇으로 기억하는데요. 여러 드라이버를 연이어 설치하던 중 도드라지게 숫자가 커서 '와! 버전이 높네. 엄청 발전했나봐'라고 생각했던 기억이 아직도 납니다. 글을 쓰는 시점의 GeForce 드라이버는 아래 그림과 같이 551.61입니다. 첫째 자리가 500을 넘었지만 아무 문제도 없습니다.

숫자가 높고 심지어 빠르게 증가하면 제가 어릴 적 느꼈던 '와! 정말 발전이 빠른가 보다'라는 감각을 전해줄 수 있습니다. 마치 자동차 속도를 mph보다 km/h로 표시하면 더 빠르다는 착각이 드는 것과 같습니다. 숫자를 말할 때마다 빠르게 발전하고 있다는 감각과, 그 감각으로 인한 추동력을 팀에 자연스럽게 만들어 줄 수 있습니다. 

둘째 자리(YearWeek, 자동 설정)

경험상 소프트웨어 버전을 언급하는 질문 중 가장 많은 질문은 시간에 관한 질문이었습니다. 그래서 버전에 시간 개념만 넣어도 정보력이 부쩍 올라갈 것이라고 확신했습니다. 버전에 시간 정보를 넣은 대표적인 사례는 CalVer (Calendar Versioning)입니다. 이 버전 규칙을 창시한 분도 저와 같은 관점이었을 것이라고 생각합니다.

다만 시간을 어느 정도의 정밀도로 넣을지는 고민이었습니다. 프로덕트 버전에 적당한 시간의 정밀도는 어느 정도일까요? 버전에 날짜를 넣는 형태 중 가장 일반적인 형식은 YYYYMMDD 형식이고, 게임 회사에서 자주 사용됩니다. 게임 회사는 해킹 방지를 위한 패치가 잦고 대규모 이벤트에 맞추어 릴리스할 때도 많아서 높은 정밀도가 요구됩니다.

반면 CalVer는 첫 자리를 YYYY 형식의 연도로 할당하는데, CalVer 소개 문서에도 비교적 호흡이 길고 규모가 큰 프로젝트에 어울린다고 말합니다. 그리고 제가 관찰하거나 경험한 많은 서비스는 두세 주 단위의 템포였습니다. 스프린트를 진행하는 팀을 봐도 대부분 주 단위로 실행하는 것을 볼 수 있었습니다. 

시간의 정밀도와 표현에 있어서 아래와 같은 후보를 두고 생각했습니다.

구성예시장점단점
긴 날짜20240223
  • 제일 명확하다.
  • 8자리는 너무 길다.
짧은 날짜240223
  • 명확하다.
  • 6자리도 길다.
  • 숫자 4개가 넘어가면 차이를 한 눈에 알아채기 어렵다.
연도 + 월2402
  • 익숙하다.
  • 정밀도가 낮다.
  • 뒷자리의 범위는 1-12뿐이므로 표현력이 낮다(정보 공간의 12%만 활용).
연도 + 주 차2408
  • 연도는 명확하고, 표현 범위도 1-53으로 적당하게 활용할 수 있다.
  • 주 차 표현이 대중적이지 않다.
  • 계산이 필요하므로 자동화가 필수적이다.

여러 사례를 종합해 본 결과, 통상적인 소프트웨어 개발에서는 주 단위의 시간 정밀도면 프로덕트의 버전에 적당하다고 생각했습니다. 그리고 여러 실험을 거친 결과 주 차(week number)를 활용하기로 결정했습니다.

저에게 이것은 모험이었습니다. 서구권에서는 달력에도 몇 주 차인지가 쓰여 있는 등 비교적 주차의 활용도가 높지만 우리에겐 어색하기 때문입니다. 저 역시 소개하는 입장에서 학습 비용이 올라간다는 게 마음에 걸리긴 했습니다.

하지만 쉽게 이해할 수 있도록 설명해서 단점을 장점으로 승화시키면 어떨까요? 예를 들어, 1년이 52주(4년마다 53주)라는 것은 상식의 영역에 속한다고 할 수 있지만, 33주 차가 어느 달이나 어느 계절에 속하는지는 감이 잘 안 올 수 있습니다. 2024년을 기준으로 주차와 달, 계절의 대략적인 관계는 아래와 같습니다.

주차1~910~19(10번대)20~29(20번대)30~39(30번대)40~49(40번대)50~53
시작하는 날2024-01-012024-03-042024-05-132024-07-222024-09-302024-12-09
마지막 날2024-03-032024-05-122024-07-212024-09-292024-12-082024-12-31
시간 감각연초여름가을겨울연말


위 표와 같은 정보를 학습해 각 주차에 대한 대략적인 감각을 한 번 익히면, 몇 개월 또는 몇 년 지난 버전을 보면서 그 버전이 나온 시기가 상반기인지 하반기인지 혹은 어느 계절에 나온 것인지를 쉽게 파악할 수 있습니다. 예를 들어 30번대 버전이라면 대략 가을에 나온 버전이라는 것을 알 수 있는데요. 실제로 이 정보를 아는 것만으로도 그 당시에 있었던 여러 가지 상황이 상기되면서 큰 도움이 되는 경험을 했습니다.

주 차를 다루면서 놀란 점은 의외로 주 차를 계산하는 규칙이 여러 가지(참고)였다는 점입니다. 그렇다 보니 날짜 계산 라이브러리마다 다른 값을 줬는데요. 여러 규칙 중 엔지니어에게 가장 친숙한 날짜와 시간에 관한 표준은 ISO8601이므로, HeadVer 또한 ISO8601 표준을 따르기로 했습니다.

어찌 됐건 프로그램이 계산한 결과를 믿어야 하는 것인데요. 이참에 잘 됐다고 생각하고 이 버전 규칙을 사용하려면 반드시 프로그램을 거쳐서 기계적으로 나오는 값을 사용하도록 강제하기로 결정했습니다. 버전 규칙을 오류 없이 적용하려면 어차피 사람이 설정하는 게 아닌 빌드할 때 자동으로 찍도록 만들어야 하므로 좋은 제약이라고 생각했습니다.

셋째 자리(Build, 자동 설정)

가장 고민을 적게 한 부분은 마지막 숫자입니다. 처음부터 이것은 빌드 번호가 되어야 한다고 생각했습니다. 여러 프로덕트를 관찰해 보면 마지막 숫자를 빌드 번호로 두는 게 꽤 일반적이라는 것을 알 수 있습니다. 

빌드 번호는 숫자만으로 소프트웨어 산출물을 구분할 수 있는 가장 간단한 방법입니다. 만약 빌드를 두 번 연달아 했음에도 둘 사이에 동일한 빌드 번호가 부여된다면 빌드 파이프라인을 점검할 필요가 있습니다.

코드가 달라지지 않았다고 동작이 같을 것이라고 짐작하는 것은 위험합니다. 빌드한 시점의 타임스탬프를 파라미터로 활용하는 함수도 있습니다. 그 외에 파일 용량이나 바이너리 패턴으로 무결성을 점검하는 후처리 과정이 있으면 주석만 추가해도 패키징된 결과물의 비트스트림 순서가 달라지고 점검 결과도 달라집니다.

이처럼 같은 코드일지라도 결과가 달라질 수 있으며, 빌드할 때마다 고유하게 취급하지 않으면 디버깅할 때 온전히 재현하지 못하는 문제가 발생할 수 있습니다. 

셋째 자리는 앞서 소개한 두 숫자보다 정밀도가 높습니다. 빌드 과정 중에 빌드 번호가 결정되므로, 두 번째 숫자와 마찬가지로 이 또한 자동으로 부여할 수 있습니다. 빌드 번호를 자동으로 하나씩 올리려면 빌드 파이프라인의 한 지점에 명확히 기능을 만들 필요가 있습니다. 통상적으로 빌드 서버를 두며, 이것이 최소한의 빌드 파이프라인이 됩니다.

Head.YearWeek.Build

여기까지 세 개의 숫자를 하나씩 살펴보며 각 자리의 용도가 무엇이고 어떻게 부여하기로 결정했는지 말씀드렸습니다. SemVer의 {Major}.{Minor}.{Patch}처럼 각 자리마다 적절한 의미를 붙여서 {Head}.{YearWeek}.{Build}라고 이름을 붙였습니다. HeadVer라는 이름에는 '이 버저닝에서 신경 쓸 것은 Head 밖에 없다. 나머지는 자동이다'라는 의미를 담고 있습니다.

위 예시를 읽으면 "사용자에게 세 번째 전달한, 2024년 8번째 주(대략 연초)에 릴리스했던, 빌드 79번"입니다. 이렇게 모든 숫자마다 의미를 가질 수 있게 되었습니다.

메타데이터 활용

SemVer에는 위의 세 자리 숫자 다음 +기호를 붙인 후에 몇 글자를 넣을 수 있는데, 이 부분을 메타데이터라고 합니다. 메타데이터 값은 버전 값으로 취급하지 않는 부가 정보일 뿐이므로, 서비스의 특징과 용도에 따라 자유롭게 붙여서 활용하실 수 있습니다. 통상적으로는 Git의 해시값을 붙이거나 버전의 별명을 붙이곤 합니다. 빌드 번호를 붙이는 경우도 있습니다.

HeadVer의 경우 마지막 자리가 빌드 번호이므로 Git의 해시값은 중복 정보에 가깝습니다. 그래서 저희의 경우 앱은 +iOS, +Android와 같이 OS 플랫폼 식별자를 넣곤 합니다. 플랫폼 정보를 넣으면 서버에서 특정 OS에서만 일어나는 문제인지 더 쉽게 파악할 수 있습니다.

위 그림은 한 앱이 릴리스한 후에 Google Analytics 대시보드에서 인스톨 추이를 나타낸 예시입니다. 단지 '+Android' 정보를 추가한 것만으로도 OS 단위의 추이를 구분해 파악할 수 있습니다. 위 예시에서는 Android 외 OS는 iOS뿐이므로 iOS는 붙이지 않았습니다.

플랫폼 식별자는 하나의 예일뿐입니다. 어떤 정보를 추가했을 때 버전의 정보력이 올라가고 팀의 커뮤니케이션이 더 수월해지는지를 관찰해 메타데이터 정보를 선택하는 게 좋습니다.

지금까지 HeadVer의 구성에 대해 설명했습니다. 이제 이 버저닝 규칙을 적용했을 때 프로덕트를 만드는 프로세스가 어떻게 달라지는지 알아보겠습니다.

고작 숫자 세 개로 프로세스가 좋아질 수 있을까

처음부터 구축하는 단단한 빌드 파이프라인

앞서 말했듯 HeadVer를 적용하려면 둘째 자리와 셋째 자리를 자동으로 지정할 수 있어야 합니다. 그렇다 보니 소프트웨어 빌드 파이프라인을 제대로 구성해, 시스템이 빌드 과정 중에 버전을 정확히 생성할 수 있도록 할 필요가 있습니다.

많은 엔지니어가 프로덕트 수준이 한 단계 올라선 후에야 빌드 파이프라인을 구축하곤 합니다. 하지만 제가 이전에 작성했던 오픈소스답게 소프트웨어 설계하기에서 언급했듯이, 설정 파일을 비롯해 빌드 파이프라인 구축까지 프로젝트를 처음 시작할 때 환경부터 갖춰 놓으면 좋습니다.

자동화가 가능한 구조를 미리 만들어 놓고 빌드와 릴리스를 할 때마다 적절한 메시지를 동료들에게 전달하는 게, 나중에 낡은 프로세스를 고치는 비용보다 언제나 더 저렴하기 때문입니다. 

버전이란 소프트웨어 패키지에 붙은 태그라고 말할 수 있습니다. 버전 숫자는 프로그램의 일부도 아니고, 프로그램 외부에 붙어 있는 식별자일 뿐입니다. 아래 그림처럼 GitHub에서도 버전은 태그의 한 부류로 취급하고 있습니다. 작성했던 태그 중 하나를 선택하고 릴리스 노트를 작성하면, 선택한 태그를 그대로 버전으로 취급하고 릴리스 결과로 패키징합니다.

가장 순수한 커밋 로그는 프로그램의 동작을 변화시키는 소스 코드의 변경 내역만 담고 있는 커밋 로그입니다. 버저닝을 커밋 로그에 혼입하지 않으려면 빌드 파이프라인을 제대로 만들고, 어느 시점에 버저닝이 이뤄져 산출물에 버전이라는 도장을 찍는지 명확하게 구분할 필요가 있습니다. 이를 돕고자 HeadVer 저장소에는 프로그래밍 언어마다 적용 가능한 스크립트가 있으니 참고하시기 바랍니다.

이처럼 HeadVer를 적용한다는 결정만으로도 빌드 파이프라인을 구축할 수 있으며, 팀은 릴리스마다 정확한 버전으로 대화할 수 있게 됩니다.

개발자와 QA 사이의 명확하고 효율적인 커뮤니케이션

저희는 QA 프로세스가 꽤 엄격하게 자리 잡고 있습니다. 개발 완료 후에는 최소 1주 이상의 QA 과정을 거치는데요. QA 기간 중에 버그 등록과 버그 수정의 순환이 매일 몇 차례씩 진행되며, 이를 'Dev/QA 루프'라고 부르고 있습니다. 전체적인 흐름은 아래 그림과 같습니다. 

Dev/QA 루프를 몇 회전 거치면서 버그 레벨(critical-major-minor-trivial) 중에 major 레벨 이상의 버그가 0건이 되면 릴리스할 수 있습니다. 이 과정에서 QA 팀이 정확한 버전으로 신고하고, 개발 팀도 정확한 바이너리로 버그를 재현하는 게 중요하며, 이 역할을 마지막 자리의 빌드 번호가 담당합니다. QA는 테스트를 하며 기록한 버전에 빌드 번호가 있으므로 정확한 버전으로 신고할 수 있고, 개발자는 커밋에 기록된 버전 태그를 보며 정확한 지점에서 체크아웃하고 재현할 수 있습니다. 

Minor 레벨로 밀려난 버그라 할지라도 언젠가는 수정될 텐데요. 아래 그림은 이슈 트래커에 등록된 어느 Android 앱의 버그 티켓 중 하나입니다. 버전 앞에 AND라는 접두어로 Android 앱을 구분하고 있습니다. 

위 그림을 보면, 5.2045.119 버전에서 발견한 버그이며 6.2047.124 버전에서 수정했다고 나와있습니다. Head값이 5일 때 신고됐고 6에서 해소된 것이니 이 버그는 Dev/QA 루프가 아니라 다음 릴리스에서 해소된 이슈라는 것을 알 수 있습니다. 또한 그 사이의 기간은 약 2주(2020년 45번째 주와 47번째 주)라는 것도 읽을 수 있습니다. 아마도 개발자가 버그를 해결할 때 빌드 119번 바이너리로 재현이 잘 되었나 봅니다.

이처럼 버전을 통해 버그의 상세한 원인이나 해결 방법까지 깊게는 모를지라도, 티켓에 등록된 버그가 언제 어떤 단계의 릴리스에서 해소돼 사용자에게 나갔는지는 알 수 있습니다.

보다 계획적인 프로덕트 기획

HeadVer에서 사람이 수정할 수 있는 것은 head 숫자 하나밖에 없습니다. 그렇기에 다음 버전을 기획할 때도 단 하나의 숫자만 말하도록 강제합니다.

아래 그림은 가맹점에서 사용하는 태블릿 앱의 프로덕트 릴리스 계획입니다. 그림과 같이 프로덕트 매니저는 head 버전마다 릴리스할 주요 Epic을 넣고 관리합니다. 이렇게 하면 프로덕트의 주요 기능이 언제 릴리스될지 보다 정확하게 예측할 수 있으므로 프로덕트 팀 전체적으로 안정감이 높아지고 커뮤니케이션 비용도 줄어듭니다.

Head 숫자가 다달이 빠르게 올라가는 점도 주목할만합니다. 만일 버전 정보로 1.8.8, 1.8.9, 1.9.0이라고 적혀있으면 어떤 이유로 마지막 숫자를 올리고 가운데 숫자를 올리는지 해석하느라 불필요한 논의와 의문이 나옵니다. IT 서비스는 릴리스마다 사용자 경험이 얼마나 좋아지는지가 중요하지 어느 숫자를 얼마나 올리는지는 그다지 의미가 없습니다. 사용자에게 어떤 가치를 주는지 구분할 수만 있어도 충분합니다.

한편, 새 버전의 기획을 시작할 때부터 릴리스될 버전을 고정하고 싶어 하는 경우가 있었습니다. 이런 규칙을 가진 프로세스 때문에 'v4.xxxx.xx'라는 다소 지저분한 표기를 사용하는 팀도 있었습니다. 이런 경우, 버전을 왜 고정하고 싶은 것인지 근본적인 원인을 파악하는 게 필요한데요. 지금까지 파악한 결과 '팀의 규칙상 위키 제목에 릴리스 버전을 써야 해서'와 같은 사소한 문서 템플릿의 문제인 경우가 대부분이었습니다.

스테이크 홀더의 관점마다 답을 주는 정보

프로덕트와 해당 프로덕트에서 제공하는 서비스를 바라보는 관계자들의 관심 분야는 서로 무척이나 다릅니다. 관심 분야가 다르기에 나오는 질문도 각양각색입니다. 아래 그림처럼 HeadVer는 여러 관계자들이 던진 다양한 관점의 질문을 하나씩 수집해, 문서나 이력을 뒤적거리지 않고도 대부분의 스테이크 홀더들에게 적절한 수준의 답을 줄 수 있도록 구성했습니다.

프로덕트의 규모가 커질수록 일정한 주기로 릴리스하게 되며, 통상적으로 짧게는 1주에서 길게는 3개월 정도를 릴리스 주기로 설정해 관리합니다. 이때 몇 개월 전 또는 몇 년 전의 릴리스를 기억하는 것은 어렵기에 기록을 찾아보곤 하는데요. 이렇게 기록을 찾을 때 적절한 힌트를 제공하면 더 빠르고 정확하게 찾을 수 있습니다.

모두가 반드시 완벽하고 정확한 답을 기대하고 질문하는 것은 아닙니다. 대략적인 정보로도 충분히 답이 되는 경우가 많으며, 시간에 관한 질문이 대표적인 예입니다. 버그 리포트가 올라온 버전이 언제 릴리스된 것인지 물었을 때 '72일 6시간 전에 릴리스했다'라는 답을 기대하는 사람은 없습니다. 그저 작년 겨울 정도에 릴리스했다는 답으로 충분한 경우도 많습니다. 프로덕트 릴리스 사이클 중에 나오는 버전 관련 질문의 상당수를 단지 숫자 세 개만으로 대부분 해소할 수 있다면 질문과 답변에 소요되는 매우 큰 시간 비용을 절감하는 셈입니다. 

또한 HeadVer를 사용하면 동료와의 대화 효율도 개선됩니다. 기획할 때는 head만 말하고, 디버깅할 때는 build만 말하므로 대화는 간소해지면서 정보는 더 정확해집니다. 현재 저희 팀에서는 v3라고만 말하며 서비스를 기획하고, b79라고 표기하며 디버깅합니다. 의미 없는 1.8.9 같은 숫자를 말하거나 타이핑하면서 숫자를 잘못 전달하는 바람에 발생하는 비용까지 줄이는 셈입니다.

한 회사의 모든 앱을 모두가 같은 규칙으로 해석

여러분의 회사에 앱이 하나 이상이면 각 앱마다 개선 주기도 다르고 스펙을 기록하는 방법도 다를 수 있습니다. 그럴 때 다같이 HeadVer를 사용하면 옆 팀의 앱이라도 주요 기능의 릴리스는 Head 기준으로 찾을 수 있고, 언제 릴리스했는지는 두 번째 자리에서 읽을 수 있고, 버그가 발생했을 때는 빌드 번호를 정확히 말하며 소통할 수 있습니다.

데마에칸도 앱이 5개에 프로덕트 팀은 그보다 훨씬 많다 보니 앱의 버전을 읽는 방법만 통일해도 앱 단위로 정보를 찾고 파악하는 효율이 대폭 올라갑니다. 그뿐만 아니라, head 숫자를 통해서 각 앱의 발전 속도도 간접적으로 알 수 있습니다.

시행착오 - 망치를 만들면 못만 보인다

누구나 규칙 하나를 만들면 모든 곳에 적용해 보고 싶은 욕심이 듭니다. 저 역시 마찬가지였습니다. 하지만, 하나의 규칙이 프로덕트를 만드는 세상 모든 프로세스에 정답을 줄 수는 없습니다. 이번 장에서는 제가 이곳저곳에 적용해 보면서 겪은 시행착오를 말씀드리겠습니다.

핫픽스를 내야 할 때

딱 하나의 아주 작은 버그 때문에 핫픽스를 릴리스해야 하는 경우가 종종 있습니다. 이에 대처하는 방법으로 아래 세 가지가 있습니다.

  1. Head를 올린다: 고작 이 작은 일로 head를 올려야 하나 싶을 때가 있을 텐데요. 사용자에게 도달하는 횟수라는 head 본래의 의미가 있다 보니 head를 주저 없이 올리는 것도 좋습니다.
  2. 가만히 둔다: 어차피 버그를 수정하고 릴리스하면 build 숫자가 올라가므로 build 숫자만 올려서 릴리스해도 됩니다.
  3. 임의의 버전으로 부른다: v22의 핫픽스를 v22.1이라는 임의의 버전으로 부르는 방법입니다. 

마지막 세 번째 방법이 흥미롭습니다. 팀에서 사용하는 모습을 보니 v22.1이라는 임의의 버전을 만든 후 관리하는 경우가 있었습니다. v22.1 문서 페이지를 열면 HeadVer 버전이 기록돼 있으니 어떤 버전인지는 알 수 있었지만, 시스템 어디에도 없는 v22.1이 문서에는 있으니 뭔가 이상했습니다.

이렇게 된 원인은 두 가지였습니다. 하나는 버그로 인한 고객 클레임에 대응해야 하는 CS 및 운영 팀에서 빌드 번호 같은 것은 외우기 힘들어하기 때문에 PM이 그냥 쉽게 v22.1이라고 불러달라고 하는 경우입니다. 또 하나는 이미 v23, v24 등 다음 계획이 있는 상황에서 굳이 핫픽스 때문에 head 숫자를 하나씩 밀어내는 것이 번거로워서 v22.1이 되는 경우입니다.

실제 버전 외에 별명처럼 부르는 버전이 있다는 게 이상하다고 생각했는데요. 이처럼 HeadVer는 빌드 후에 버전이 확정되므로 버전을 미리 고정해 놓는 상황일 때는 어떤 버전이라고 불러야 할지 애매해지는 경우가 있습니다. 하지만 핫픽스가 자주 있는 일은 아니고, 프로덕트의 프로세스 관점에서 head만 수동으로 설정하고 나머지는 자동으로 설정하는 제약의 장점은 여전히 유효하다고 생각합니다.

API 서버에 적용할 때

API에 HeadVer를 적용했을 때의 장점은 버저닝에 더 이상 신경 쓰지 않아도 된다는 점입니다. SemVer에서 Breaking Change를 알릴 때를 보면 SemVer의 major를 수정하든 HeadVer의 head를 수정하든 첫 자리가 바뀌는 것은 똑같습니다.

하지만 몇 번 적용해 보니, API 개발은 외부 동작의 변화 없이 리팩토링하는 경우도 많은데 그때마다 버전이 자동으로 올라가는 게 오히려 API 사용자들을 혼란스럽게 만들 수 있다는 의견이 있었습니다. 그래서 팀 내에서도 API 서버는 HeadVer를 적용하지 않는 경우도 있습니다.

여기서 얻은 교훈이 있다면, 어느 버전 관리 시스템이든 프로덕트와 상황에 따라 적합한 게 따로 있다는 것입니다. 즉, SemVer는 API를 다룰 때 유용하고, CalVer는 대규모이면서 주기가 장기간인 프로덕트에 어울립니다. HeadVer 또한 적합한 프로덕트 대상이 있는데요. 바로 최종 사용자가 있는 서비스 프로덕트입니다.

마치며

이 버전 관리 시스템을 무엇이라고 부를까 꽤 오래 고민했습니다. SemVer처럼 입에 잘 붙는 이름으로 짓고 싶었는데요. 고민이 길어져 2021년 봄에는 이름을 못 붙인 상태로 어떤 세미나에 나가서 'Progressive YearWeek Versioning'이라고 말하기도 했습니다. 참 어려운 이름이었죠.

SemVer와 CalVer 모두 어떤 한 단어를 차용했다는 특징이 있습니다. 저 역시 이 버전 관리 시스템을 단 하나의 단어로 말한다면 무엇일지 생각하다가 첫째 자리를 SemVer의 major와는 달리 head라고 이름 붙인 게 눈에 들어왔고, head만 수동으로 조작할 수 있다는 강제성을 돋보이게 하기 위해 head라는 단어를 전면에 드러내는 게 좋겠다고 생각했습니다. 그렇게 버전 관리 시스템을 창안한지 3년 만에 HeadVer라고 이름을 지었고, 그 후 다양한 프로덕트에 적용한지 또 3년이 지났습니다.

사용자에게 더 빠르게 더 큰 가치를 전달하고 싶으시다면 HeadVer를 여러분의 프로덕트에 적용해 보세요. 몇 번의 릴리스를 겪은 후에는 팀이 대화하는 방식과 프로세스가 보다 효율적으로 바뀌어가는 것을 몸소 경험하실 수 있을 것입니다. 많은 이용 부탁드립니다.