본문으로 건너뛰기
zoe

"이건 뭐야?"

테스트 빌드를 같이 보던 백엔드 팀원이 화면을 가리키며 물었다. "이건 뭐야?"

방금 전에 앱을 스토어에서 업데이트한 참이었다. 우리가 넣어둔 강제 업데이트 안내를 따라 최신 버전을 받았고, 앱이 새로 떴다. 그런데 몇 초 뒤 "새로운 업데이트가 있어요"라는 다이얼로그가 또 화면을 덮고 있었다. 방금 업데이트했는데, 앱은 또 업데이트하라고 하고 있었다. 팀원이 당황한 게 당연했다.

강제 업데이트와 OTA가 겹칠 때

우리 앱이 업데이트를 미는 경로는 두 개다. 하나는 스토어를 통한 네이티브 업데이트다. 네이티브 코드나 설정이 바뀌면 버전이 올라가고 스토어 심사를 거친다. 여기에 "이 버전 아래로는 못 쓴다"는 최소 버전 게이트를 걸어두면, 너무 오래된 버전을 쓰는 유저는 스토어로 보내진다.

다른 하나는 OTA다. JS 번들이나 에셋만 바뀐 경우, 스토어 심사 없이 앱 안에서 바로 번들을 갈아끼운다. 버그 핫픽스나 문구 수정이 여기 해당한다. 심사가 없으니 빠르다.

문제는 이 둘이 유저에게는 똑같은 "업데이트"로 보인다는 것이다. 강제 업데이트로 방금 스토어에서 새 빌드를 받았는데, 그 빌드가 뜨자마자 앱은 OTA 서버를 확인한다. 스토어 빌드가 나온 뒤 발행된 OTA가 하나라도 있으면, 앱은 그걸 내려받고 다이얼로그를 띄운다. 유저 입장에서는 "방금 했는데 왜 또?"가 된다. 팀원이 본 장면이 정확히 그거였다.

조용함이 기본이어야 했다

원래 우리 OTA는 새 번들을 받으면 무조건 다이얼로그를 띄웠다. "업데이트가 준비됐어요, 지금 재시작할까요?"

그런데 이 물음 자체가 대부분 불필요했다. OTA 도구는 번들을 내려받아도 그 자리에서 앱을 갈아엎지 않는다. 다운로드만 해두고, 다음에 유저가 앱을 완전히 껐다 켤 때 조용히 새 번들로 뜬다. 즉 다이얼로그로 "지금 재시작?"을 묻지 않아도, 어차피 다음 실행 때 알아서 적용된다. 매번 묻는 건 사용자의 흐름을 끊는 방해였을 뿐이다.

업계 기본값을 확인해봐도 그랬다. CodePush든 expo-updates든, 기본 동작은 조용히 받아두고 다음 실행에 적용하는 쪽이다. 사용 중에 다이얼로그로 끼어드는 건 오히려 드문 선택이었다.

그런데 긴급 픽스는 빨라야 한다

그렇다고 전부 조용히 두면 다른 문제가 생긴다. 모바일 앱은 콜드 스타트가 의외로 드물다. 며칠씩 앱을 완전히 끄지 않는 유저가 많다. 로그인이 풀리는 것 같은 급한 버그를 고쳤는데 조용히만 두면, 그 픽스가 유저에게 닿기까지 며칠이 걸릴 수 있다.

그래서 필요한 건 "대부분 조용히, 긴급할 때만 알림"이었다. 여기서 진짜 문제가 시작됐다. 앱이 "이 업데이트가 긴급인지"를 어떻게 알 것인가.

OTA에는 버전이 없다

강제 업데이트 쪽은 쉬웠다. 네이티브 버전은 3.2.1, 3.2.2처럼 숫자로 올라가니까, "최소 버전"과 비교하면 그만이다. 그런데 OTA는 버전이 바뀌지 않는다. OTA는 같은 네이티브 빌드 위에서 JS만 교체하는 거라, 버전 라벨은 그대로다. 버전으로는 OTA끼리 구분할 수 없다.

그럼 OTA끼리는 무엇으로 구분하나. 시스템이 주는 건 두 가지뿐이다. 하나는 업데이트 ID인데, 순서가 없는 UUID라 "더 최신"을 따질 수 없다. 다른 하나는 생성 시각인데, "더 나중"은 알아도 "긴급이었나"는 담고 있지 않다.

결국 OTA에는 "몇 번째 긴급이냐"를 나타낼 내장 숫자가 없었다. 없으니 만들어야 했다. criticalIndex라는 값을 앱 설정에 심고, 앱이 지금 실행 중인 번들의 값과 새로 받은 업데이트의 값을 비교하게 했다. 새 값이 더 크면 긴급으로 본다.

숫자를 어떻게 올릴 것인가

처음엔 그냥 1, 2, 3으로 세는 카운터를 생각했다. 긴급 배포할 때마다 하나씩 올리는 식이다. 그런데 여기서 한 가지 질문이 걸렸다. 왜 "긴급이다/아니다"의 참거짓이 아니라 숫자인가.

참거짓으로 하면 긴급 배포를 건너뛴 기기가 픽스를 놓친다. 긴급 픽스(참)를 못 받은 유저가 곧바로 다음 일반 배포(거짓)를 받으면, 거짓을 보고 "긴급 아니네" 하며 조용히 넘어간다. 놓친 긴급 픽스를 영영 못 받는다.

숫자면 이야기가 다르다. 긴급 배포에서 값을 1로 올리고, 이후 일반 배포는 그 값을 그대로 둔다. 긴급을 건너뛴 기기(현재값 0)가 다음 일반 배포(값 1)를 받으면, 0이 1보다 작으니 "내가 못 받은 긴급이 있었구나"를 감지한다. 값이 한 방향으로만 올라가기 때문에, "이 값보다 작으면 그 사이에 긴급이 있었다"가 자연히 표현된다.

카운터의 불편함은 올릴 때마다 이전 값을 기억해야 한다는 것이었다. 그래서 날짜 정수로 바꿨다. 20260709처럼. 긴급 배포할 때 그냥 오늘 날짜를 넣으면 어제 값보다 무조건 크다. 같은 날 두 번이면 뒤에 순번을 붙여 20260709_01, 20260709_02. 이전 값을 들추지 않아도 단조 증가가 보장되고, 숫자만 봐도 언제 낸 긴급인지 읽힌다.

미리 생각했어야 하는 것

팀원의 "이건 뭐야?"가 없었다면 한동안 몰랐을 문제다. 되짚어보면 처음 OTA 다이얼로그를 넣을 때 이 겹침을 생각했어야 했다. "업데이트를 받았으니 알려주자"가 자연스러워 보였지만, 그 알림이 강제 업데이트 직후에 겹쳐 뜬다는 것, 그리고 대부분의 OTA는 유저가 알 필요조차 없다는 것을 놓쳤다.

기본값을 "알림"으로 둔 게 실수였다. 방해는 예외여야 하는데 그걸 기본으로 깔아뒀다. 사용자에게 무언가를 알리는 기능을 넣을 때, 다음부터는 "알려야 하는 경우"보다 "알리지 않아도 되는 경우"를 먼저 세어볼 생각이다. 조용히 지나가도 되는 일을 굳이 붙잡고 묻지 않는 것도 설계다.