
127개 커밋 릴리스란 실제로 무엇인가
GeekBye v1.7.0은 열하루 동안 127개의 커밋이었습니다. 바깥에서 보면 자잘한 것 백 가지처럼 보입니다. 안에서 보면 그것은 함께 엮인 두 개의 큰 기능이었습니다 — 그리고 그중 하나는 엉뚱한 곳에 지어졌다가, 릴리스 도중에 뜯겨 나가 다시 지어졌습니다. 여기 큰 릴리스의 해부가 있습니다.
GeekBye v1.7.0에 담긴 모든 것을 그저 나열하기만 하는 이 블로그 글의 어떤 버전이 존재합니다 — 새로 디자인한 홈 화면, 캘리브레이션 흐름, 접을 수 있는 사이드바, 새 설정, 그리고 계속 이어집니다. 그것은 정확할 테지만 아무것도 가르쳐 주지 않을 것입니다. 왜냐하면 127개 커밋 릴리스의 정직한 이야기는 그 목록이 아니기 때문입니다. 그것은 그 모양 입니다.
여기 그 모양이 있습니다. 백스물일곱 개의 커밋은 백스물일곱 개의 자잘한 것처럼 들립니다. 거의 결코 그렇지 않습니다. v1.7.0은 약 열하루에 걸쳐 장기 유지 브랜치 위에서 병렬로 지어져 끝에 함께 엮인 두 개의 큰 기능이었습니다. 큰 릴리스를 이해한다는 것은 그 두 가닥을 이해한다는 뜻입니다 — 그리고 우리가 무언가를 엉뚱한 자리에 지었다가 도중에 다시 뜯어내야 했던 그 한 곳을 이해한다는 뜻입니다.
첫째 가닥: 캘리브레이션
첫째 가닥은 캘리브레이션 — 당신의 커뮤니케이션 역량에 대한 AI 음성 평가였습니다. 당신은 AI "커리어 코치"와 라이브 통화를 시작하고, 구조화된 일련의 단계(워밍업, 행동, 기술 커뮤니케이션, 압박 대응, 목표 설정)를 거친 뒤, 마지막에 여섯 개 차원에 걸쳐 점수를 받습니다: 자신감, 명료함, 구체성, 몰입도, 침착함, 관련성. 그 아래에서는 구체적인 발화 지표도 측정합니다 — 당신의 말하기 속도, 군더더기 단어의 빈도, 당신의 멈춤이 전략적으로 읽히는지 아니면 머뭇거림으로 읽히는지.
그 결과물은 등급이 아니라 출발점 입니다. 캘리브레이션은 당신의 강점, 성장 영역, 그리고 — 결정적으로 — 연습을 시작하기에 알맞은 권장 난이도를 만들어 냅니다. 그것은 제품이 당신을 어디서 맞이해야 하는지 를 보정합니다. 각 역량은 요약, 구체적인 제안, 그리고 예시로서 당신 자신의 통화에서 실제로 인용한 한 줄을 담은 펼침형 피드백 카드로 돌아옵니다. 나머지 연습 경험 전체가 그 위에 지어지는 바로 그 기능입니다.
둘째 가닥: 완전히 새로운 창
둘째 가닥은 프리미엄 리디자인 이었습니다 — 그리고 이것을 리디자인이라 부르는 것은 그 가치를 깎아내립니다. 그것은 기존 화면들의 재도색이 아니었습니다; 그것은 사실상 여섯 개의 명시적 단계로 지어 낸 두 번째 애플리케이션 창 이었습니다: 접을 수 있는 Notion 스타일 사이드바, 기존 세션 목록을 대체한 헤더 드롭다운, 히스토리와 브레드크럼을 갖춘 브라우저 스타일 내비게이션, 그리고 홈, 프로필, 미팅, 설정의 프리미엄 재구축.
두 개의 큰 기능. 그것이 127개 커밋의 실제였습니다. 그리고 그것들을 출시하는 데 가장 어려웠던 부분은 둘 중 어느 하나를 작성하는 것이 아니었습니다 — 그것은 엮는 일 이었습니다. 두 브랜치는 서로 의존적이었습니다; 하나는 다른 하나로 병합되었고, 캘리브레이션 브랜치는 리디자인이 그 밑에서 움직이는 동안 어긋나지 않고 동기 상태를 유지하려고 main 브랜치를 네 번이나 따로 끌어와야 했습니다. 커밋 수는 큰 릴리스의 비용이 아닙니다. 장기 유지 브랜치와 통합 순서가 그 비용입니다.
읽어 둘 가치가 있는 부분: 우리는 캘리브레이션을 엉뚱한 곳에 지었다
여기 그 실수가 있습니다. 그리고 이것은 좋은 실수인데, 너무나 흔하기 때문입니다.
GeekBye에는 확고한 아키텍처 규칙이 있습니다: 모든 AI 작업은 백엔드에 산다. 클라이언트는 서버와 대화하는 얇은 껍데기입니다. 모두가 이것을 알고 있었습니다.
그런데도 캘리브레이션은 처음에 클라이언트의 로컬 데이터베이스에 지어졌습니다. 전용 테이블, 데이터베이스 마이그레이션, 254줄짜리 리포지토리, IPC 핸들러, 그리고 515줄에 달하는 평가 문항 뭉치 — 모두 사용자의 기기 위에 살았습니다. 작동은 했습니다. 그것은 또한 앱 전체가 그 위에 지어진 계약을 조용히 위반했습니다.
사흘 뒤, 하나의 커밋이 그 모든 것을 백엔드로 옮기려 여덟 개 파일에 걸쳐 680줄을 삭제 했고, 그곳에서 캘리브레이션 데이터는 제대로 된 서버 측 모델이 되었으며, 문항 로직과 채점은 서버의 관심사가 되었습니다. 또 다른 커밋은 515줄짜리 클라이언트 문항 뭉치를 통째로 삭제했습니다. 그 diff는 거의 우스울 지경입니다: 한 줄 삽입, 육백여든 줄 삭제.
아무도 680줄의 버릴 코드를 쓰겠다고 나서지 않았습니다. 그것은 이런 일이 늘 벌어지는 방식대로 일어났습니다: 클라이언트 지름길이 바로 거기 있고, 로컬에서 프로토타이핑하는 편이 더 빠르며, "나중에 백엔드로 옮기면 되지"는 무해하게 느껴집니다. 하지만 당신의 아키텍처가 진실의 원천이 어디 사는지 이미 말해 주고 있다면, 그것을 다른 어딘가에 짓는 것은 지름길이 아닙니다 — 그것은 스스로에게 미리 예약해 둔 재작업입니다. 남은 교훈은 이것입니다: 로컬 버전이 세우기에 더 빠를 때조차, 처음부터 계약이 말하는 곳에 두어라.
오직 통합만이 찾아낼 수 있었던 버그
증거가 하나 더 있습니다. 이것이 큰 릴리스의 대표적인 실패 양상이기 때문입니다. 캘리브레이션과 새 내비게이션이 함께 연결되어 실제로 돌아가기 시작하자, 앱은 뚜렷한 이유 없이 속도 제한 — HTTP 429 — 을 건드리기 시작했습니다.
원인은 순전한 통합이었습니다. 캘리브레이션 상태가 컴포넌트 수준에서 fetch되고 있었고, 그래서 이동할 때마다 그것을 다시 fetch했습니다 — 그리고 버그를 드러내려고 개발 중에 의도적으로 effect를 두 번 호출하는 React의 strict mode가 그것을 다시 두 배로 만들었습니다. 그 결과 화면 전환마다 네 번에서 여덟 번의 동일한 캘리브레이션 요청이 발사 되었고, 서버의 속도 제한기를 건드리기에 충분했습니다. 그 수정은 fetch를 마운트 시점의 몇 번의 호출로 통합했습니다.
당신은 캘리브레이션만 따로, 혹은 내비게이션만 따로 테스트해서는 이 버그를 찾을 수 없었을 것입니다. 그것은 오직 이음매 에서만 존재합니다 — 각각으로는 올바른 두 기능이 만나는 곳에서. 그것이 큰 릴리스가 매기는 세금입니다: 마지막 1마일은 기능을 짓는 일이 아니라, 그것들이 마침내 한 방에 있을 때에만 망가지는 모든 것을 발견하는 일입니다.
큰 릴리스가 가르쳐 준 세 가지
- 127개 커밋 릴리스는 자잘한 것 백 가지가 아니라 두세 개의 큰 주제다. 그 가닥을 찾아라. 일 — 그리고 위험 — 은 그것들이 어떻게 함께 엮이는가에 살지, 그 수에 살지 않는다.
- 처음부터 계약이 말하는 곳에 진실의 원천을 두어라. 우리는 캘리브레이션을 클라이언트에서 백엔드로 옮기며 680줄을 삭제했다. 로컬 지름길은 미리 예약해 둔 재작업이다; 아키텍처는 이미 답을 말해 주었다.
- 통합은 격리가 잡을 수 없는 실패를 드러낸다. 그 429 폭풍은 올바른 두 기능 사이의 이음매에 살았고 오직 그것들이 함께 돌아갈 때에만 나타났다. 기능별 테스트만이 아니라 통합 단계를 위한 예산을 잡아라.
이것은 우리가 훨씬 더 큰 규모에서 다시 들려줄 이야기의 v1 시대 사촌입니다 — 버전 2가 실제로 무엇을 요구하는가: 206개의 정직한 상태 커밋(v2.0.0)을 보십시오. 그곳에서는 "큰 숫자는 사실 몇 개의 큰 아이디어다"라는 같은 진실이 v2 재작성 전체에 걸쳐 펼쳐집니다. v1 이야기의 앞 장은 깜빡임 없이 실시간 리포트를 스트리밍하는 방법(v1.6.13)을 보십시오; 그리고 전체 흐름에 대해서는 소프트웨어를 완벽하게 출시하는 일의 해부학을 보십시오.