스트리밍 규모에서의 실시간 광고 개인화, 어떻게 설계할까
JioHotstar는 수천만 명의 동시 스트리밍 시청자에게 개인화된 광고를 실시간으로 선택하고 전달하는 분산 아키텍처를 공개했다. 단순한 광고 삽입이 아니라, 재생 중 수백 밀리초 안에 사용자 컨텍스트를 분석하고 광고를 결정해야 하는 복잡한 워크플로우다. 이 사례는 고트래픽 서비스에서 실시간 응답성과 개인화를 동시에 달성해야 하는 Java 백엔드 개발자에게 직접적인 설계 인사이트를 제공한다.
광고 의사결정(Ad Decisioning)과 워터폴 티어링
광고 요청이 들어오면 단일 서버가 처리하는 것이 아니라, 여러 광고 소스를 우선순위 계층(waterfall tier)으로 나누어 순차적으로 조회한다. 상위 티어에서 적합한 광고를 찾으면 즉시 반환하고, 실패하면 다음 티어로 내려가는 구조다. 이 방식은 수익 극대화와 응답 시간 사이의 균형을 맞추는 핵심 전략이다.
실무적으로 이 패턴은 외부 API 연동이 많은 서비스에서 자주 접하게 되는 폴백 체인(fallback chain) 과 유사하다. 각 티어는 독립적인 서비스로 분리되어 있으며, 타임아웃과 서킷 브레이커를 통해 한 티어의 장애가 전체 요청 실패로 이어지지 않도록 격리한다.
// 워터폴 티어 처리 예시 (개념 코드)
for (AdTier tier : adTiers) {
Optional<Ad> ad = tier.requestAd(context);
if (ad.isPresent()) return ad.get();
}
return fallbackAd();
페이싱 알고리즘과 레이턴시 최적화
광고 예산을 시간대별로 고르게 소진시키는 페이싱(pacing) 은 분산 환경에서 특히 까다롭다. 각 노드가 독립적으로 광고 요청을 처리하기 때문에, 중앙 집중식 카운터로 예산을 관리하면 병목이 생긴다. JioHotstar는 이를 로컬 캐시와 주기적 동기화 방식으로 해결하며, 약간의 오차를 허용하는 eventual consistency 전략을 채택했다.
레이턴시 최적화 측면에서는 다음과 같은 전략이 적용된다.
- 사전 페치(pre-fetch): 광고 슬롯이 시작되기 전에 미리 후보 광고를 가져와 캐싱
- 병렬 요청: 여러 광고 소스에 동시 요청 후 가장 빠른 응답 채택
- 타임아웃 버짓 관리: 전체 요청에 허용된 시간 예산을 각 단계에 분배하여 SLA 준수
// 병렬 광고 요청 예시
List<CompletableFuture<Ad>> futures = adSources.stream()
.map(source -> source.fetchAsync(context)
.orTimeout(50, TimeUnit.MILLISECONDS))
.toList();
CompletableFuture.anyOf(futures.toArray(new CompletableFuture[0]))
.thenApply(ad -> (Ad) ad);
서비스 간 조율과 대규모 트래픽 대응
스트리밍 플랫폼의 광고 워크플로우는 사용자 프로파일 서비스, 광고 인벤토리 서비스, 과금 서비스, 콘텐츠 메타데이터 서비스 등 다수의 마이크로서비스가 단일 요청 안에서 협력해야 한다. JioHotstar는 각 서비스가 동기적으로 강하게 결합되지 않도록 이벤트 기반 비동기 조율과 서비스별 SLA 계층화를 적용했다.
대규모 트래픽(스포츠 이벤트 등 트래픽 피크)에 대비하기 위해 샤딩과 로드 분산을 서비스 단위로 적용하고, 특정 서비스가 과부하 상태일 때 자동으로 degraded mode로 전환해 최소한의 기능을 유지한다. 이는 단순히 인프라 스케일 아웃만으로는 해결되지 않으며, 애플리케이션 레벨에서의 graceful degradation 설계가 필수임을 보여준다.
정리
- 워터폴 티어링은 광고 소스를 우선순위 계층으로 분리해 수익성과 응답 속도를 동시에 관리하는 패턴으로, 폴백 체인이 필요한 모든 고트래픽 서비스에 적용 가능하다
- 페이싱과 예산 관리는 중앙 집중 대신 로컬 캐시 + 주기적 동기화로 확장성을 확보하고, eventual consistency를 의도적으로 수용해야 한다
- 레이턴시 최적화와 graceful degradation은 인프라 확장과 별개로 애플리케이션 코드 수준에서 타임아웃 버짓, 병렬 요청, 서킷 브레이커 등으로 설계되어야 한다