
배치 구조를 탈피하고 단 한 번의 Redis 조회로 1분부터 7일까지의 광고 노출을 제어하는 고성능 스트리밍 아키텍처
토스 광고 시스템이 기존 배치 중심의 Frequency Capping 구조를 탈피하여 Apache Flink 기반의 완전한 실시간 스트리밍 시스템으로 전환한 과정을 다룹니다. 대규모 상태 데이터를 처리하면서 발생하는 RocksDB의 기술적 한계를 분석하고, 백필과 캐치업이라는 2단계 전략을 통해 데이터 정합성을 유지하며 무중단 전환에 성공한 실전 경험을 공유합니다. 특히 윈도우 크기에 따라 달라지는 병목 패턴과 이를 해결하기 위한 런타임 튜닝 전략이 핵심입니다.
대규모 상태 데이터를 Flink RocksDB로 관리하며 성능 병목을 겪고 있거나, 복잡한 배치 시스템을 실시간 스트리밍으로 전환하려는 데이터 엔지니어에게 강력히 추천합니다.
기존의 광고 노출 횟수 제한(Frequency Capping) 시스템은 Redis와 Airflow 배치를 혼합한 구조로, 실시간성이 떨어지고 복잡한 DAG 의존성 관리 및 서빙 시 여러 계층을 합산해야 하는 운영 부담이 있었습니다. 특히 1분부터 7일까지의 다양한 슬라이딩 윈도우 구간을 정밀하게 처리하면서도 서빙 레이어의 조회를 단일화해야 하는 과제가 있었습니다.
Apache Flink를 도입하여 집계 구간별 특성(Minutes, Hours, Days)에 따라 3개의 앱으로 분리하고, RocksDB를 상태 저장소로 활용하여 7일치 대규모 데이터를 실시간 처리하도록 설계했습니다. 백필(Backfill)과 캐치업(Catch-up) 파이프라인을 분리하여 초기 정합성을 확보하고, RocksDB의 Write Stall 및 Filter Block Cache Miss 문제를 해결하기 위한 맞춤형 튜닝과 Flink Changelog 설계를 적용했습니다.
7일 구간 기준 약 230GB에 달하는 대규모 상태 데이터를 실시간으로 집계할 수 있게 되었으며, 서빙 레이어에서는 Redis 단일 조회만으로 1분~7일 구간의 데이터를 제공받을 수 있게 되어 복잡도가 대폭 감소했습니다. 또한 상태(State)를 단일 진실 공급원(SSOT)으로 관리함으로써 장애 시에도 Redis를 정확하게 재구성할 수 있는 운영 신뢰성을 확보했습니다.
Trade-off
앱을 3개로 분리함으로써 병목 지점을 독립적으로 해결할 수 있게 되었으나, 관리해야 할 Flink 잡의 개수가 늘어나고 공통 코드베이스 내에서의 설정 분기 로직이 추가되었습니다. 또한 RocksDB 상태 규모가 커짐에 따라 체크포인트 I/O 부하가 증가하여 이를 상쇄하기 위한 추가적인 아키텍처적 장치가 필요했습니다.
특정 사용자에게 광고가 과도하게 노출되는 것을 방지하기 위해 정해진 기간 내 노출 횟수를 제한하는 광고 기술 메커니즘입니다.
Apache Flink에서 대규모 상태 데이터를 로컬 디스크에 저장하고 관리하기 위해 사용하는 고성능 키-값 저장소 엔진입니다.
과거의 누적 데이터를 처리하는 과정(Backfill)과 실시간 이벤트를 따라잡으며 상태를 동기화하는 과정(Catch-up)을 의미합니다.


