Codepush ‐ hot updater 적용 - study-pals/frontend GitHub Wiki

배경

마이크로소프트에서 App Center를 통한 RN의 CodePush 지원을 중단함에 따라, 대안을 탐색하고 이를 프로젝트에 적용한다.

목표

  • 무료이거나, 최대한 과금 요소가 적은 선택지를 채택한다.
  • 외부 인프라(서비스)를 이용할 경우, 추후 다른 인프라로 이전하는 상황에 대처 가능하도록 확장성 있는 인터페이스를 설계한다.

리서치

CodePush의 App Center로부터의 독립 선언문

위 포스팅을 이정표로 리서치 방향을 잡았다.

요약하면 MS의 CodePush(정확히는 App Center) 지원 종료에 대응하기 위해 대체재를 탐색하고, 결국 직접 CodePush를 위한 인프라를 구축하게 되기까지의 여정을 담은 내용이다.

글쓴 분은 회사에서 aws를 사용하는데, 당시에 hot-updater가 supabase, cloudflare만 옵션으로 제공하고 있어 hot-updater를 사용하지 않으셨다.

하지만 내 경우에는 그러한 제약이 없기에 이 hot-updater를 더 자세히 알아보고자 했다.

Hot Updator

공식 문서

토스의 FE 개발자 강선규 님이 만드신 서비스이다.

버전 컨트롤과 콘솔을 제공하며 현재는 supabase, cloudflare, aws s3, firebase 네 가지 provider를 지원한다(25-08-11 시점).

Provider 정하기

codepush에 사용할 storage Provider를 정해야 한다. hot-updater가 지원하는 네 가지 클라우드 서비스에 대해, 월간 사용량에 따른 과금 규모를 예측해 보았다.

gpt 5

요청비용(GET·Class B 기준)

월간 GET 수 Cloudflare R2 AWS S3(Standard) Supabase Storage Firebase Storage(GCS)
1만 $0.0036 $0.004 $0 $0.004
10만 $0.036 $0.04 $0 $0.04
100만 $0.36 $0.40 $0 $0.40

근거: R2 Class B $0.36/백만, S3 GET $0.0004/천, GCS Class B 예시 $0.0004/천. Cloudflare Amazon Web Services, Inc. Google Cloud

저장비용(GB·월)

월 저장량 Cloudflare R2 AWS S3(Standard) Supabase Storage* Firebase Storage(GCS)
1 GB $0.015 $0.023 $0.00 $0.026
10 GB $0.15 $0.23 $0.00 $0.26
100 GB $1.50 $2.30 $0.00 $2.60

egress 비용(인터넷 전송)

월 egress Cloudflare R2 AWS S3(Standard) Supabase Bandwidth** Firebase Storage(GCS)***
1 GB $0.00 $0.09 $0.00 $0.00
10 GB $0.00 $0.90 $0.00 $0.00
100 GB $0.00 $9.00 $0.00 $10.80
1 TB (1024 GB) $0.00 $92.16 $69.66 $121.68
  • *Supabase Pro는 250 GB/월 포함 후 $0.09/GB 초과 과금. ***Firebase는 10 GiB/월 무료

OTA 업데이트 구조상 egress 비용이 대부분일 것이므로, egress 비용이 무료인 Cloudflare R2를 채택하기로 결정하였다.

Cloudflare R2는 어떻게 무료로 egress를 제공하는가?

Cloudflare R2는 자체적인 CDN 내에 구현되어 있는데, 이 CDN의 이점을 살려 인터넷 전송(egress)에 대한 단가를 따로 매기지 않는다고 한다. Cloudflare의 유저 유입 전략의 일부로 볼 수 있을 것 같다.

즉, hot-updater 같이 많은 디바이스에 JS 번들을 뿌리는 시나리오에서는 egress 비용이 없는 cloudflare가 aws S3, Supabase, Firebase와 대비해 비용 측면에서 압도적으로 유리하다.

그렇다면 Flaw는 없을까?

egress를 무료로 제공하는 것에 대한 반대급부로, 추가적인 과금 요소나 어떠한 Flaw가 존재할지 모른다는 생각이 들었다. 아래는 분석 결과이다.

gpt 5

저장 용량 과금

  • 표준 스토리지: $0.015/GB·월 (S3 Standard의 약 65% 수준)
  • 별도 “아카이브/인프리퀀트 액세스” 계층이 없어, 장기 보관 비용 절감 여지가 S3·GCS 대비 적음.
  • 즉, 장기간 대용량 데이터 보관에는 S3 Glacier나 GCS Archive 같은 초저가 계층이 없어 불리할 수 있음.

트랜잭션 레이턴시

  • R2는 Strong Consistency지만, S3처럼 수십억 객체를 다루는 초대규모 워크로드에서의 지연 데이터가 아직 많다는 평가가 있음.
  • egress 무료를 보완하려고 속도보다 비용에 최적화된 설계라서 초당 요청 처리량이 S3보다 낮을 수 있음.

지역 선택 제한

  • 현재는 Cloudflare 글로벌 네트워크 기반이라서, 특정 리전에만 저장하는 옵션이 없음(데이터 레지던시 이슈).
  • 금융·정부 규제나 GDPR 등에서 물리적 리전 고정이 필요하면 제약이 될 수 있음.

인프라 연계 의존성

  • R2 단독 사용은 문제 없지만, Cloudflare Workers나 Pages, Stream과 함께 쓰면 편리함 + 낮은 레이턴시 확보 가능.
  • 반대로, Cloudflare 에코시스템에 락인이 걸릴 수 있음 → 나중에 다른 스토리지로 이전 시 마이그레이션 트래픽이 한 번에 몰리면, 그 순간은 무료 egress라도 요청 수/변환 처리 비용이 커질 수 있음.

egress 비용이 무료라는 이점을 상회할 만한 결점은 없다고 판단하고, Cloudflare를 통한 hot-updater 환경 구축을 시작했다.

구현

1. Cloudflare prerequisites

Cloudflare 계정 생성

https://www.cloudflare.com/

Cloudflare 계정이 없다면, 홈페이지에서 계정을 생성한다.

API 토큰 생성

R2 스토리지와 D1 데이터베이스에 edit 권한이 있는 API 토큰을 생성해야 한다.

위 화면에 진입하여 Create Token 버튼을 누르고, Custom Token을 생성한다.

위의 두 가지 권한을 토큰에 부여해야 한다. 나머지는 건드릴 필요 없다.

worker.dev 서브도메인 발급

만약 cloudflare 계정을 새로 생성했다면, Cloudflare 대시보드에서 Workers & Pages 탭에 들어가주어야 한다. 그러지 않을 시 workers.dev 서브도메인이 생성되지 않아 hot-updater init 과정에서 404 에러가 발생하게 된다.

2. hot-updater init

우선 프로젝트에 dev 디펜던시로 hot-updater를 설치하고, init을 명령한다.

npm i -D hot-updater
npx hot-updater init

hot-updater는 init 시에 대화형 cli를 제공하여 수월하게 init을 끝마칠 수 있었다. 이후 cli의 지시만 잘 따라가면 init이 완료되고, 프로젝트 루트에 .env와 hot-updater.config.ts 파일이 생성된다.

3. 프로젝트 configuration

https://hot-updater.dev/guide/providers/2_cloudflare

여기까지가 공식 문서의 Step 3: Generated Configurations 부분이다. 이제 공식 문서의 Step 4: Add HotUpdater to Your Project 부터 그대로 따라가면 된다.

4. Update 전략 결정

hot-updater는 fingerprint, app version 의 두 가지 업데이트 전략을 제공한다.

fingerprint 방식

fingerprint 방식은 앱 빌드 시 생성된 네이티브 코드에 대해 fingerprint(해시값)을 생성하고, 이 fingerprint 값을 기반으로 OTA 대상을 구분한다.

같은 fingerprint를 가지는 앱들은 동일한 네이티브 바이너리를 공유하므로, JS 번들만 교체하는 OTA 업데이트가 가능하다.

네이티브 코드가 변경되면 새로운 fingerprint가 생성되며, 이 경우 스토어를 통한 바이너리 업데이트가 필요하다.

app version 방식

app version 방식도 기본적인 앱 업데이트 로직은 fingerprint와 동일하다. 다만 네이티브 코드를 해시하여 fingerprint를 생성하는 대신, 개발자가 직접 관리하는 앱 버전을 통해 OTA 대상을 구분한다.

나의 선택: fingerprint 방식

hot-updater의 기본 업데이트 전략은 fingerprint이다. 기본값으로 제공할 만큼 그 구현에 결함은 없을 것으로 생각되고(기존에 expo에서 사용하던 @expo/fingerprint를 확장하였다), 앱의 네이티브 버전을 관리하는 부담을 줄일 수 있으므로 fingerprint 방식을 채택하였다.

5. JS 번들 배포

hot-updater deploy 시에 현재의 네이티브 코드를 해시하여 현재 생성되어 있는 fingerprint와 동일한지 판별하는 과정을 거친다(만약 생성되어 있는 현재의 fingerprint가 네이티브 코드를 해시한 값과 일치하지 않는다면, 현재의 fingerprint가 stale하다는 의미이므로 deploy가 거부된다). 때문에 첫 1회 수동으로 fingerprint를 생성해주어야 한다.

새로운 fingerprint를 생성한 후에는, fingerprint와 네이티브 코드 사이의 정합성을 위해 반드시 앱을 리빌드해주어야 한다. 그렇지 않으면 앱 바이너리에 새로운 fingerprint가 반영되지 않는다.

Important: After creating a new fingerprint, you must rebuild your app. This ensures that the updated native environment and the new fingerprint are correctly incorporated into the app binary, preventing compatibility issues with apps built using the previous fingerprint.

npx hot-updater fingerprint create
npx hot-updater deploy -p <android | ios>

6. hot-updater 동작 테스트

https://hot-updater.dev/guide/simulator-test

앱이 실행될 때 JS번들 버전 체크 GET 요청을 보내고, Cloudflare의 worker가 정상적으로 이 요청을 수신하는 것을 확인할 수 있다.

7. 콘솔 설정

npx hot-updater console 명령어를 통해 지금까지의 deploy 내역을 확인하고, 특정 JS 번들에 대해 force update를 강제할지 여부 또한 설정할 수 있다.

동작 확인

JS 번들을 deploy한 후, 앱이 스스로 업데이트를 반영하는 것을 확인할 수 있다.

https://github.com/user-attachments/assets/1de91e3b-1469-45ee-86b4-d22e7bf55d25