stanislav_dubich@dev:~$ cat ads-platform/README.md

Ads Platform

B2B видеореклама VAST/VMAP. Миллионы показов/сутки. CPM-аукцион в реальном времени. Точный учёт бюджетов до копейки.

Бизнес-задача

Платформа связывает рекламодателей и паблишеров. Рекламодатель задаёт максимальный CPM и бюджет, паблишер — минимальный CPM. Платформа проводит аукцион, показывает рекламу, списывает деньги, начисляет выплаты — всё в реальном времени.

Ключевая задача

Запрос на рекламу должен вернуть VAST за < 50ms, но при этом нужно: проверить баланс, провести аукцион по CPM, отфильтровать по гео/формату/тегам, сгенерировать XML с трекинг-пикселями.

Решение: разделение на два сервиса. Java — админка, управление кампаниями, финансы. Go — горячий путь: аукцион, отдача VAST, приём событий. Горячие данные (кампании, балансы) — в Redis, Go читает только оттуда.

┌─────────────────┐     ┌─────────────────┐
│  Java Backend   │────▶│    PostgreSQL   │
│  (админка, API) │     │  (users, camps) │
└────────┬────────┘     └─────────────────┘
         │ sync
    ┌────▼────┐
    │  Redis  │◀────────────────────────────┐
    │ (cache) │                             │
    └────┬────┘                             │
         │ read-only                        │
┌────────▼────────┐     ┌─────────────────┐ │
│   Go Service    │────▶│      NATS       │ │
│ (аукцион, VAST) │     │   (events)      │ │
└────────┬────────┘     └────────┬────────┘ │
         │                       │ batch    │
         │              ┌────────▼────────┐ │
         └─────────────▶│   ClickHouse    │─┘
           GeoIP lookup │  (analytics)    │
                        └─────────────────┘
                

Почему два сервиса

Проблема: админка и показ рекламы — разные паттерны нагрузки. Админка: сложные формы, транзакции, отчёты. Показ: тысячи RPS, задержка критична.

Решение: Java (Spring Boot) для админки — ORM, Security, сложная бизнес-логика. Go (fasthttp) для горячего пути — минимум аллокаций, горутины, < 10ms на запрос. Независимое масштабирование: Go-поды автоскейлятся по CPU.

Аукцион второй цены (Redis)

Как работает: активные кампании индексированы в Redis Sorted Set по CPM. При запросе: пересечение множеств (формат ∩ теги ∩ geo), сортировка по ставке, проверка баланса. Победитель платит ставку второго места + 1 цент (аукцион второй цены — стандарт AdTech для честной конкуренции). Всё за O(log N).

Финансовый учёт: горячий кошелёк + холодный журнал

Проблема: баланс нужен мгновенно (проверка перед показом), но также нужен полный аудит транзакций.

Решение: гибридная модель. Redis хранит текущий баланс — атомарный DECRBY при каждом показе, проверка за O(1). PostgreSQL хранит append-only журнал транзакций для финансовой отчётности, сверки и восстановления Redis после сбоя.

Сверка: фоновая задача сверяет балансы Redis с суммой событий в PostgreSQL, исправляет расхождения, алертит при аномалиях.

Пакетная обработка событий (NATS → ClickHouse)

Проблема: 100M запросов/день = 100M INSERT-ов. ClickHouse любит пакеты, не любит одиночные вставки.

Решение: Go публикует события в NATS без ожидания ответа. Отдельный потребитель накапливает буфер (1000 событий или 2 сек таймаут), затем пакетный INSERT. Нагрузка на ClickHouse снижена в 1000 раз.

Генерация VAST/VMAP

IAB-стандарт для видеоплееров. VAST — одиночный ролик. VMAP — несколько рекламных блоков (до, в середине). XML генерируется на лету с URL-ами для трекинга.

Путь без аллокаций: sync.Pool для буферов, быстрые шаблонизаторы вместо encoding/xml. Минимизация нагрузки на сборщик мусора при высоком RPS.

Стек

Java Backend

Java 21 Spring Boot 3 Spring Data JPA Spring Security Liquibase

Go Service

Go 1.25 fasthttp go-redis clickhouse-go NATS MaxMind GeoIP2

Data

PostgreSQL ClickHouse Redis NATS JetStream S3 (видео)

Infra

Kubernetes ArgoCD Prometheus NGINX Ingress Sealed Secrets

Моя роль

Архитектура с нуля, реализация обоих сервисов (Java + Go), настройка Kubernetes-инфраструктуры.