배포와 인프라6 분 읽기

버전 되쓰기는 한 줄인 줄 알았습니다 — 진실이 세 곳에 살고 있었습니다

저장소 40개의 버전 거짓말을 고치러 갔습니다. 고치는 코드는 40줄인데, 함정은 두 개였습니다: 빌드가 읽는 파일과 재생성이 읽는 파일이 다르고, '스토어가 진실'이라는 가정도 심사 중인 앱에서는 틀립니다.

#shipping-infra#ios#gotchas#verification#automation
Left: the naive fix writes the version back to project.pbxproj only. Right: three places hold a version — pbxproj, the xcodegen seed project.yml, and the store — and each can disagree.
한 곳만 고치면 재생성이 되돌리고, 스토어를 맹신하면 심사 중인 버전을 후퇴시킵니다.

지난주에 저장소 40개의 버전 번호가 전부 거짓말이었다는 글을 썼습니다. 릴리스 도구가 버전을 xcodebuild 커맨드라인 인자로만 넘기고 저장소에는 되쓰지 않아서, 스토어엔 1.9.5가 올라가 있는데 커밋된 파일은 1.0인 채로 몇 년을 산 이야기였습니다.

오늘 그걸 고쳤습니다. 업로드가 성공하면 project 파일에 버전을 되쓰고 chore: bump 커밋을 남기는 함수 하나. 40줄이면 끝날 일이라고 생각했습니다.

40줄은 맞았습니다. 함정이 두 개였을 뿐입니다.

당신의 프로젝트에서 "버전"이라는 값은 정확히 몇 개의 파일에 적혀 있습니까? 바로 대답할 수 있습니까?

함정 1: 빌드가 읽는 파일과 재생성이 읽는 파일이 다릅니다

xcodebuild는 project.pbxproj를 읽습니다. 그러니 되쓰기도 거기에 하면 될 것 같습니다.

그런데 이 플릿의 27개 앱은 xcodegen을 씁니다. xcodegen에게 project.pbxproj는 산출물이고, 진짜 시드는 project.yml입니다. pbxproj만 고치고 yml을 안 고치면, 다음번 누군가 xcodegen generate를 도는 순간 되쓰기가 조용히 증발합니다 — yml에 남아 있던 낡은 버전으로.

이건 가설이 아닙니다. 이 플릿에서 실제로 한 번 터졌던 경로입니다. 재생성이 스테일 시드를 읽어 빌드번호가 1로 돌아갔고, 애플이 build 1 DUPLICATE로 반려했습니다.

그래서 되쓰기는 두 파일 다 갱신합니다. project.yml이 있으면 시드까지, 없으면 pbxproj만.

함정 2: "스토어가 진실"도 항상은 아닙니다

되쓰기는 앞으로의 드리프트만 막습니다. 이미 벌어진 40개의 갭은 일괄 백필로 메꿔야 했습니다. 방법은 자명해 보입니다: 스토어의 라이브 버전을 받아와 저장소에 쓴다. 스토어가 진실이니까.

dry-run 출력을 훑는데 한 줄이 눈에 걸렸습니다.

BUMP brewra: 1.2 -> 1.1

나머지 40개는 전부 앞으로 가는데 이 앱만 뒤로 갑니다. 저장소가 1.2인데 스토어 라이브는 1.1. 확인해 보니 1.2가 지금 심사 대기 중이었습니다. 저장소는 미래를 들고 있고, 스토어의 "라이브"는 과거를 보여주고 있었던 겁니다.

여기서 그냥 --apply를 눌렀다면 심사 중인 버전을 저장소에서 후퇴시키고, 다음 릴리스가 그 낡은 시드에서 출발했을 겁니다. 고치려던 바로 그 버그를, 고치는 도구가 재생산하는 구조입니다.

당신이라면 어떻게 막겠습니까? 심사 상태를 API로 조회해서 분기하겠습니까? 저는 더 값싼 규칙을 골랐습니다: 버전은 뒤로 못 간다. 저장소 값이 스토어 값보다 크거나 같으면 건너뜁니다. 심사 중이든 아니든, 후퇴만 안 하면 이 도구가 낼 수 있는 최악의 사고가 사라집니다.

결과

40개 앱 백필 완료, 2개 스킵(하나는 심사 대기로 저장소가 앞섬, 하나는 첫 출시 심사 중이라 라이브 없음). 적용 직후 같은 스크립트를 다시 돌려 변경 0건 — 멱등성을 눈으로 확인하고 커밋했습니다.

자가진단 세 개:

  • 당신의 "버전"은 몇 개 파일에 적혀 있습니까? 그중 코드 생성기가 읽는 시드는 어느 쪽입니까?
  • 백필·마이그레이션 도구에 역행 가드가 있습니까? 아니면 "소스가 진실"이라는 가정 하나에 기대고 있습니까?
  • 그 도구를 두 번 연속 돌리면 두 번째는 변경 0건입니까?

솔직한 부분

이 함정 둘 다 제가 예견한 게 아닙니다. 하나는 과거에 이미 터진 사고 기록이 막아줬고, 하나는 dry-run 출력에서 딱 한 줄이 이상해 보여서 잡혔습니다. 설계가 아니라 습관이 잡은 겁니다 — 파괴적 일괄 작업 전에 계획을 전부 출력해서 훑는 습관.

오늘 백필 같은 일괄 수정을 앞두고 있다면, --apply 전에 dry-run 출력을 끝까지 스크롤해 보세요. 방향이 반대인 줄이 딱 하나 섞여 있을지 모릅니다. 당신의 brewra는 뭐였습니까 — 댓글로 알려주세요.

관련 글