서비스 인프라

앱 심사와 단계적 출시, 되돌릴 수 없는 배포

웹은 이전 파일로 되돌리면 되지만, 사용자 기기에 설치된 앱은 거둬들일 수 없다.
스토어 심사와 단계적 출시가 어떻게 동작하는지, 그래서 어떤 값을 앱 대신 서버에 두는지 정리한다.

이 글의 목차앱의 코드는 사용자 기기에 있다6
  1. 앱의 코드는 사용자 기기에 있다
  2. 올린 빌드가 사용자에게 닿기까지
  3. 설치된 앱은 거둬들일 수 없다
  4. 바꿀 일이 생길 값은 서버에 둔다
  5. 업데이트하지 않은 앱도 서버를 계속 부른다
  6. 첫 출시 전에 넣어 둘 것

웹 서비스만 만들다가 처음 앱을 내면 배포 화면부터 낯설다.
빌드를 올리고, 심사를 신청하고, 승인되면 언제 출시할지 고르는 칸이 나온다.

웹에서는 고친 파일을 서버에 올리면 다음 방문자부터 새 화면을 본다.
문제가 생겨도 이전 파일로 돌려놓으면 된다.
앱에서는 이 두 가지가 모두 다르게 움직인다.

앱의 코드는 사용자 기기에 있다

웹 페이지의 코드는 서버에 있다.
브라우저가 접속할 때마다 서버에서 받아 가므로, 서버의 파일을 바꾸면 사용자가 받는 코드도 바뀐다.

앱은 사용자가 스토어에서 내려받는 순간 기기에 설치되고, 그다음부터는 기기에 있는 코드가 돈다.
개발자가 서버에서 바꿀 수 있는 것은 앱이 요청했을 때 돌려주는 응답뿐이다.

코드 위치에 따른 서버 변경의 반영 범위 웹은 서버의 파일을 바꾸면 다음 방문부터 코드가 바뀌지만, 앱은 기기에 설치된 코드가 돌아서 서버에서 바꿀 수 있는 것은 응답뿐이다. 개념 도해 코드 위치에 따른 서버 변경의 반영 범위 코드가 도는 곳 웹 서버 페이지 코드 · 응답 ① 방문할 때마다 받음 브라우저 받은 코드 실행 앱 스토어 앱 빌드 ① 설치할 때 한 번 기기 설치된 코드 실행 ② 요청 ③ 응답 서버 응답만 돌려줌 서버만 바꿨을 때 달라지는 것 코드 응답 웹 다음 방문부터 바뀜 다음 요청부터 바뀜 앱 업데이트 전까지 그대로 다음 요청부터 바뀜 웹은 서버의 파일을 바꾸면 다음 방문부터 코드가 바뀌지만, 앱은 기기에 설치된 코드가 돌아서 서버에서 바꿀 수 있는 것은 응답뿐이다. SOSHIN · DEV NOTES
같은 서버 변경이 웹에서는 코드까지 닿고, 앱에서는 응답까지만 닿는다.

앱을 기기에 넣어 주는 통로는 스토어다.
2008년 7월 iPhone용 App Store가 문을 열 때부터 업데이트가 나오면 스토어가 사용자에게 알려 주었지만, 받을지는 사용자의 몫이었다.

올린 빌드가 사용자에게 닿기까지

iPhone 앱은 Apple의 관리 사이트인 App Store Connect에 올린다.
이때 올리는 앱 파일을 빌드라고 한다.

App Store Connect는 사용자에게 보이는 버전 번호와 별도로, 빌드마다 붙는 빌드 문자열로 업로드를 구별한다.
그래서 같은 1.2.0 버전을 준비하면서 빌드를 여러 번 올릴 수 있다.

올린 빌드를 곧바로 출시하지 않아도 된다.
출시 전에 테스트용으로 먼저 배포할 수 있고, 이 기능이 TestFlight다.
Google Play에도 정식 출시 전에 테스트 참여자에게 먼저 배포하는 내부, 비공개, 공개 테스트 트랙이 있다.

빌드를 골라 제출하면 심사를 받는다.
Apple은 제출된 앱의 절반 이상을 보통 24시간 안에, 90%⁠를 48시간 안에 심사한다고 밝힌다.
웹처럼 고치고 몇 분 만에 내보낼 수는 없다.

심사에서 자주 걸리는 항목은 앱 완성도다.
해결되지 않은 문제의 40% 넘게가 이 항목이다.
앱이 죽거나, 임시 문구가 남아 있거나, 필요한 정보가 빠진 경우다.

심각한 버그를 고친 버전이라면 빠른 심사(expedited review)⁠를 요청할 수 있다.
빠른 심사도 순서를 당겨 줄 뿐 심사를 건너뛰게 해 주지는 않는다.

심사를 통과해도 바로 모두에게 반영되지는 않는다.
기기가 새 버전을 내려받아 설치해야 하고, 자동 업데이트를 꺼 둔 사용자는 직접 업데이트하기 전까지 옛 버전을 계속 쓴다.

빌드가 사용자 기기에 닿기까지의 단계 심사와 출시를 거친 빌드도 기기가 설치해야 바뀌고, 자동 업데이트를 끈 기기는 직접 업데이트할 때까지 옛 버전에 머문다. 개념 도해 빌드가 사용자 기기에 닿기까지의 단계 빌드가 지나는 단계 ① 빌드 업로드 App Store Connect ② 테스트 배포 TestFlight · 선택 ③ 심사 버전마다 제출 ④ 출시 승인 뒤 시점 선택 ⑤ 기기 설치 내려받아 설치 빌드 문자열로 구별 절반 이상 24시간 · 90% 48시간 안 출시 뒤 기기에 깔린 버전 버전 번호와 간격은 예시 ④ 출시 자동 업데이트 켬 1.1.0 1.2.0 자동 설치 자동 업데이트 끔 1.1.0 · 옛 버전 계속 사용 1.2.0 직접 업데이트 시간 심사와 출시를 거친 빌드도 기기가 설치해야 바뀌고, 자동 업데이트를 끈 기기는 직접 업데이트할 때까지 옛 버전에 머문다. SOSHIN · DEV NOTES
출시 시점이 같아도 기기마다 새 버전으로 바뀌는 시점은 다르다.

설치된 앱은 거둬들일 수 없다

새 버전에 큰 버그가 있다면 어떻게 될까.
웹이라면 이전 파일로 돌아가면 되지만, 사용자 기기에 깔린 앱 파일을 서버에서 되돌리는 방법은 없다.

스토어가 주는 것은 퍼지는 속도를 조절하는 기능이다.
새 버전을 일부 사용자에게 먼저 보내고, 문제가 없으면 범위를 넓히는 방식을 단계적 출시라고 한다.

Google Play의 단계적 출시에서는 개발자가 출시 비율을 정하고, 그 비율은 직접 올려야 올라간다.
문제가 보이면 출시를 중단할 수 있다.

그런데 중단해도 추가 사용자에게 퍼지지 않을 뿐, 이미 받은 사용자는 그 버전에 그대로 남는다.

Apple의 단계적 출시는 자동 업데이트를 켠 사용자 가운데 무작위로 고른 일부에게 7일에 걸쳐 1%, 2%, 5%⁠처럼 비율을 늘려 가며 보낸다.
출시를 멈춰 둘 수 있는 기간은 모두 합쳐 30일이다.

단계적 출시 중에도 누구든 App Store에서 그 버전을 직접 내려받을 수 있다.
단계적 출시가 늦추는 것은 자동 업데이트뿐이다.

배포를 멈춰도 설치된 앱은 남는다

예시 기기 10대 중 2대가 새 버전을 받은 상태다.

새 배포: 진행 중

옛 버전 설치 기기8

새 버전 설치 기기2

단계적 배포를 중단해도 이미 새 버전을 받은 기기는 그 버전을 계속 사용한다. 배포 중단은 설치 취소가 아니다.

Google Play의 단계적 출시를 기기 10대로 단순화한 예시다. 실제 배포 비율·속도가 아니며, Apple의 수동 다운로드 조건은 별도다.

결국 문제를 고치려면 고친 빌드를 다시 심사받아 올리고, 사용자가 업데이트하기를 기다려야 한다.
그동안 문제 있는 버전은 이미 받은 기기에서 계속 돈다.

바꿀 일이 생길 값은 서버에 둔다

그래서 앱을 만들 때는 어떤 값을 앱 안에 넣고 어떤 값을 서버가 정하게 할지 계속 고르게 된다.
화면 문구, 기능을 켤지 말지, 어느 서버 주소를 부를지, 응답을 몇 초까지 기다릴지 같은 값들이다.

앱 안에 넣으면 코드는 단순하다.
대신 바꾸려면 새 빌드와 심사를 거쳐야 한다.

서버가 정하게 하면 앱이 실행될 때 서버에 값을 물어봐야 해서 복잡해진다.
그 대신 문제가 생겼을 때 서버 설정만 바꿔 바로 끌 수 있다.

결정 위치에 따른 변경 반영 경로 운영 중 즉시 바꿔야 하는 판단을 앱에 고정하면 복구 경로도 심사에 묶인다. 개념 도해 결정 위치에 따른 변경 반영 경로 앱 동작의 결정 경로 앱 고정 ① 앱 실행 설치된 버전 ② 결정값 읽기 앱 번들 ③ 동작 선택 버전 고정 서버 결정 ① 앱 실행 설치된 버전 ② 결정값 조회 서버 응답 ③ 동작 선택 응답별 결정 결정 위치별 변경 반영 순서 앱 고정 ① 코드 수정 ② 심사·배포 ③ 사용자 갱신 다음 버전 서버 결정 ① 설정 변경 ② 다음 응답 ③ 동작 전환 운영 중 반영 변경 반영 순서 운영 중 즉시 바꿔야 하는 판단을 앱에 고정하면 복구 경로도 심사에 묶인다. SOSHIN · DEV NOTES
앱 안에 고정한 값은 새 빌드와 심사를 거쳐야 바뀌고, 서버가 정하는 값은 다음 응답부터 바뀐다.

기능을 켜고 끄는 데 쓰는 이런 값을 기능 플래그(feature flag)⁠라고 한다.
Firebase Remote Config 같은 서비스를 쓰면 앱 업데이트 없이 앱의 동작과 모양을 바꿀 수 있고, 설정 값을 기능 플래그로 쓸 수도 있다.

Remote Config는 앱 안에 기본값을 넣어 두고 서버에서 받은 값으로 덮어쓰는 방식이다.
네트워크가 끊겨도 앱은 기본값으로 동작해야 하므로, 서버가 정하는 값에도 앱 안의 기본값은 필요하다.

업데이트하지 않은 앱도 서버를 계속 부른다

새 앱과 옛 앱은 같은 서버를 동시에 부른다.
옛 버전이 오래 남을수록 서버도 그만큼 오래 옛 앱의 요청을 받아 줘야 한다.

응답에서 필드를 지우거나 이름을 바꾸면 새 앱은 잘 돌아도 옛 앱은 깨질 수 있다.
그래서 응답 형식은 필드를 더하는 쪽으로 바꾸고, 앱은 모르는 필드를 무시하게 만들어 둔다.

응답 필드 변경에 따른 옛 앱의 동작 새 앱과 옛 앱이 같은 응답을 읽으므로, 필드를 더하면 둘 다 동작하지만 이름을 바꾸거나 지우면 옛 앱이 찾던 필드가 사라진다. 개념 도해 응답 필드 변경에 따른 옛 앱의 동작 한 서버를 함께 부르는 두 버전 서버 응답 형식 하나 ① 같은 응답 ② 새 앱 1.2.0 바뀐 형식에 맞춘 코드 ② 옛 앱 1.1.0 price 필드를 읽는 코드 응답을 바꾸는 방식별 결과 응답 필드 · 예시 새 앱 옛 앱 필드 추가 title price + discount 정상 정상 · 모르는 필드 무시 이름 변경 title price → amount 정상 깨짐 · price 없음 필드 삭제 title price 삭제 정상 깨짐 · price 없음 새 앱과 옛 앱이 같은 응답을 읽으므로, 필드를 더하면 둘 다 동작하지만 이름을 바꾸거나 지우면 옛 앱이 찾던 필드가 사라진다. SOSHIN · DEV NOTES
아래 두 줄에서 새 앱은 정상이어도 옛 앱은 찾던 price 필드를 잃는다.

웹에서도 이미 열려 있는 탭이나 브라우저 캐시에 옛 코드가 한동안 남는다.
앱은 그 기간이 사용자가 업데이트할 때까지로 훨씬 길다.

비교할 것웹 배포앱 배포
새 코드가 닿는 방식서버에 배포하면 바로 바뀜심사와 기기 업데이트를 거침
되돌리기이전 파일로 다시 배포설치된 앱은 회수할 수 없음
옛 버전이 남는 곳열린 탭과 캐시에 잠시업데이트 전까지 사용자 기기에

필드를 지우거나 뜻을 바꿔야 할 때는 지원을 끊을 옛 버전부터 정한다.
그 버전 사용자에게 업데이트를 요구할 방법도 있어야 한다.

Android의 인앱 업데이트에는 즉시 업데이트 흐름이 있다.
사용자가 업데이트하고 앱을 다시 시작해야 계속 쓸 수 있게 하는 화면이다.

플랫폼이 이런 기능을 주지 않는다면, 앱이 서버에서 최소 지원 버전을 받아 와 직접 업데이트를 안내하게 만든다.

첫 출시 전에 넣어 둘 것

여기서 순서 문제가 하나 생긴다.
업데이트 안내든 기능 플래그든, 서버에 묻는 코드가 그 앱 안에 이미 들어 있어야 동작한다.

그런 코드 없이 나간 버전은 나중에 바꿀 길이 없다.
업데이트를 안내하는 코드마저 업데이트를 거쳐야 들어간다.

게다가 Google Play의 단계적 출시는 업데이트에만 쓸 수 있다.
Apple도 버전 업데이트를 낼 때 고르는 선택지로 두고 있어서, 첫 버전은 처음부터 모든 사용자에게 한 번에 나간다.

그래서 첫 출시 전에 최소 지원 버전 확인과, 위험한 기능을 끌 수 있는 스위치 정도는 넣어 두는 편이 안전하다.
새 기능은 그다음 업데이트부터 단계적 출시로 좁게 시작해서, 오류 보고와 지표를 보며 범위를 넓혀 간다.

소신을 상징하는 까마귀 목판화 일러스트
작성자소신 · Soshin

웹과 서버를 만들고, 개발하며 배운 것들을 글로 남깁니다.

좌우로 움직여 보세요