NLP Blog

16. 버전 관리와 브랜치 관리

|

16. 버전 관리와 브랜치 관리

  • VCS는 꼭 써라!
  • 트렁크 기반 개발이 구글이 쓰니까 짱짱이다!

16.1 버전 관리란?

  • skip

16.1.1 버전 관리가 중요한 이유

  • skip 너무 당연함

16.1.2 중앙집중형 VCS vs 분산형 VCS

  • SVN, GIT?
  • skip

16.1.3 진실 공급원

  • 중앙집중형 VCS는 시스템 설계에서부터 진실 공급원 (source-of-truth)이라는 개념을 사용
    • Trunk에 가장 최근 커밋된 것이 현재 버전임
    • 개발자가 프로젝트를 체크아웃하면 기본적으로 이 트렁크 버전이 제공
    • 수정한 내역을 이 버전위에 다시 커밋하면 해당 변경이 완료됨
  • 분산형 VCS에는 단일 진실 공급원 (Single Source of Truth : SSOT)라는 개념이 존재하지 않음
    • 분산형 VCS를 운형하려면 정책과 규범을 더 명확하게 정해 지켜야 함
    • 잘 관리되는 프로젝트들은 특정 리포지터리의 특정 브랜치 하나를 진실 공급원으로 정하여 혼란의 여지을 없앰 (GitHub or GitLab…)
    • 변경 사항이 주 리포지터리의 트런크 브랜치에 반영되어야 비로소 작업이 완료됨

시나리오: 명확한 진실 공급원이 없다면?

  • 팀원의 리포지터리에서 가져온 이후 무엇이 수정되었는지가 명확하지 않을 수 있음
  • 중앙의 진실 공급원이 없다면 누군가는 다른 릴리스에 포함시킬 기능 목록을 따로 관리해야 할 것이고 이는 결국 중앙화된 진실 공급원 모델을 모방한 것임
  • 새로운 팀원이 합류한다면 정상 작동하는 최신 코드를 어디에서 복사해 올 것인가…?

16.1.4 버전관리 vs 의존성 관리

  • 버전 관리 정책은 개념적으로 의존성 관리 (21장 참고)와 매우 비슷함
  • 차이점은 크게 두가지
    • VCS 정책은 주로 코드를 어떻게 관리할지를 다루고, 대체로 훨씬 세세하게 관리
    • 의존성 관리는 훨씬 어려움, 다른 조직에서 통제하는 프로젝트들을 관리해야하기 때문

16.2 브랜치 관리

16.2.1 ‘진행 중인 작업’은 브랜치와 비슷하다

  • WIP는 모두 하나의 브랜치와 같다
  • 개발자가 상위 진실 공급원으로 푸시하기 전까지 수많은 변경하상을 로컬 리포지터리에 커밋해놓는 분산형 VCS 모델을 생각하면 더 명확함
  • 중앙집중형 VCS에서도 아직 커밋하지 않고 계류 중인 변경들은 브랜치에 커심한 변경과 개념적으로 다르지 않음
    • Perforce는 파일을 변경 중인 사람이 누구인지 찾고, 그 사람이 아직 커밋하지 않은 병경의 내용을 검토해볼 수 있음
  • 예제
    • Widget의 이름을 OldWidget으로 바꾸자!
      • 진실 공급원 리포지터리를 트렁크 브랜치에 있는 Widget의 이름을 바꿉니다.
      • 진실 공급원 리포지터리의 모든 브랜치에서 Widget의 이름을 바꿉니다.
      • 진실 공급원 리포지터리의 모든 브랜치에서 Widget의 일므을 바굽니다. 그리고 Widget을 참조하는 계류 중인 변경들까지 모두 찾아서 바꿉니다.

16.2.2 개발 브랜치

  • 일관된 단위 테스트 (11장 참고)가 보편화되기 전 시절에는 작은 병경 하나라도 시스템을 망가뜨릴 위험이 컸기 때문에 ‘트렁크’를 특별 취급하는 것이 당연
    • 테크리드 왈 “우리는 새로운 변경이 모든 테스트를 통과할 때까지 트렁크에 커밋하지 않습닏. 그리고 우리 팀은 기능별로 개발 브랜치를 따로 만들어 사용하죠”
  • 개발 브랜치 (dev branch)는 ‘구현은 다 했지만 커밋하진 않았어요’와 ‘이제부터 이 코드를 기준으로 개발하세요’의 중간 단계
  • 제품 안정성 유지 차원에서 개발 브랜치를 과하게 사용하는 버전 관리 정책은 근본적으로 잘못됨, 결국은 똑같은 커밋들이 트렁크에까지 병합될 것므로, 큰 단위로 한꺼번에 병합하기보다는 작게 작게 자주 병합하는게 쉬움
    • 각각의 변경을 작성한 당사자가 병합하는 편이 서로 관련 없는 변경들을 나중에 누군가 몰아서 병합하는 것보다 쉬움
    • 병함 시 프리서브밋 테스트가 문제를 발견했을 때도 똑같음
  • 거대 개발 브랜치의 병합은 고통임!

개발 브랜치에 중독되어 가는 과정

  • ‘오래된 개발 브랜치를 병합하니 안정성이 떨어진다’ -> ‘병합은 위험하다’ : 개발 브랜치에 중독
  • ‘더 나은 테스트’와 ‘브랜치 기반 전략 탈피’로 정면 돌파 해야 함
  • 조직이 커지면 개발 브랜치의 수도 늘어나며 병합 난이도가 자연스래 치솟음
  • 병합 후 다시 테스트하는 데 드는 노력은 사실 모두 무가치한 오버헤드
  • 트렁크 기반 개발이 필요!
    • 테스트와 CI를 적극 활용하여 모든 빌드와 테스트가 항상 성공하도록 관리
    • 완벽하지 않거나 테스트되지 않은 기능은 비활성화
    • 엔지니어 개개인이 트렁크와 동기화하고 트렁크에 커밋!

16.2.3 릴리스 브랜치

  • 제품의 릴리스간격이 몇 시간 이상이면 릴리스 브랜치를 따로 생성하는게 좋음
  • 개발 브랜치와 달리 릴리스 브랜치는 대체로 무해함
  • 개발 브랜치와 릴리스 브랜치의 가장 큰 차이는 생을 마감하는 모습
    • 개발 브랜치는 트렁크에 다시 병합될 텐데, 중간에 다른 팀이 브랜치를 추가로 따서 가지를 더 뻗을 수도 있음
    • 반면 릴리스 브랜치는 홀로 존재하다가 결국 사라짐
  • 구글의 DORA (DevOps Research and Assessment) 조직의 연구 결과에 따르면 최고 수준의 기술 조직에는 릴리스 브랜치조차 없음
    • 트런크로부터 하루에도 몇 번씩 릴리스할 수 있는 지속적 배포 (CD)가 잘 자리 잡은 조직에서는 대체로 릴리스 브랜치를 건너뜀
    • 수정사항을 적용해 다시 배포하는게 훨씬 쉽기 때문
  • 같은 DORA 연구에서 ‘트렁크 기반 개발’ 조직과 ‘장시간 유지되는 개발 브랜치가 적은’ 조직일 수록 기술적 성취가 뛰어나다고 이야기 함

16.3 버전 관리 @ 구글

  • 구글은 소스코드 대부분은 하나의 리포지터리, 즉 모노리포에서 관리되며 약 5만여 엔지니어에게 공유됨
  • Chromium, Android 같은 오픈 소스 프로젝트를 제외하고는 거의 모든 프로젝트가 모노리포에서 진행
  • Piper라는 중앙집중형 VCS 사용 (80TB가 넘음!)
    • CaaS, 프로덕션 환경에서 분산 마이크로서비스 형태로 구동
    • 동시 편집 지원
    • 하루 6 ~ 7 만 건의 커밋 처리
  • 모든 디렉터리 계층에 OWNERS 파일을 두어 커밋을 승인할 수 있는 엔지니어의 사용자 이름을 기록

16.3.1 원-버전

  • 구글 버전 관리 정책의 중심에는 One-Version이 있음
  • 단일 진실 공급원 개념을 확장한 개념
  • 모든 의존성이 우리 리포지터리에 담겨 있고 각 의존성은 단 하나의 안정된 버전만 존재해야 함
  • 서드 파티 패키지들도 Piper에는 단 하나의 버전만 저장해둬야 함 (완벽히 지킬 수는 없음)
  • 내부 패키지의 경우 다시 패키징하거나 이름을 바꾸지 않고는 포크할 수 없음 (원본과 포크 버전을 별다른 추가 노력 없이 같은 프로젝트에 섞어 둘 수 있는 기술적으로 안전한 방법)

16.3.2 시나리오: 여러 버전을 허용한다면?

  • 어떤 팀이 공통 인프라 코드에서 버그를 발견
  • 이 팀은 원본 인프라의 코드를 바로 수정하지 않고 포크하여 버그를 자체 해결하는 전략을 택함, 라이브러리 이름이나 심볼을 바꾸지 않은 채로! (위 원-버전 정책을 어김)
  • 주변 팀한테 여기 Abseil의 개선 버전을 체크인해뒀으니 필요하면 쓰세요~ -> 망하기 시작
  • 21장에서 살표보겠지만 위험한 상황으로 접어듬 (그림 16-2, 16-3 참조)
  • 운 좋으면 빌드실패, 최악은 어려운 런타임 버그
  • 포크가 코드베이스에 갈림길을 하나 추가해버림
  • 같은 대상을 가리키는 전이 의존성 (transitive dependency)들은 반드시 단 하나를 가리켜야 함.
  • 즉, ‘새로운 의존성을 하나 추가한다’라는 간단한 일이 자칫하면 코드베이스 전체의 테스트를 모두 수행하여 존재하는 모든 갈림길들의 조합을 다시 점검해야 하는 큰 작업으로 번질 수 있음
  • 이것을 가능하게 하는 여러 트릭 (셰이딩 등)이 존재하지만 모두 무가치한 쓸데없는 짓

16.3.3 원-버전 규칙

개발자가 ‘이 구성요소는 어떤 버전을 사용해야 하죠?’ 라고 문든 상황을 만들지 않아야 합니다.

  • 의존성을 새로 추가할 때 선택할 수 있는 버전을 제한 한다.

16.3.4 장수 브랜치는 (웬만하면) 금지

  • 개발 브랜치를 되도록 만들지 말고, 만들더라도 매우 짧게 쓰고 없애야 함
  • 대규모 변경 (22장 참고)을 위한 위리 정책과 도구들은 트렁크 기반 개발이 중요하다는 데 더욱 힘을 실어 줌
    • 코드베이스 전반에 적용되는 광범위하고 얕은 변경은 그 자체로 이미 엄청난 규모의 작업
    • 트런크에 체크인된 모든 것을 수정해야 함
    • 그런데 무수한 개발 브랜치들까지 찾아서 고드를 적절하게 수정해야 한다면 부담이 더욱 더 커짐
    • 분산형 VCS는 영향 받는 브랜치를 모두 찾아내는게 불가능 할 수도 있음
  • 구글은 빌드 호라이즌 (Build Horizon)이라는 정책을 써버 잠재적인 버전 왜곡이 지속되는 기간의 상한선을 정해 둠
    • 프로덕션 환경에서 구동 중인 모든 제품은 최대 6개원 안에 다시 빌드하여 재배포 해야함

16.3.5 릴리즈 브랜치는 어떤가?

  • 릴리스 브랜치가 광범위한 비용을 발생시키지는 않음

16.4 모노리포 (단일 리포지터리)

  • 모노리포 방식은 그 자체로 몇가지 이점을 제공
    • 그 중 최고 : 원-버전을 고수하기 쉬움

중요한 것은 모노리포냐 아니냐가 아니라 ‘원-버전’ 원칙을 최대한 준수하는 것

  • VCS와 빌드 시스템 같은 소프트웨어 엔지니어링 도구들은 세분화한 리포지터리들과 모노리포를 영리하게 혼합하여 점점 더 모노리포와 비슷한 경험을 제공하도록 지화 함
    • git submodule
    • Bazel의 외부 의존성
    • CMake의 서브 프로젝트
    • 가상 모노리포 (VMR)

16.5 버전 관리의 미래

  • VCS는 확장성을 개선해 더 큰 리포지터리를 지원할 것
  • 리포지터리들이 프로젝트와 조직 경계를 넘어 더 유연하게 연동되도록 해주는 반대 방향의 기술도 발전할 것임
  • 아마도 현존하는 패키지 관리 단체나 리눅스 배포자 중 하나가 업계 표준의 가상 모노리포 구축을 촉진할 것
  • 그 모노리포가 제공하는 유틸리티를 이용하여 상호 호환되는 의존성들을 하나의 단위처럼 활용하기가 쉬워질 것
  • 버전 번호는 차츰 타임스탬프 정도의 의미로 퇴색될 것

16.6 마치며

  • 원-버전 규칙 적극 권장
  • 개발자들이 어디로 커시해야 할지, 혹은 어느 버전을 이용해야 할지를 선택할 수 없어야 함

16.7 핵심 정리

  • VCS 꼭 쓰셈
  • 어느 버전을 사용할지 선택할 수 있다면 잠재적으로 확장성이 떨어진다는 뜻
  • 원-버전 규칙은 조직의 효율에 지대한 영향을 줌. 어디에 커밋할지 혹은 어느 걸 사용할지 성택할 일을 없애면 일이 기가 막히게 단순해짐
  • 어떤 언어에서는 셰이딩, 분리 컴파일, 링커 숨기기 같은 기술로 문제를 회피할 수 있지만, 쓸모없는 짓, 기술 부채만 쌓임
  • 트런크 기반으로 개발하는 조직일수록 성과가 좋을 가능성이 높음

Comments