이미지 주소를 바로 넘겨 봤다 글 표지
Launch LabReact Native이미지캐시

이미지 주소를 바로 넘겨 봤다

이벤트 이미지 URL을 RN Image에 직접 넣는 모드를 추가했다. 파일 저장을 생략했을 때 기다리는 구간과 실패 처리가 어떻게 달라지는지, 같은 URL로 다시 열면 서버 요청이 오는지 확인했다.

연재 중

Launch Lab

읽는 중03

이벤트에 맞춰 바뀌는 앱의 첫 화면을 만들며, 이미지 준비와 실패 처리의 기준을 정하는 React Native 개발 기록입니다.

전체 목차
  1. 01앱을 켤 때 이벤트 이미지를 받아 봤다
  2. 02이벤트 이미지 요청을 일부러 실패시켜 봤다
  3. 03이미지 주소를 바로 넘겨 봤다현재 글

앞선 두 실험에서는 서버 이미지를 파일로 내려받아 표시했다. 실행 중에 새 파일을 준비할지, 이전에 준비한 파일부터 보여줄지를 비교했다.

그렇다면 서버가 준 이미지 주소를 Image에 바로 넘기면 어떨까. 앱에서 파일을 저장하는 단계 없이도 이벤트 화면을 만들 수 있다. 이 방식까지 비교하고 나서 직접 파일을 관리할 이유를 판단하기로 했다.

기존 앱에 원격 URL 모드를 하나 더 넣었다. 이번 질문은 다운로드 코드를 줄였을 때 시작 화면의 기다림도 줄어드는가였다.

파일 대신 URL을 넘겼다

React Native는 원격 URL을 Image의 source로 받는다. 크기는 레이아웃으로 정하고 이미지 요청과 로드는 컴포넌트에 맡길 수 있다. React Native 이미지 문서

이벤트 정보 API를 읽고 응답을 검증하는 코드는 유지했다. 달라진 부분은 그다음이다. 기존 모드에서는 이미지 바이트를 받아 파일에 쓰고, 그 파일의 URI를 화면에 넘겼다. 원격 모드에서는 응답에 들어 있는 주소를 그대로 사용했다.

const campaign = await fetchCampaign(signal, run);
// No image fetch, getSize, prefetch, or file write on this path.
return campaign ? { ...campaign, uri: campaign.imageUrl } : null;

화면은 같은 Image를 쓴다. 아래의 decision.asset.uri가 기존 모드에서는 로컬 파일 주소이고, 이번 모드에서는 HTTP 이미지 주소다.

source={{ uri: decision.asset.uri }}

원격 모드는 앱이 관리하던 이미지 캐시를 읽거나 갱신하지 않도록 했다. 다른 모드에서 저장한 이미지가 실패 시 대신 나오는 것도 막았다. 이번에는 그 파일 관리가 없는 조건을 보려는 것이었다. 실제 서비스에서 원격 요청 실패 시 저장 이미지로 대체하는 조합까지 금지한다는 뜻은 아니다.

OS의 첫 네이티브 시작 화면은 그대로 뒀다. 이번에도 그다음에 나오는 RN 이벤트 화면을 비교했다.

주소를 받았다고 이미지가 뜬 건 아니었다

기존과 같은 Flip3의 Android 15 릴리스 앱을 사용했다. Mac의 실험 서버에 USB로 연결했고, 이벤트 정보 응답은 700ms, 이미지 응답은 200ms 늦췄다. 새 URL에는 캠페인 버전을 붙여 이전 이미지와 구분했다.

원격 URL 모드에서는 785ms에 이미지 주소를 골랐다. 하지만 onLoad는 1061ms에 왔다. 주소를 화면에 넘긴 뒤에도 이미지가 도착하기를 기다리고 있었다.

같은 B 이미지로 실행 중 파일 준비 방식과, 준비된 파일을 먼저 표시하는 방식도 실행했다.

실행 방식이미지 선택이미지 onLoad홈 layout
원격 URL 직접 표시785ms1061ms2130ms
실행 중 파일로 준비1031ms1066ms2135ms
준비한 파일 먼저 표시14ms66ms1116ms

시간은 JS 관찰 코드의 시작점부터 쟀다. 각 조건을 한 번씩 실행한 흐름 기록이며 앱 전체 시작 시간이나 성능 벤치마크는 아니다. 첫 원격 실행에서는 이미지 캡처도 함께 했다. 몇 ms 차이로 어느 방식이 빠르다고 결론낼 수는 없다.

원격 URL과 실행 중 파일 준비는 모두 이번 실행에서 네트워크 응답을 기다렸다. 다만 기다리는 위치가 달랐다. 전자는 주소를 고른 뒤 Image에서, 후자는 파일을 준비한 뒤 주소를 넘기는 구조였다. 준비된 파일을 먼저 쓰는 모드는 갱신 요청을 보내면서도 현재 이미지 표시를 그 응답에 걸어 두지 않았다.

원격 URL로 받은 B 캠페인을 표시한 Flip3 실기기 화면
이번에는 파일을 직접 저장하지 않고 같은 B 이미지를 띄웠다.

세 방식 모두 로드 성공 뒤 1초를 유지했다. 이 시간은 비교 조건이다. onLoad가 실제 사용자에게 완전히 보인 순간을 뜻하지 않으므로 정확한 광고 노출 시간으로도 쓰지 않았다.

같은 URL로 다시 열었더니

앱이 파일을 저장하지 않는다고 캐시까지 없다고 가정하지는 않았다. 네이티브 이미지 로더의 처리가 남아 있어서 앱 로그와 서버 요청 로그를 함께 봤다.

B의 URL을 그대로 두고 앱 프로세스를 종료한 뒤 원격 모드로 다시 열었다. 이벤트 정보는 새로 받았다. 이번에는 761ms에 주소를 고르고 791ms에 onLoad가 왔다. 그 사이 서버에 이미지 요청은 없었다. 최초 실행에는 이미지 요청이 한 번 있었고, 같은 URL의 재실행에는 없었다.

서버의 이미지 응답에는 Cache-Control: no-store도 붙어 있었다. 그래도 이 실행에서 요청 없이 로드된 사실은 남았다. 앱이 작성한 저장 코드와 별개로 이미지 로더 쪽 캐시의 영향을 받은 것으로 해석했다. 어떤 저장 계층에서 읽었는지까지 추적한 것은 아니어서 이를 Android 전체의 캐시 규칙으로 일반화하지는 않았다.

오류 실험에는 처음부터 새로운 버전이 붙은 URL을 사용했다. 이미 로드한 주소를 그대로 둔 채 서버만 실패시키면, 이미지 요청이 서버까지 오지 않아 실패 조건을 만들지 못할 수 있었다. 실제로 요청이 왔는지도 함께 확인했다.

Image에 넘긴 뒤의 실패도 처리했다

원격 모드에서는 이벤트 정보 조회가 성공해도 이미지가 실패할 수 있다. 따라서 정보 조회 성공과 이미지 표시 결과를 따로 기록했다. 홈 화면에도 선택한 이벤트 이름과 실제 표시 결과를 나눠 적었다.

이미지 요청에 HTTP 503을 반환하자 정보 조회는 성공한 상태로 남았고, Image의 오류 콜백에서 홈으로 이동했다. 이 실행에서는 onLoad가 없었다. 서버 로그에도 해당 이미지 요청과 503 응답이 남았다.

이번에는 이미지 응답만 4초 늦춰 봤다. 기존 파일 모드의 1.5초는 다운로드와 저장까지의 제한이었다. 원격 모드는 별도 파일 준비가 없으므로 정보 조회부터 이미지 onLoad까지에 1.5초를 줬다. URL을 받은 뒤 다시 1.5초를 더하지 않고 남은 시간만 썼다. 제한을 판단하는 끝 지점까지 동일한 두 구현은 아니다.

원격 이미지 응답을 기다리며 기본 로고와 로딩 표시를 보여주는 실제 화면
주소는 받았다. 이미지는 아직 오는 중이었다.

이 실행에서 정보 조회는 776ms에 끝났고, 792ms에 이미지 로드가 시작됐다. 준비 제한으로 인트로를 끝낸 시점은 1510ms, 홈 layout은 1555ms였다. 홈 진입을 확인한 뒤에도 4.5초 더 로그를 수집했지만 다시 이미지로 돌아가거나 홈을 중복으로 여는 기록은 없었다.

서버 쪽에서는 이미지 응답을 다 보내기 전에 연결이 닫혔다. 이 기기에서는 뷰가 사라지면서 진행 중인 요청도 종료된 것으로 해석했다. 모든 환경에서 네트워크가 즉시 취소된다고 보장하는 코드는 아니다. 별도로, 제한이 지난 뒤 로드 콜백이 도착해도 화면을 다시 열지 않는 처리를 단위 테스트로 확인했다.

정보 요청 자체의 실패와 4초 지연, 응답을 받았을 때 이미 만료된 이벤트, 이벤트가 없는 경우도 실행했다. 모두 기본 화면이나 홈으로 이어졌다. 실제 셀룰러 단절을 재현한 실험은 아니다.

직접 저장할 이유부터 정해야겠다

원격 URL만으로도 이벤트 이미지를 표시할 수 있었다. 앱이 이미지 파일을 저장하고 정리하는 코드는 이 모드에 필요하지 않았다. 그래도 로딩 화면, 제한 시간, 오류 뒤 이동은 남았고 같은 주소를 다시 읽을 때는 캐시도 개입했다.

이번 실행에서 서버가 고른 이벤트를 받아 보여주고 실패하면 넘어가도 된다면, 원격 URL 방식부터 시작할 만했다. 네트워크 응답을 기다리지 않고 준비한 이벤트부터 보여주며 새 이벤트는 다음 실행에 반영하려면, 어떤 파일을 언제 사용할지 앱이 관리하는 쪽이 명확했다. 그만큼 만료와 파일 정리도 맡아야 한다.

세 모드는 비교용으로 남겨 뒀다. iOS에서의 로딩과 캐시, 실제 통신 단절은 아직 확인하지 않았다. 이벤트가 준비되지 않았을 때 기본 화면까지 1초를 유지할지도 남아 있는 선택이다.