프리로드 API 없는 광고 SDK에 프리로딩을 적용하며 배운 것
Frontend Engineer

매시간 황금알(aka, 플레이허브 v2.1)의 반복형 광고 미션에서 프리로딩을 수명주기로 설계하기까지
시작하며
매시간 황금알(aka, 플레이허브 v2.1)은 여러 모바일 앱 안에서 사용자가 광고에 참여하고 포인트 같은 보상을 받을 수 있는 리워드 서비스입니다. 한 가지 참여 흐름에서는 사용자가 광고를 보고 코인을 쌓은 뒤, 마지막 광고까지 마치면 황금알을 열 수 있습니다.
이 흐름에서 광고는 한 번만 등장하지 않습니다. 사용자는 코인을 쌓을 때마다 광고 참여 버튼을 누르고, 마지막에는 황금알을 열기 위해 한 번 더 광고를 봅니다. 광고가 준비될 때마다 몇 초씩 기다려야 한다면 각각의 대기는 짧더라도 전체 경험에서는 반복해서 흐름을 끊습니다.
당시 일부 광고 공급 경로는 입찰과 소재 준비에 기존 제한보다 더 긴 시간이 필요했습니다. 충분히 기다리면 보여줄 수 있는 광고가 있었지만, 기존 요청 시간 안에는 응답하지 못해 후보에서 제외되는 경우가 많았습니다. 그렇다고 모든 광고 요청의 제한 시간을 일괄적으로 늘리면, 원래 빠르게 준비되던 광고까지 함께 기다려야 했습니다.
처음의 질문은 단순했습니다.
사용자가 버튼을 누르기 전에 광고를 요청해 두면 기다림을 줄일 수 있지 않을까?
하지만 구현을 진행하면서 프리로딩은 요청 시점 하나를 앞당기는 문제가 아니라는 사실을 알게 됐습니다. 어떤 광고를 미리 요청할지, 미리 준비한 광고가 실제 노출 순서와 여전히 맞는지, 보이지 않는 광고의 노출을 언제 집계할지, 사용하지 않은 광고를 언제 폐기할지까지 함께 정해야 했습니다.
더구나 이 SDK는 프리로드를 정식으로 지원하지 않았습니다. 실제 광고 실행을 미리 시작하면 노출 시점과 SDK 상태가 어긋날 수 있다는 것을 알았지만, 광고에 진입할 때마다 반복되는 대기를 더 큰 제품 위험으로 판단했습니다. 이 글의 핵심은 단순히 광고를 일찍 요청한 방법이 아니라, 그 선택으로 새로 생긴 책임을 어떻게 제한하고 되돌릴 수 있게 만들었는지에 있습니다.
이 글에서는 매시간 황금알의 반복형 광고 미션에 프리로딩을 적용하며 광고 할당과 SDK 초기화, 노출과 집계, 실패와 복구의 경계를 어떻게 나눴는지 이야기합니다.
정확히는 매시간 황금알의 모든 광고가 아니라, 외부 참여 화면에서 특정 SDK형 광고를 미리 준비한 작업입니다. 저는 프론트엔드에서 광고 후보를 나눠 요청하고 다시 병합하는 흐름, 보이지 않는 SDK 광고를 준비했다가 실제 차례에 노출하는 수명주기, 로컬 재현 환경과 대기시간 계측을 구현했습니다. 서버의 요청별 타임아웃과 광고 우선순위 계약, 출시 뒤 발견된 SDK 정리 문제는 동료들과 함께 경계를 맞추고 후속 보완했습니다.
1. 광고를 더 오래 기다리게 할 수는 없었습니다
광고를 보여주기 전에는 여러 단계가 필요합니다. 먼저 여러 공급 경로에 광고 후보를 요청하고, 응답한 후보의 우선순위를 정한 뒤, 선택된 광고의 소재나 SDK를 준비해야 합니다. 외부 광고 공급사는 그 안에서 다시 입찰을 수행하기도 합니다.
문제가 된 공급 경로는 입찰을 마치는 데 더 긴 시간이 필요했습니다. 제한 시간을 늘리면 해당 광고가 후보에 들어올 가능성은 높아집니다. 하지만 광고 후보를 한꺼번에 평가하는 구조에서 전체 제한 시간을 늘리면 다른 공급 경로의 결과도 그만큼 늦게 확정될 수 있었습니다.
선택지는 크게 두 가지였습니다.
- 사용자가 버튼을 누른 뒤 더 오래 기다린다.
- 사용자가 화면을 살펴보는 동안 광고 준비를 먼저 시작한다.
반복형 리워드 화면에서 첫 번째 선택은 부담이 컸습니다. 코인과 황금알을 얻는 과정에서 광고 진입이 여러 번 반복되기 때문입니다. 결국 긴 입찰 시간을 사용자 대기 시간으로 그대로 전가하는 대신, 다음 행동이 광고 참여로 정해지는 순간부터 준비를 시작하기로 했습니다.
기술적으로 가장 깔끔한 선택은 아니었습니다
프리로드를 지원하지 않는 SDK의 실제 실행 경로를 미리 시작하면 노출 시점과 SDK 내부 상태가 어긋날 수 있다는 사실을 알고 있었습니다. 보이지 않는 광고가 SDK 안에서는 이미 실행 중일 수 있고, 우리가 관리하는 impression과 공급사가 인식하는 시점이 달라질 수도 있었습니다. 숨겨 둔 세션이 사용되지 않았을 때 언제 닫을지, 실제 광고 순서가 바뀌면 어떤 세션을 유지할지도 제품 코드가 책임져야 했습니다.
기술적으로 더 깔끔한 선택은 공급사가 정식 preload API를 제공할 때까지 기다리거나, 해당 광고를 송출 후보에서 제외하는 것이었습니다. 하지만 그렇게 하면 사용자가 광고를 볼 때마다 겪는 대기는 그대로 남습니다. 노출 시점의 차이는 특정 SDK에 한정되어 있었고, 관계사와 집계 기준을 맞추며 조정할 수 있었습니다. 반면 광고를 볼 때마다 생기는 대기는 사용자에게 직접 반복되며 참여 흐름을 끊었습니다. 그래서 당시에는 노출 시점의 불일치보다 반복되는 대기 경험을 먼저 줄이는 것이 더 중요한 제품 과제라고 판단했습니다.
이것은 실제 이탈률이 이미 측정됐다는 뜻은 아닙니다. 어느 위험을 먼저 줄일 것인지에 대한 제품·비즈니스 판단이었고, 판단 기준은 문제의 반복 빈도와 통제 가능한 범위, 되돌릴 수 있는지였습니다.
그래서 기술적 한계를 모른 채 프리로딩을 적용한 것이 아니라, 한계를 알고도 더 큰 문제를 먼저 줄이기로 했습니다. 적용 범위를 특정 SDK와 외부 참여 화면으로 제한하고, 피처 플래그로 즉시 되돌릴 수 있게 했으며, 준비·노출·폐기 상태를 별도 이벤트로 관측했습니다. 기술적으로 완벽하지 않은 선택을 비즈니스적으로 타당하게 만들려면, 위험을 숨기는 것이 아니라 통제 가능한 범위로 좁히고 되돌릴 수 있어야 했습니다.
피처 플래그는 단순한 비상 스위치보다 넓게 사용했습니다. off와 프리로딩 적용 값을 나누고 유닛 정보를 함께 평가하도록 배선해, 초기 코드가 배포된 뒤에는 추가 코드 작업 없이 어드민 설정만으로 선택한 유닛과 rollout 범위에 기능을 켜거나 끌 수 있게 했습니다. 작은 범위에서 시작해 적용 대상을 점진적으로 넓히거나, 문제가 생기면 즉시 기존 CTA 시점 광고 요청으로 돌아갈 수 있었습니다.
플래그 값은 CTA 이후 광고 대기시간을 기록하는 이벤트에도 함께 남겼습니다. 덕분에 같은 이벤트 스키마에서 미적용 모드와 적용 모드의 결과를 나눠 관찰하고, 작은 범위의 rollout을 점진적으로 확대할 근거를 만들 수 있었습니다. 다만 feature flag와 공통 이벤트만으로 인과적인 A/B 테스트가 성립하는 것은 아닙니다. 무작위 배정과 공급 조건 통제가 필요하므로, 이 글에서는 실제 A/B 결과나 개선 수치를 주장하지 않습니다.
여기서 중요한 기준은 버튼 자체가 아니었습니다. 버튼은 화면 개편에 따라 다른 컴포넌트로 바뀌거나 잠시 비활성화될 수 있습니다. 반면 “사용자의 다음 행동이 광고 참여다”라는 제품 상태는 더 오래 유지되는 계약입니다.
그래서 저는 프리로딩 시작 조건을 다음처럼 정의했습니다.
- 사용자가 외부 참여 화면에 있고
- 서버 상태와 사용자 식별 정보가 준비됐으며
- 앱으로 돌아가 보상을 받아야 하는 상태가 아니고
- 다음 행동이 코인 광고 또는 황금알 광고 시청일 때
프리로딩을 UI 버튼의 생명주기가 아니라 제품 단계의 생명주기에 연결한 것입니다.
SDK 세션은 먼저 실행하지만 프리로드 결과를 제품 흐름의 전제로 만들지는 않습니다. CTA 시점의 상태에 따라 소비·대기·폴백을 선택하고, 사용하지 않은 세션은 DOM과 SDK 양쪽에서 정리합니다.
2. 광고 후보를 미리 받는 것만으로는 부족했습니다
처음에는 기존 광고 후보 조회를 미리 실행하면 충분할 것으로 생각할 수 있습니다. 그러나 광고 후보가 정해졌다는 것과 실제 광고를 바로 보여줄 수 있다는 것은 다른 상태였습니다.
일반 이미지나 HTML 소재는 응답을 받은 뒤 화면에 그릴 수 있습니다. 반면 일부 SDK형 광고는 스크립트를 준비하고 광고 실행을 요청한 뒤, SDK가 내부 로딩을 마칠 때까지 기다려야 했습니다. 광고 후보를 미리 받아도 SDK 초기화를 버튼 클릭 뒤에 시작하면 사용자가 기다리는 시간은 여전히 남습니다.
그래서 저는 프리로딩을 두 단계로 나눴습니다.
- 광고 후보 선할당: 여러 공급 경로에서 사용할 광고 후보를 미리 받습니다.
- SDK 광고 선실행: 프리로딩 대상 SDK 광고가 할당됐다면 보이지 않는 상태로 초기화를 시작합니다.
SDK가 지원하는 프리로드와 우리가 만든 프리로드는 달랐습니다
여기서 ‘프리로드’라는 같은 이름으로 불리는 두 방식을 구분할 필요가 있습니다.
Google Ad Placement API처럼 프리로딩을 정식으로 지원하는 SDK가 있습니다. Google API에 preloadAdBreaks 설정을 전달하면 광고를 미리 내려받아 캐시하는 내부 과정은 SDK가 관리하고, 제품은 실제 광고 시점에 adBreak()를 호출합니다. Google의 Ad Placement API 문서도 미리 받은 광고를 이후 adBreak() 호출에서 사용한다고 설명합니다. 즉 다운로드·캐시와 실제 노출을 서로 다른 API 계약으로 다룰 수 있습니다.
반면 이번에 다룬 SDK에는 별도의 preload API가 없었습니다. 광고를 준비하는 유일한 방법은 실제 광고 실행 함수를 호출하는 것이었습니다. 따라서 우리는 광고를 캐시에 담아 달라고 요청한 것이 아니라, 실제 렌더링 경로를 먼저 시작한 뒤 생성된 광고 프레임을 숨기고 사용자 입력과 노출 신호를 보류했습니다. 실제 차례가 오면 새로 광고를 시작하는 대신 이미 진행 중인 세션을 화면에 드러냈습니다.
| 구분 | SDK가 지원하는 프리로드 | 실제 실행을 숨긴 프리로드 |
|---|---|---|
| 준비 시작 | SDK의 preload 설정·API 호출 | 실제 광고 실행 함수 호출 |
| 준비 중 화면 | SDK 내부에서 다운로드·캐시 | 광고 DOM이 만들어지지만 숨김 |
| 실제 노출 | 나중에 별도 show·break 호출 | 진행 중인 hidden 세션을 reveal |
| 수명주기 책임 | SDK가 다운로드·캐시를 관리하고 제품이 노출 시점을 결정 | 제품 코드가 DOM·SDK 세션·트래킹을 함께 관리 |
| 대표 위험 | 너무 이른 호출, 가용성 오판, 캐시 만료 | 조기 impression, 실행 중 세션 충돌, 정리 누락, 숨은 리소스 사용 |
정식 프리로드에서는 제품이 노출 시점을 결정하고 다운로드·캐시는 SDK 계약에 맡깁니다. 실제 실행을 숨긴 방식에서는 제품 코드가 실행 시작부터 reveal 또는 폐기와 SDK 종료까지 중간 상태를 직접 관리합니다.
프리로드 API가 없더라도 렌더링 비용까지 사용자 클릭 전에 지불할 수 있다는 장점은 있었습니다. 광고가 실제로 선택되면 이미 준비된 화면을 곧바로 드러낼 수 있었습니다. 대신 SDK 입장에서는 ‘미리 준비 중’이 아니라 ‘광고 실행 중’일 수 있었습니다. 제품 코드가 준비와 노출의 차이를 별도로 만들어야 했고, 사용하지 않은 세션도 명시적으로 끝내야 했습니다.
여기에는 구조적인 낭비도 있었습니다. 먼저 준비한 SDK 광고가 최종 노출 광고와 항상 같지는 않았습니다. SDK 그룹에서 대상 광고를 발견하면 hidden 실행을 시작했지만, 최종 후보를 병합한 뒤 더 높은 우선순위의 다른 광고가 선택되면 준비해 둔 세션은 노출하지 않고 폐기해야 했습니다. 프리로드 대상을 고르는 일과 실제 송출 후보를 고르는 일이 서로 다른 결정이라는 점이, 미사용 세션과 정리 책임을 만들었습니다.
즉 이 구현은 SDK가 제공하는 프리로드를 호출한 것이 아니라, 실제 광고 실행을 안전하게 숨기고 나중에 소비하는 방식으로 프리로드의 효과를 만든 것이었습니다. 뒤에서 겪은 SDK 세션 정리와 노출 집계 문제도 이 차이에서 시작됐습니다.
SDK형 광고와 이미지·HTML·영상형 광고는 준비 방식과 기다릴 수 있는 시간이 달랐습니다. 한 요청으로 모두 묶는 대신 SDK 계열과 비SDK 계열을 나눠 병렬로 요청했습니다. 비SDK 광고에는 더 긴 입찰 시간을 줄 수 있게 하고, SDK 응답은 먼저 도착하는 대로 초기화를 시작할 수 있게 했습니다.
서버에 별도의 “프리로드 광고” 개념을 만들지는 않았습니다. 기존 광고 조회 API가 광고 형식, 필요한 후보 수, 요청별 제한 시간을 받을 수 있도록 계약을 확장했습니다. 프론트엔드는 같은 API를 사용자 클릭 전이라는 다른 시점에 사용했습니다.
이렇게 하면 서버는 광고 후보를 고르는 일반적인 책임을 유지하고, 프론트엔드는 현재 제품 단계에 맞춰 요청 시점과 소비 시점을 관리할 수 있습니다.
3. 도착 순서가 광고 우선순위를 대신해서는 안 됐습니다
광고 후보를 두 그룹으로 나눠 요청하자 새로운 문제가 생겼습니다. SDK 광고와 비SDK 광고의 응답 시점이 달라졌고, 먼저 도착한 결과만으로 광고를 보여주면 원래 서버가 정한 우선순위가 깨질 수 있었습니다.
프리로딩은 광고를 빨리 준비하기 위한 기능이지 특정 공급사의 광고를 우선 노출하기 위한 기능이 아니었습니다.
각 광고 응답에는 기존 송출 정책에서 사용하던 우선순위 값이 포함됐습니다. 저는 제한 시간 안에 확보한 두 그룹의 응답을 이 값으로 다시 정렬해 하나의 후보 목록을 만들었습니다. 같은 우선순위의 광고만 한 참여 사이클 안에서 안정적인 임의 순서를 사용했습니다.
응답을 언제까지 기다릴지도 정해야 했습니다. 한쪽 응답이 끝없이 늦어지면 프리로딩 때문에 오히려 광고 참여를 시작하지 못할 수 있습니다. 일정 시간이 지나면 완료된 그룹만으로 이번 후보 목록을 확정했습니다.
여기서 타임아웃의 의미도 나눴습니다.
- 광고 공급 경로가 입찰에 사용할 수 있는 시간
- 이번 후보 목록에 결과를 포함할지 결정하는 시간
- 브라우저가 네트워크 요청 자체를 중단하는 시간
세 시간은 같지 않습니다. 후보 목록을 확정하는 시간이 지났다고 네트워크 요청까지 즉시 사라지는 것은 아닙니다. 늦게 도착한 SDK 응답은 현재 광고 순서에는 다시 끼워 넣지 않았습니다. 같은 참여 사이클이 유효한 동안 hidden 준비의 입력으로 관찰할 수는 있었지만, 확정된 후보 목록에서 소비할 수 없다면 결국 폐기했습니다.
따라서 단일 요청과 후보 집합 자체가 같다고 보장한 것은 아닙니다. 그룹별 제한 시간 때문에 확보되는 후보는 달라질 수 있었습니다. 지키려 한 계약은 ‘먼저 도착한 광고가 먼저 노출된다’가 아니라, 이번 목록에 포함된 후보들 사이에서 기존 우선순위 값이 다시 적용된다는 것이었습니다.
시간 제한은 단순히 “몇 초 후 실패”가 아니라, 늦은 결과를 어느 의사결정까지 사용할 것인지 정하는 경계였습니다.
4. 우리가 관리하는 노출 신호는 reveal까지 보류했습니다
SDK 광고를 버튼 클릭 전에 준비하려면 광고 실행을 어느 정도 선행해야 했습니다. 화면에는 보이지 않게 하고 사용자 입력도 받지 않게 했지만, SDK 내부에서는 광고가 로딩되기 시작했습니다.
이때 광고를 준비한 것과 사용자에게 실제로 보여준 것을 구분하지 않으면 노출 집계가 어긋납니다.
프리로드 단계에서 발생한 다음 신호는 바로 보내지 않았습니다.
- 서비스의 광고 노출 트래커
- 프론트엔드의 노출 이벤트
- SDK에 명시적으로 보내는 노출 신호
최종 광고 후보 목록에서 해당 광고가 실제로 선택되고 화면에 드러나는 순간까지 보관했다가 함께 전송했습니다.
hidden preload: SDK 광고 준비 → 노출 신호 보류- 실제 광고 차례: hidden 세션 reveal → 보류한 노출 신호 전송
다만 프론트엔드가 외부 SDK 내부의 모든 집계를 통제할 수 있다고 보지는 않았습니다. 우리가 관리하는 서비스 노출 시점과 SDK 내부의 블랙박스 동작을 구분했고, 운영 지표도 서비스가 실제로 reveal한 시점을 기준으로 보도록 했습니다.
서비스가 관리하는 노출 의미는 실제 reveal 시점에 맞췄습니다. 다만 공급사 SDK 내부의 impression이나 과금 판단까지 프론트엔드가 통제했다고 보지는 않았습니다. 그 경계는 관계사와 집계 기준을 맞추고 운영 데이터를 대조해야 하는 별도 위험으로 남았습니다.
5. 프리로딩이 끝나지 않아도 원래 흐름은 동작해야 했습니다
사용자는 광고가 준비될 때까지 기다린 뒤 버튼을 눌러 주지 않습니다. 네트워크가 느리면 프리로딩 도중에 먼저 CTA를 누를 수 있고, 특정 광고가 할당되지 않거나 SDK 초기화가 실패할 수도 있습니다.
프리로딩 성공을 제품 흐름의 전제로 만들지 않았습니다.
CTA를 누른 시점에는 세 가지 상태 중 하나를 선택했습니다.
| 상태 | 처리 |
|---|---|
| 광고 후보와 SDK가 준비됨 | 준비한 목록과 세션을 소비 |
| 프리로딩이 진행 중 | 정해 둔 후보 확정 시점까지 현재 요청을 기다린 뒤 결과에 따라 소비하거나 폴백 |
| 준비된 결과가 없거나 실패함 | 기존 CTA 시점 광고 요청으로 폴백 |
선택된 SDK 광고가 조기에 종료되면 다음 후보를 시도했고, 후보가 모두 실패하거나 광고가 없으면 기존 NoFill 참여 계약을 유지했습니다. 즉 프리로딩 실패가 광고 참여 실패나 보상 실패로 확대되지 않게 했습니다.
또한 피처 플래그를 끄면 광고 요청·노출·보상 흐름 전체가 기존 CTA 시점 방식으로 돌아가게 했습니다. 성능 기능을 출시할 때는 성공 경로뿐 아니라 기능 전체를 즉시 우회할 수 있는 복구 경로도 필요했습니다.
6. 화면에서 지웠다고 SDK까지 끝난 것은 아니었습니다
초기 구현 뒤에는 프리로딩이 새로운 실패 경로를 만들기도 했습니다.
Google과 같이 준비와 노출을 분리한 계약에서는 다운로드·캐시의 내부 수명주기를 API가 관리합니다. 제품 코드는 프리로드 설정과 실제 노출 호출을 연결하면 됩니다. 하지만 우리는 실제 광고 실행을 먼저 시작해 숨기고 있었기 때문에, 보이지 않는 동안에도 SDK 세션과 DOM을 직접 관리해야 했습니다. 이 구조적인 차이가 첫 번째 운영 문제로 이어졌습니다.
사용하지 않게 된 hidden 광고 프레임을 DOM에서 제거하면 세션도 끝난다고 생각했습니다. 그러나 외부 SDK 안에는 광고가 여전히 실행 중인 상태로 남아 있었습니다. 다음 광고를 준비하면서 다시 광고 실행을 요청하면 SDK는 이미 광고가 진행 중이라며 새 요청을 거절했습니다.
사용자에게는 광고가 보이지 않고 로딩 화면만 유지되다가 건너뛰기 안내로 넘어가는 현상으로 나타났습니다.
출시 뒤 팀의 후속 분석에서는 정리의 책임을 두 단계로 다시 나눴습니다.
- 화면에서 hidden 광고 프레임을 제거한다.
- 아직 실행 중인 SDK 세션을 명시적으로 종료한다.
반대로 같은 광고가 새로운 할당 식별자로 다시 내려온 경우에는 무조건 세션을 종료하고 다시 시작하지 않았습니다. 광고 자체와 광고 네트워크가 같다면 진행 중인 세션을 재사용하고, 화면에서 세션을 찾기 위한 식별 정보만 새 할당에 맞췄습니다. 노출 트래킹은 실제 렌더링을 시작한 광고의 맥락을 유지했습니다. 그렇지 않으면 이미 준비된 광고가 닫혔다 다시 로딩되며 화면이 깜빡일 수 있었기 때문입니다.
여기서 한 가지 기준이 생겼습니다.
UI 리소스의 수명과 외부 SDK 세션의 수명은 같지 않습니다.
React 컴포넌트와 DOM을 정리하는 것만으로 외부 시스템의 상태까지 정리됐다고 가정해서는 안 됐습니다.
7. 무엇을 측정해야 하는지도 다시 정했습니다
광고 요청을 페이지 진입 시점으로 앞당겼다는 사실만으로 사용자의 경험이 좋아졌다고 말할 수는 없습니다.
프리로딩의 목적은 페이지 로딩 시간을 줄이는 것이 아니라, 사용자가 CTA를 누른 뒤 광고를 기다리는 시간을 줄이는 것이었습니다. 그래서 다음 결과를 하나의 기준으로 기록할 수 있게 했습니다.
- 광고가 화면에 나타남
- SDK 광고가 시작됨
- 광고가 없어 안내 화면으로 전환됨
- 일정 시간 뒤 대체 CTA가 나타남
- 사용자가 광고 화면을 닫음
각 결과에는 CTA 이후 경과 시간과 프리로드 피처 플래그의 원본 값, 실제 적용 모드, 미리 받은 후보 사용 여부, hidden SDK 세션 사용 여부를 함께 남겼습니다. 같은 이벤트 스키마로 미적용 모드와 적용 모드를 나눠 볼 수 있게 했습니다. 다만 광고 노출, NoFill, 대체 CTA, 사용자 종료는 의미가 다른 결과이므로 하나의 대기시간 분포로 섞지 않고 결과 유형별로 해석해야 했습니다.
관측도 제품 코드의 부속물이 아니라 기능의 일부였습니다. 프리로딩처럼 요청 시점과 소비 시점이 분리된 기능은 사용자의 한 번의 행동이 여러 비동기 작업으로 흩어집니다. 문제가 생긴 뒤 원인을 재구성하려면 사용자의 참여, 광고 요청, 후보, SDK 세션을 같은 맥락으로 연결할 수 있어야 했습니다.
이 글에서 실제 대기시간이 몇 퍼센트 줄었다고 주장하지는 않습니다. 구현과 계측 구조는 확인했지만, 같은 조건의 프리로드 적용 전후 운영 데이터를 이 글의 근거로 확정하지 못했기 때문입니다. 이 작업에서 확인할 수 있었던 성공 조건은 프리로드 성공 여부와 관계없이 기존 참여 흐름으로 복구되는지, 실제 reveal 전에는 서비스 노출 신호를 보내지 않는지, 사용하지 않은 SDK 세션을 종료할 수 있는지였습니다.
프리로딩을 바라보는 기준이 달라졌습니다
처음에는 광고 프리로딩을 네트워크 요청 시점을 앞당기는 성능 최적화로 생각했습니다.
구현과 운영을 거치며 판단 기준은 다음처럼 달라졌습니다.
- 프리로딩 시점은 UI 이벤트보다 제품 상태를 기준으로 정합니다.
- 광고 할당과 SDK 준비를 서로 다른 단계로 봅니다.
- 도착 순서가 기존 광고 우선순위를 대신하지 않게 합니다.
- 준비와 실제 노출·집계를 분리합니다.
- 프리로딩 실패 시 원래 참여 흐름으로 돌아갈 수 있어야 합니다.
- 성공 여부는 요청 시작 시점이 아니라 사용자가 기다린 시간으로 측정합니다.
- SDK가 제공하는 프리로드와 실제 실행을 숨겨 만든 프리로드의 책임 범위를 구분합니다.
이 선택은 반복되는 사용자 대기가 크고, 적용 범위를 제한할 수 있으며, 피처 플래그로 즉시 되돌릴 수 있을 때 타당했습니다. 반대로 공급사 집계와의 차이가 계속 커지거나, 미사용 세션과 예외 처리 비용이 기능 확장 속도를 막거나, 정식 preload 계약을 지원하는 대안이 생긴다면 더 이상 임시 구조를 유지할 이유가 없습니다.
프리로드를 지원하지 않는 SDK 위에 만든 hidden 세션, 노출 신호 보류, 재할당·폐기 로직은 모두 제품 코드가 대신 떠안은 복잡성입니다. 당장의 사용자 경험을 위해 그 비용을 빌렸다면, 위 신호가 나타날 때 정식 preload 계약으로 전환하거나 공급사와 계약을 개선하고 제품 코드의 수명주기 보정 로직을 걷어내야 합니다.
저는 이것이 의도적으로 선택한 기술부채에 가깝다고 생각합니다. 기술부채는 단순히 코드가 지저분하다는 뜻이 아니라, 지금의 제품 가치를 위해 미래의 유지보수 비용을 알고 빌리는 결정입니다. 중요한 것은 부채를 만들지 않는 척하는 것이 아니라, 왜 감수했는지와 어떤 위험을 관측할지, 언제 어떤 조건에서 갚을지를 함께 남기는 일입니다.
광고를 빨리 보여주려면 일을 더 일찍 시작하면 된다는 생각은 절반만 맞았습니다. 일을 일찍 시작할수록 아직 사용하지 않은 광고, 늦게 도착한 응답, 외부 SDK 세션처럼 관리해야 할 중간 상태도 늘어납니다.
결국 프리로딩의 핵심은 “얼마나 빨리 요청했는가”가 아니었습니다.
아직 노출하지 않은 광고를 어느 참여를 위해 준비했고, 언제 실제 광고로 전환하며, 사용하지 않았다면 어떻게 안전하게 끝낼 것인가?
이 질문에 답할 수 있을 때, 프리로딩은 단순한 선요청을 넘어 사용자 대기 시간을 줄이는 제품 기능이 될 수 있습니다.


