① AdFlow - Mini OTT Ad Platform 프로젝트 설계

2026. 8. 27. 19:58ㆍPROJECT/Toy Project

AdFlow - Mini OTT Ad Platform 프로젝트 설계

1. 프로젝트 개요

목표

대규모 요청 환경에서 광고를 저지연으로 선택·노출하고, 발생한 광고 이벤트를 유실·중복 없이 처리하여 정산 가능한 데이터로 만드는 미니 OTT 광고 플랫폼을 구현한다.

이번 프로젝트는 단순한 성능 테스트나 기술 데모가 아니라, 사용자가 직접 조작하고 결과를 확인할 수 있는 실제 서비스 형태의 결과물을 만드는 것을 우선한다.

핵심은 다음과 같다.

  • 광고 캠페인을 직접 생성할 수 있다.
  • OTT 플레이어에서 실제 광고가 노출된다.
  • 사용자 조건에 따라 서로 다른 광고가 선택된다.
  • Frequency Cap과 광고 예산 소진을 실제 화면에서 확인할 수 있다.
  • Impression / Click 이벤트가 실시간으로 수집된다.
  • 관리자 대시보드에서 광고 성과를 확인할 수 있다.
  • Traffic Simulator로 가상의 대규모 사용자를 발생시킬 수 있다.
  • 이후 부하 테스트와 성능 개선은 실제 서비스의 확장 과정으로 진행한다.

2. 프로젝트 핵심 방향

기존처럼 테스트 자체가 결과물이 되지 않도록 다음 순서로 진행한다.

Product
실제로 사용할 수 있는 광고 플랫폼 구현

↓

Problem
사용자 증가와 이벤트 증가로 문제 발생

↓

Experiment
문제를 재현하고 측정

↓

Engineering
Redis / Kafka / 동시성 제어 등으로 해결

↓

Result
개선된 시스템이 실제 서비스 화면에서도 동작

즉,

"k6로 3,000 RPS를 테스트했다"

가 아니라

"실제로 동작하는 광고 플랫폼을 만들고, 사용자가 증가했을 때 발생한 문제를 측정하고 개선했다"

를 프로젝트의 전체 스토리로 가져간다.


3. 주요 사용자 시나리오

3.1 광고주 / 운영자

광고 관리 콘솔에서 캠페인을 생성한다.

예시:

캠페인명: 아이폰 신제품 광고
예산: 50,000원
타겟 연령: 20 ~ 30대
타겟 장르: 스포츠
Frequency Cap: 사용자당 하루 2회
상태: ACTIVE

운영자는 다음 정보를 확인할 수 있다.

  • 노출 수
  • 클릭 수
  • CTR
  • 사용 예산
  • 잔여 예산
  • 캠페인 상태
  • Frequency Cap 상태

3.2 OTT 사용자

사용자가 OTT 콘텐츠를 재생하면 광고 요청이 발생한다.

사용자 A
28세
관심 장르: 스포츠

↓

축구 콘텐츠 재생

↓

Ad Serving API 호출

↓

타겟 조건에 맞는 광고 선택

↓

광고 재생

↓

Impression 이벤트 발생

같은 사용자가 광고를 여러 번 보게 되면 Frequency Cap이 적용된다.

1번째 재생 → 아이폰 광고
2번째 재생 → 아이폰 광고
3번째 재생 → 다른 광고

예산이 모두 소진되면 해당 광고는 더 이상 노출되지 않는다.

50,000원
↓
39,000원
↓
21,000원
↓
3,000원
↓
0원

ACTIVE → BUDGET_EXHAUSTED

4. 주요 화면

프로젝트 결과물은 아래 4개 화면을 중심으로 구성한다.

4.1 OTT Player

광고가 실제로 노출되는 사용자 화면이다.

기능:

  • 콘텐츠 재생
  • 광고 요청
  • 광고 영상 또는 광고 카드 노출
  • Impression 발생
  • 광고 클릭 시 Click 이벤트 발생
  • Frequency Cap 적용 결과 확인
  • 예산 소진 시 광고 변경 확인

예시:

┌─────────────────────────────────────┐
│             Mini TVING              │
│                                     │
│            [ 콘텐츠 영상 ]           │
│                                     │
│  ─────────────●────────────         │
│                                     │
│           광고가 시작됩니다          │
│                                     │
└─────────────────────────────────────┘

4.2 Campaign Console

광고 캠페인을 생성하고 관리하는 관리자 화면이다.

기능:

  • 캠페인 생성 / 수정 / 중지
  • 광고 소재 등록
  • 예산 설정
  • 노출 기간 설정
  • 타겟 조건 설정
  • Priority 설정
  • Frequency Cap 설정
  • 캠페인 상태 조회

예시:

캠페인: 아이폰 신제품 광고
예산: 50,000원
타겟: 20 ~ 30대
장르: 스포츠
Frequency Cap: 하루 2회
상태: ACTIVE

4.3 Ad Dashboard

광고 이벤트와 성과를 확인하는 운영 대시보드다.

주요 지표:

  • Impression
  • Click
  • CTR
  • 사용 예산
  • 잔여 예산
  • Ad Requests / sec
  • Impression / sec
  • Kafka Consumer Lag
  • p95 / p99 latency
  • Error Rate

예시:

아이폰 캠페인

노출            1,831
클릭               42
CTR              2.29%
오늘 사용 예산    9,155원
잔여 예산        40,845원

████████░░░░░░░░ 18.3%

실시간 이벤트 스트림도 제공한다.

LIVE AD EVENTS

19:21:01  IMPRESSION   Campaign 12
19:21:02  IMPRESSION   Campaign 12
19:21:02  CLICK        Campaign 12
19:21:03  IMPRESSION   Campaign 31
19:21:04  IMPRESSION   Campaign 12

SSE 또는 WebSocket을 사용해 실시간으로 표시한다.


4.4 Traffic Simulator

실제 사용자 수천 명을 대신하여 가상의 OTT 사용자를 발생시키는 시뮬레이터다.

기능:

  • 동시 사용자 수 설정
  • 연령대 분포 설정
  • 콘텐츠 장르 분포 설정
  • 사용자별 광고 요청 생성
  • Impression / Click 이벤트 생성
  • 캠페인별 광고 분배 결과 확인

예시:

User Simulator

동시 사용자
[ 1000 ]

사용자 분포
20대 █████████ 40%
30대 ███████   30%
40대 ████      20%
기타 ██        10%

콘텐츠
☑ 스포츠
☑ 드라마
☑ 예능

[ ▶ Simulation ]

시뮬레이션 실행 후 실시간 결과:

Active Users     1,000
Ad Requests/s      834
Impressions/s      761

현재 가장 많이 노출되는 광고

1. Nike    31%
2. Apple   24%
3. Netflix 17%

5. 시스템 아키텍처

                       ┌─────────────────┐
                       │ Campaign Admin  │
                       └────────┬────────┘
                                │
                                ▼
                         PostgreSQL
                                │
                         Campaign Sync
                                │
                                ▼
                             Redis
                                │
                                │
Client ───────► Ad Serving API ─┼────► Ad Selection
                                │
                                ├────► Frequency Cap
                                │
                                ├────► Budget / Pacing
                                │
                                └────► External Ad Adapter
                                           │
                                           ▼
                                     Mock Ad Exchange

Client
  │
  ├── impression
  └── click
          │
          ▼
      Event API
          │
          ▼
        Kafka
          │
     ┌────┴─────┐
     ▼          ▼
 Dedup Worker  Aggregator
     │          │
     └────┬─────┘
          ▼
      PostgreSQL
          │
          ▼
     Billing Report

6. 광고 선택 규칙

광고 요청이 들어오면 다음 순서로 후보 광고를 필터링한다.

1. 활성 상태의 캠페인인가?
2. 캠페인 시작 / 종료 기간 안인가?
3. 예산이 남아 있는가?
4. 사용자가 Frequency Cap을 초과하지 않았는가?
5. 사용자 타겟 조건에 부합하는가?
6. 콘텐츠 장르 조건에 부합하는가?
7. 후보 중 Priority가 가장 높은 광고를 선택한다.

추후 가중치 기반 선택이나 Pacing 알고리즘을 추가할 수 있다.


7. MVP API

초기에는 아래 API만 구현한다.

Campaign

POST /campaigns
GET /campaigns
GET /campaigns/{id}
PATCH /campaigns/{id}
POST /campaigns/{id}/creatives

Ad Serving

GET /ads?userId={userId}&contentId={contentId}

Event

POST /events/impression
POST /events/click

Dashboard

GET /dashboard/campaigns/{campaignId}
GET /dashboard/summary

Simulator

POST /simulations
POST /simulations/{id}/start
POST /simulations/{id}/stop

8. 핵심 도메인

Campaign

Campaign
- id
- name
- status
- budget
- spentBudget
- startAt
- endAt
- priority
- targetAgeMin
- targetAgeMax
- targetCategory
- frequencyCap

Creative

Creative
- id
- campaignId
- type
- mediaUrl
- clickUrl

User

User
- id
- age
- preferredCategories

Content

Content
- id
- title
- category

AdEvent

AdEvent
- eventId
- campaignId
- creativeId
- userId
- contentId
- type
- occurredAt

9. 기술 스택

Backend

Java 21
Spring Boot
Spring Data JPA

Data

PostgreSQL
Redis
Kafka

Frontend

Next.js
React

프론트엔드는 시연 가능한 수준에 집중하고, 프로젝트의 중심은 Backend에 둔다.

Observability / Test

k6
Prometheus
Grafana

Infra

Docker Compose

추후 필요 시:

gRPC
Kubernetes
AWS

MongoDB는 명확한 사용 이유가 생기지 않는 한 넣지 않는다.


10. 개발 단계

Phase 1. 실제 사용 가능한 제품 MVP

목표는 성능이 아니라 실제 서비스 형태 완성이다.

구현:

  • Campaign CRUD
  • Creative 등록
  • User / Content 샘플 데이터
  • 광고 선택 로직
  • OTT Player
  • Impression / Click 이벤트
  • 기본 Dashboard

완료 조건:

Campaign Console에서 광고를 생성하고, OTT Player에서 해당 광고가 실제로 노출되며, Dashboard에서 Impression과 Click을 확인할 수 있다.


Phase 2. Frequency Cap

조건:

동일한 사용자는 동일한 광고를 하루 최대 N번까지만 볼 수 있다.

초기 구현:

GET frequency

if frequency < cap
    INCR frequency

문제:

동시 요청에서 Race Condition 발생 가능.

개선 후보:

  • Redis Atomic Operation
  • Lua Script

결과는 OTT Player에서 동일 광고가 더 이상 노출되지 않는 형태로 직접 확인한다.


Phase 3. Budget Control

캠페인 예산이 소진되면 광고 노출을 중단한다.

초기에는 PostgreSQL 기반으로 구현하고 동시 요청 시 Overspending 문제를 재현한다.

비교 후보:

1. DB Pessimistic Lock
2. DB Optimistic Lock
3. Redis DECR
4. Redis Lua Script

비교 지표:

  • 처리량
  • p95
  • p99
  • Overspending 여부
  • 실패율

결과는 Dashboard에서 예산이 감소하고 최종적으로 캠페인이 BUDGET_EXHAUSTED 상태로 전환되는 모습으로 보여준다.


Phase 4. Kafka Event Pipeline

Impression / Click 이벤트를 Kafka 기반으로 처리한다.

OTT Player
   ↓
Event API
   ↓
Kafka
   ↓
Consumer
   ↓
Aggregation

목표:

  • API와 이벤트 처리 분리
  • 대량 이벤트 처리
  • Consumer 장애 대응
  • 재처리 가능 구조

Phase 5. 이벤트 중복과 정합성

Kafka 재처리 상황을 가정한다.

IMPRESSION
IMPRESSION
IMPRESSION

하나의 이벤트가 여러 번 처리되면 정산 데이터가 잘못될 수 있다.

해결:

At-least-once Delivery
+
Idempotent Consumer
+
eventId 기반 Deduplication

검증:

  • 중복 이벤트 주입
  • 최종 집계 데이터 확인
  • 정산 데이터와 실시간 데이터 비교

Phase 6. 실시간 Dashboard

Kafka Consumer에서 처리되는 광고 이벤트를 SSE 또는 WebSocket으로 전달한다.

표시 항목:

Impression / sec
Click / sec
CTR
Ad Request / sec
Campaign Budget
Kafka Lag
p99
Error Rate

목표는 시스템 내부에서 처리되는 이벤트를 사용자가 직접 볼 수 있게 만드는 것이다.


Phase 7. Traffic Simulator

가상의 사용자를 생성하여 광고 요청을 발생시킨다.

Simulator 내부 동작:

User 생성
↓
Content 선택
↓
Ad Request
↓
광고 노출
↓
Impression
↓
일부 확률로 Click

시뮬레이터를 통해 다음을 직접 확인할 수 있어야 한다.

  • 캠페인별 노출 분배
  • Budget 감소
  • Frequency Cap
  • 요청량 증가
  • Kafka Event 증가
  • Dashboard 변화

11. 성능 개선 실험

성능 실험은 제품이 완성된 이후 진행한다.

Experiment 1. PostgreSQL 기반 광고 서빙

초기 구조:

Ad Request
   ↓
Campaign SELECT
   ↓
Targeting SELECT
   ↓
Budget SELECT
   ↓
Frequency SELECT
   ↓
Response

부하 단계:

100 RPS
500 RPS
1,000 RPS
3,000 RPS

측정:

RPS
p50
p95
p99
Error Rate
DB Connection
CPU

문제가 확인된 이후 Redis를 도입한다.

핵심은 Redis를 사용했다는 사실이 아니라 다음을 증명하는 것이다.

PostgreSQL 기반 광고 후보 검색 구조에서 병목이 발생했고, Redis 도입 후 동일 조건에서 응답 지연과 DB 부하가 얼마나 개선되었는지 측정한다.


Experiment 2. 광고 예산 동시성

조건:

남은 광고 노출 가능 횟수 = 100
동시 요청 = 1,000

정상 기대값:

광고 노출 = 100

잘못된 구현에서는 100회를 초과하는 Overspending이 발생할 수 있다.

해결 방식을 비교한다.

Pessimistic Lock
Optimistic Lock
Redis Atomic
Lua Script

Experiment 3. Frequency Cap Race Condition

조건:

한 사용자는 하루 최대 3회 노출

동시 요청을 발생시켜 3회를 초과하는 상황을 재현한 뒤 Redis 기반 원자적 연산으로 해결한다.


Experiment 4. Kafka Event Duplication

동일한 eventId를 가진 이벤트를 여러 번 발행한다.

검증:

입력 이벤트 = 3건
실제 유효 이벤트 = 1건
최종 집계 증가량 = 1

12. 정산 데이터 구조

광고 이벤트는 돈과 연결되기 때문에 실시간 통계와 최종 정산 데이터를 구분한다.

Realtime Counter
        +
Source of Truth Event
        +
Reconciliation Batch

Realtime

사용자 대시보드에서 빠르게 확인하기 위한 값.

Source of Truth

실제 발생한 광고 이벤트 기록.

Reconciliation

실시간 집계와 원본 이벤트를 비교하여 차이를 보정한다.

이 구조를 통해 다음 문제를 다룰 수 있다.

실시간 Dashboard에는 Impression이 10,001건인데 정산 데이터에는 10,000건이라면 어떻게 할 것인가?


13. 외부 광고 플랫폼 연동

실제 외부 플랫폼 대신 Mock Ad Exchange를 구현한다.

AdFlow
  ↓
External Ad Adapter
  ↓
Mock Ad Exchange

추후 VAST 형태의 응답 구조를 단순화하여 구현할 수 있다.

목표:

  • 외부 광고 플랫폼 연동 구조 경험
  • Adapter 패턴 적용
  • 외부 장애 / Timeout 처리
  • Fallback 광고 제공

14. Legacy Migration 실험

신규 광고 플랫폼으로 점진 전환하는 구조를 추가할 수 있다.

Legacy Ad Server
        │
        │ 90%
        ▼
      Client
        ▲
        │ 10%
        │
New Ad Server

전환 단계:

Legacy 100%
↓
Legacy 90% / New 10%
↓
Legacy 50% / New 50%
↓
New 100%

Shadow Traffic을 통해 두 시스템의 결과를 비교할 수 있다.

Legacy Result
vs
New Result

이를 통해 안정적인 점진 전환 전략을 실험한다.


15. AI 활용 방향

AI는 프로젝트 초기에 억지로 넣지 않는다.

핵심 시스템을 완성한 뒤 운영 자동화 영역에서 선택적으로 적용한다.

예시:

Grafana Alert
     ↓
AI Incident Analyzer
     ↓
관련 메트릭 분석
     ↓
원인 후보 정리
     ↓
Runbook 제안

또는:

  • 장애 로그 요약
  • Kafka Lag 이상 원인 분석
  • 캠페인 이상 트래픽 탐지 결과 설명
  • 성능 테스트 결과 자동 요약

AI 자체보다 운영 업무 자동화라는 목적을 우선한다.


16. 프로젝트 결과물

최종적으로 GitHub README나 포트폴리오에서 아래 흐름을 직접 보여줄 수 있어야 한다.

Demo Scenario

1. Campaign Console에서 광고 캠페인 생성

2. OTT Player에서 콘텐츠 재생

3. 사용자 조건에 맞는 광고 노출

4. Impression / Click 이벤트 발생

5. Dashboard에서 실시간 성과 확인

6. 같은 사용자가 반복 재생
   → Frequency Cap으로 광고 변경

7. Traffic Simulator 실행
   → 요청량 / 이벤트량 증가

8. 캠페인 Budget 감소
   → 예산 소진 후 광고 중단

9. Dashboard에서 시스템 지표 확인

이 시나리오 하나만으로도 프로젝트가 단순 Backend API 모음이 아니라 실제 동작하는 광고 플랫폼처럼 보이게 한다.


17. 프로젝트에서 보여주고 싶은 역량

Backend Design

  • Spring 기반 서버 설계
  • 도메인 모델링
  • API 설계
  • 서비스 분리

Database

  • PostgreSQL 모델링
  • 트랜잭션
  • 동시성 제어
  • Lock 전략 비교

Cache / Real-time

  • Redis
  • Frequency Cap
  • Budget Counter
  • Atomic Operation

Event Driven Architecture

  • Kafka
  • Producer / Consumer
  • At-least-once
  • Idempotent Consumer
  • Deduplication

Performance

  • k6 부하 테스트
  • RPS
  • p95
  • p99
  • Error Rate

Observability

  • Prometheus
  • Grafana
  • Kafka Lag
  • 시스템 메트릭

Product Thinking

  • 단순 기술 데모가 아닌 실제 서비스 형태의 결과물
  • 사용자 관점의 시연 가능한 기능
  • 성능 개선이 실제 서비스에 어떤 영향을 주는지 확인

18. 프로젝트 한 줄 소개

대규모 OTT 환경을 가정해 광고 캠페인 관리, 저지연 광고 서빙, Frequency Cap, 예산 제어, Kafka 기반 이벤트 처리와 정산 정합성을 구현한 미니 광고 플랫폼.

조금 더 포트폴리오스럽게 표현하면:

실제 광고 노출부터 이벤트 수집과 정산 데이터까지 이어지는 광고 플랫폼을 구현하고, 트래픽 증가 과정에서 발생하는 캐시, 동시성, 이벤트 중복, 데이터 정합성 문제를 측정하고 개선한 프로젝트.


19. 우선 개발 순서

초반에는 욕심내지 않고 아래 순서로 진행한다.

1. Campaign Domain
2. Creative Domain
3. Campaign CRUD
4. User / Content Mock Data
5. Ad Selection API
6. OTT Player
7. Impression / Click Event
8. Campaign Dashboard
9. Frequency Cap
10. Budget Control
11. Kafka Event Pipeline
12. Traffic Simulator
13. Real-time Dashboard
14. Performance Test
15. Concurrency Experiment
16. Event Deduplication
17. Reconciliation
18. Legacy Migration Experiment

첫 번째 목표는 다음 한 문장으로 잡는다.

Campaign Console에서 광고를 하나 생성하고 OTT Player를 실행했을 때 내가 만든 광고가 실제로 재생되게 만든다.

여기까지 만든 뒤 성능과 분산 시스템 문제를 하나씩 추가한다.

반응형