패키지 버전은 어떻게 작성해야할까

semver headver package version develop semantic-release

시멘틱 버전의 의미와 현대적인 방식의 버전 관리 방식을 소개합니다.

true
2021-09-02
Photo by Jimmy Dean on Unsplash

한줄요약

버전 정책은 semver를 사용하되, 개발자가 필수로 사용하는 커밋메세지에 규칙을 정하여 자동화합니다.

버전을 어떻게 하지?

코드를 작성할 때 가장 고민하는 것은 이름을 짓는 것입니다. 하지만, 버전 변경을 정하는 것 또한 매우 큰 고민이기도 합니다. 제가 devops를 맡아서 배포 및 운영 책임을 가지면서 개발자들과 배포용 버전 관리에 대해서 많은 고민을 하고 있습니다. 많이들 semver를 사용하기는 합니다만, 실제 의미로 사용하기 보다는 기간적인 의미를 둘 때가 많습니다. 특히 릴리즈 주기를 지정하는 순간, 버전의 의미와 기간의 의미는 많이 섞이게 됩니다.

저는 k8s를 사용하는 운영자로서 개발자와 빌드된 도커 이미지로 소통하기로 정했는데요. 그러다보니 도커 이미지의 태그가 매우 중요한 정보가 되었습니다. 그래서 개발 버전은 git의 short hash를, 이외에 사용을 위한 버전은 vsemver를 태그하여 태그 정보에 변경 부분을 작성하는 규칙을 정했습니다. 예를 들어 v1.1.3 버전을 배포해야 하는 것이면, 테스트를 거친 어플리케이션의 v1.1.3 버전 이미지를 배포하는 식이지요.

그런데 이런 버전 정보를 본래의 의미에 맞게 올리는 것이 쉬운 일이 아니었습니다.

semver란?

semver의 의미를 설명하는 단락입니다. 충분히 이해하신다면 건너뛰어도 좋습니다.

semver는 오늘날 대부분의 조직에서 사용합니다. 전체 명칭은 Semantic Versioning 이고, 명세서(Specification)를 지칭하는 표현입니다.

명세 전체는 공식 문서를 확인하는 것을 추천합니다.

간단히 축약하면 X.Y.Z 의 형식을 따르는 버전 체계라고 할 수 있습니다. X는 주 버전(Major)으로 0으로 시작할 경우 개발 중임을, 그 이상의 숫자로 표현할 경우 각 버전 내에서 api 명세를 많이 변경하지 않을 것임을 뜻합니다. 0.~ 으로 시작하는 버전은 현재 개발중임으로 중간에 사용법이 자유롭게 바뀔 수 있다는 경고입니다. 대신 개발자에게 유지 책임에서 자유롭게함으로써 초기 개발상황을 반영한 정책이라고 볼 수 있습니다.

Z는 수 버전(Patch)으로 api 명세의 변경 없이 버그 수정 등의 수정이 발생한 경우에만 올립니다. 앞선 버전이 변경될 시 0으로 초기화 합니다.

Y는 직전 버전의 api 명세는 그대로 사용할 수 있으나 Z 버전 업과는 다른 수준의 큰 변경이 있을 때 올립니다. 대표적으로 api 추가나 기존 api를 제거할 예정이라고 알리는 때를 들 수 있습니다. 내부적으로 대대적인 변경이 있을 때에도 api의 변경이 없더라도 올릴 수 있습니다. 예를 들면 의존하는 패키지의 변경이나 파라미터, 결과값에 컬럼이 추가되는 것 등을 들 수 있습니다.

X는 기존 버전을 사용하는 코드를 변경해야만 하는 변화가 있는 경우 올립니다. 단순하게는 함수나 파라미터의 이름이 바뀌었거나 제거된 경우가 있습니다. 혹은 아예 사용 체계 자체가 바뀌는 경우도 주 버전을 올리는 이유입니다.

semver의 문제점

semver의 문제점은 여러 faq나 각 버전을 올리는 규칙을 공유함에도 불구하고, 각 개발자가 판단하는 버전업의 경계가 다르다는 것입니다. 특히 버전을 올리는 것을 각 개발자가 수동으로 수행하기 때문에 생기는 어려움도 발생하였습니다. 저의 경우, 주 버전을 기간에 맞추고 나머지 버전은 무시한채 식별자 변경으로 개발하는 것을 경험하기도 했습니다. 그만큼 변경점에 대한 판단과 버전 변경의 고민을 개발자에게 주면 생기는 문제가 발생하는 것이지요.

주변 분들에게 질문해보니, 듣기 좋은 규칙을 하나 얻었습니다. 버그 수정은 수버전, 기능 추가는 부버전, 제거는 주버전을 올립니다. 큰 변경으로 주버전을 올리는 것은 논란이 되지 않으니 신경쓰지 않아도 되구요. * 관련 아이디어를 공유해주신 코딩맛집yongmin님 감사합니다.

그럼에도 불구하고, 버전을 보는 사람으로 하여금 정보를 전달할 수 있는 방법을 찾는 것이 중요할 것입니다.

headver

이런 문제를 해결하기 위해 line에서는 자체적으로 사용하는 headver를 구성하여 발표하기도 했습니다. headver는 수동 버전 관리의 문제와 여러 버전 체계의 혼란을 최소화하기 위해 주 버전 이외의 버전을 다른 자동화된 정보로 처리하였습니다. {주버전}.{년주}.{빌드번호}로 구성된 semver와 문법 호환하는 정책을 제안한 것인데요. 주 버전의 의미는 그대로 같고, yyww의 2자리 년도와 해당 주를 2자리수로 표현한 날짜, 같은 기간내 실행된 증가하는 빌드 번호로 구성합니다. 이렇게 하면 주 버전만 수동으로 관리하고, 나머지는 기간과 순서의 의미를 전달함으로써 버전이 일정 기간과 비슷하게 연결되는 점을 활용합니다. yyyyMMdd 가 아닌 이유는 자동증가하는 빌드 번호 덕분에 중간 기간 표현은 yyww로 충분하다고 합니다. 특히 스프린트를 진행하는 경우, 주단위 혹은 2주단위를 많이 사용하므로 개별 스프린트 단위를 표현할 수 있기도 합니다.

주버전 조차도 공개 단위로 제안하고 있기도 합니다. 사용자에게 전달하는 타이밍에 주버전을 올림으로써 적극적으로 주 버전을 올리는 것은 제안합니다. 버전 변경의 고민을 최소화하려는 결정을 느낄 수 있었습니다.

이렇게 하면 버전을 올리는 것에 대해 논의하는 시간을 줄일 수 있으며 충분히 소통하는 사람들 간에 정보를 전달할 수 있습니다.

제가 가진 고민과 비슷한 문제를 해결하려는 노력을 확인할 수 있어서 매우 좋았습니다. 버전 관리의 자동화라는 아이디어를 얻을 수 있었던 점도 저에게 매력적이었구요.

calver

최근 Rstudio 에서는 자사 사용 제품의 버전 체계를 calver(calendar-version)으로 전환한다고 공유하였습니다. calver{4자리년(yyyy)}.{2자리월(MM)}.{patch}로 구성합니다. 이 전환으로 블로그에서는

  1. 바로 버전의 나이(?)를 확인할 수 있다.
  2. 고객이 지원 서비스를 받는 기간을 이해하기 쉽다.
  3. 새로운 기능을 추가할 것인지를 정하기 쉽다.

는 장점을 내세웠습니다.

이 버전 체계 변경의 목적을 고객 지원 기간을 정리하기 위해서라고 합니다.

지원 기간은 리눅스나 nodejs에서의 정책때문에 익숙한 단어일텐데요. lts라는 장기 지원 버전과 단기 지원 버전을 번갈아 출시하는 정책을 가지고 있습니다.

rust는 특이하게 년도별 edition을 제공하기도 합니다. 메인 버전은 장기 지원 체계를 유지하고, 매년 실험적인 기능을 년도별 edition에 담아 그 기간만 지원하는 정책입니다.

두 지원 정책 모두 정기적인 릴리즈 계획과 더불어 정해진 기간동안 유지보수 및 지원하는 정책을 취함으로써 새로운 기능 테스트와 더불어 유지보수 리소스를 합리적으로 가져가기 위해 설계되었습니다.

Rstudio에서는 이번 버전 체계의 변화를 통해 아래와 같은 지원 정책을 취합니다. latest 버전은 기간과 상관없이 지원하고, 과거 버전의 경우 calver 기준 18개월 까지만 지원합니다. 지원 정책 문서에 이러한 내용이 업데이트되었고, 관련한 공유를 블로그로 한 것 같습니다.

이렇게 지원 정책과 버전을 이해하기 쉽게 일치시킴으로써 버전에 노출되는 사람에게 전달하는 정보가 강조되는 효과가 있습니다. 다른 버전 체계와 비교하자면, 년.월 정보의 주버전과 patch의 자동화 가능한 수버전이 특징이라 볼 수 있습니다. headver와 같이 주버전을 하나만 관리하되 그것 조차 년월로 취급하여 배포 타이밍을 이름으로 사용하였습니다.

특히 많은 프로젝트가 릴리즈 주기를 가지고 있다는 점에서, 그리고 주기 단위로 주, 부 버전을 올리는 계획을 세운다는 점에서 calver는 더욱 사용자 지향적 버전 체계라는 생각이 들었습니다.

bnver

저 나름대로 새로운 버전 체계를 생각해보게 되었습니다. 물론 headver처럼 semver 호환까지는 고려하지 못했지만, 버전에 개발자와 운영자에게 유용한 정보를 담으면 좋겠다고 생각했습니다. bnverBranch Name Version 이란 뜻입니다. 구성은 {Branch Name}.{yyyyMMdd}.{git short hash} 입니다. 이렇게 하면 브랜치 이름으로 개발 버전인지, 새로운 관리 버전인지, main 버전인지를 전달할 수 있습니다. yyyyMMdd는 보시는 바와같이 날짜입니다. yyww는 개인적인 감상으로는 가독성이 떨어진다 느꼈습니다. 지금이 몇째주 이지? 라는 걸 주단위 스프린트 같이 사용해야지만 쉬운 방식이라 생각했거든요. 더 범용으로 사용하기에는 년월일 방식이 효과적이라 생각했습니다. git short hash는 아직 고민이 많습니다. 빌드 시스템들이 파이프라인이 트리거링된 git hash 정보를 전달하는 경우가 많기 때문에 쓸모가 없다 싶다가도, 운영자 입장에선 현재 배포된 서비스가 기대하는 버전이 맞는지 확인하는데 중요한 정보라고 생각합니다.

bnver의 장점이라면 모든 버전이 자동으로 동작하며, 개발자가 신경써야 할 부분은 빌드 파이프라인이 어떤 브랜치를 받아서 동작하는지 입니다. 이미 브랜치를 분리하여 작업하는 워크플로우가 광범위하게 사용되고 있기 때문에 변경없이 사용할 수 있습니다.

단점이라면 api 호환성에 대한 정보가 전혀 없다는 점 입니다. 호환성 정보를 전달하기 위한 방법이 없는 것은 아닙니다. main 브랜치를 mainv1이나 v1 등으로 변경해서 사용하면 됩니다. 이 정도 변경이라면 사용하기 어려울 수 있겠다 싶긴 합니다.

개발자에게 여러 자유도를 허락하면서 운영자에게 정보를 전달하는 방법으로 생각해 보았습니다.

다시 semver

semver의 본래 문제로 돌아가 봅시다. 3단계의 버전을 가지고 있으며 각 버전을 개발자가 수동으로 관리한다는 것이 큰 문제입니다. headver는 수동 관리용 버전을 1개로 줄이고, 그 조차도 간단한 정책을 취함으로써 소모적인 논쟁을 해결합니다. bnver는 개발자와 운영자 사이의 정보 전달과 모든 버전 자동화를 목표로 설계하였습니다. 물론 브랜치를 관리하는 것 자체가 수동이긴 하지만, 개발자의 작업 흐름에 사용되는 동작을 활용한다는 점이 자동의 의미를 유지합니다. 그럼 semver의 가장 큰 문제는 실제 코딩에서의 변경에 대한 의미를 판단하여 개발자가 수동으로 관리한다는 점이라 할 수 있겠습니다.

semver를 자동으로 작성할 수 있다면, 그럼 문제가 해결되는 걸까요?

semantic-release

semver를 자동화하려면 어떻게 하면 좋을까요? bnver가 가진 아이디어를 활용할 수 있을 것 같습니다. 개발자의 워크플로우내에서 semver를 조정할 수 있는 정보를 받아서 자동으로 동작해주면 됩니다. 가장 간단하게는 코드 변경분을 보고 semver의 어느 버전을 변경할지를 정하면 되는 것이지요. 규칙을 정하면 가능할 것도 같으면서도 어렵다는 느낌이 듭니다.

우선 개발자의 워크플로우에 항상 사용되는 것이 무엇인지 부터 살펴보면 좋을 것 같습니다. 필수 단계라고 하면 2가지를 생각할 수 있습니다. 위에 언급한 코드 그 자체가 1번이고, 두번째는 커밋 메세지 입니다.

본 단락의 제목인 semantic-release는 정형화된 커밋 메세지를 활용하여 semver를 자동으로 작성해 줍니다.

예를 들면 커밋 메세지에 BREAKING CHANGE 라는 글자가 있다면 주버전을 올리는 식입니다.

이 접근은 semver의 많은 부분을 해결하면서, 개발자에게 버전의 논란을 많이 줄일 수 있는 방식입니다. 개발자는 버그 수정, hotfix, api 개선 등 실제로 작업한 내용을 바탕으로 커밋 메세지를 작성하면 됩니다. 미리 정의한 커밋 활동의 카테고리를 팀 내에서 합의를 하고, 그리고 그 합의된 활동 이름이 각 semver 버전에 연동하는 것으로 작동합니다.

이렇게 하면 개발자가 집중해야 할 것과 그것을 노출하는 방식을 분리하여 논의점을 대표화할 수 있습니다. 예를 들어 어떤 커밋이 발생할 때 자신이 작업한 내용이 어떤 카테고리에 속하는지는 이슈 생성때 부터 논의하거나 정할 수 있습니다. 버전 그 자체는 개발자가 신경쓸 필요가 없는 것이지요. 예를 들어 semantic-release-actionbreaking이나 revert라는 글자가 있다면 각각 주버전과 수버전을 올리는 규칙이 기본으로 들어가 있습니다. 유명한 몇 가지 프로젝트에서 사용하는 관례도 선택할 수 있게 되어 있습니다. 이 부분을 그대로 사용해도 좋고, 팀 내에서 사용할 관례를 정하는 것도 좋은 것 같습니다. 논의점을 대표화한다는 말의 뜻이기도 합니다. 어떤 단어가 커밋 메세지에 있으면 어떤 버전을 올린다는 논의를 대표적으로 한번만 하게 함으로써 개별 커밋때 논의할 내용을 대표적으로 논의할 수 있게 되었습니다.

웹 페이지에 html 이 내용과 디자인으로 분리한 것처럼 커밋메세지와 버전표현을 분리함으로써 자동화를 달성한 것이지요.

깃헙 액션인 semantic-release-action의 접근이 공개 버전을 사용하는 프로젝트에서 사용할 수 있는 최선의 선택이지 않나 싶습니다. 이 깃헙 액션은 버저닝의 고민을 커밋메세지로 전환하고, 커밋 메세지를 잘 작성하는 것을 독려하는 효과가 있습니다. 그리고 개발자는 태그가 무엇인지, 릴리즈가 무엇인지 신경쓰지 않을 수 있게 됩니다.

깃의 많은 부분이 자동화보다는 명시적인 동작으로 구성된 것을 생각하면, semantic-release-action는 큰 자동화 규칙이라고 할 수 있습니다. 당장 태그가 무엇인지 몰라도 된다는 점이 참 마음에 듭니다. 브랜치 전략과도 완전히 분리된 정책을 취하는 것도 중요한 설계 관점이라고 생각합니다.

배포 담당자로써 무엇이 좋은가?

사실 배포 담당자는 명시적인 버전이기만 하다면 나머지는 작은 문제일 수 있다고 생각합니다. 다만 광범위하게 퍼져있는 semver를 자동화를 통해 자체가 가진 문제를 해결할 수 있다면 가장 효과적이겠다 생각하고 있습니다. headver는 결국 버전을 3개에서 1개로 단순화함으로써 문제를 해결한 것이라고 생각이 들구요. bnver 또한 비슷한 전략입니다만, 그 조차 개발자의 깃 워크플로우에 의존하게 함으로써 자동화에 초점을 맞춘 것이라 생각합니다. 가장 큰 단점으로 main 브랜치라는 그간의 관례를 버려야 할 수도 있는 결정이라는 점이라 어떻게 하는게 좋을지 고민하게 되었습니다. 이때 semver의 자동화가 가장 합리적인 선택이라는 생각이 듭니다. 물론 팀내에 어떤 커밋 활동이 있는지 카테고리를 정하고, 각 카테고리를 어떤 semver 버전을 올리는데 사용할지를 정해야 하긴 합니다만, 이 부분의 의사 결정을 중앙화함으로써 발산해버릴 논의를 빠르게 끝내고 활용할 수 있다는 장점이 있습니다.

R 패키지 개발에서 어떻게 사용하면 좋을까?

그대로 사용해도 좋다고 생각합니다. 다만 usethis 패키지와 pkgdown 패키지의 활성화로 NEWS.md 작성이 보편화된 상황인데요. 이 부분까지 커밋 메세지로 자동화하는 것이 어떤가 하는 생각이 들었습니다.

명시적으로 작성하길 선호한다면, 최소한 NEWS.md와 github release가 동기화되는 정도는 필요하다 생각이 드네요. 한번 r-lib의 action 패키지에 example로 제안해 봐야겠습니다.

긴 글 읽어주셔서 감사합니다.

Corrections

If you see mistakes or want to suggest changes, please create an issue on the source repository.

Reuse

Text and figures are licensed under Creative Commons Attribution CC BY-NC-ND 4.0. Source code is available at https://github.com/mrchypark/mrchypark.github.io, unless otherwise noted. The figures that have been reused from other sources don't fall under this license and can be recognized by a note in their caption: "Figure from ...".

Citation

For attribution, please cite this work as

Park (2021, Sept. 2). mrchypark: 패키지 버전은 어떻게 작성해야할까. Retrieved from https://mrchypark.github.io/post/패키지-버전은-어떻게-작성해야할까/

BibTeX citation

@misc{park2021패키지,
  author = {Park, Chanyub},
  title = {mrchypark: 패키지 버전은 어떻게 작성해야할까},
  url = {https://mrchypark.github.io/post/패키지-버전은-어떻게-작성해야할까/},
  year = {2021}
}