Перейти к содержанию

Case 01. Real-time Betting Antifraud

Практикум · Связанная теория · Редакционный стандарт

Тип материала — практический архитектурный кейс. HYPOTHETICAL — все численные параметры, сценарий и схемы. INFERENCE — выбор и критерии. UNKNOWN — фактическая производительность и параметры, которые предстоит согласовать.

1. Business problem

Оператор хочет удерживать подозрительные ставки до денежного эффекта. История событий остаётся доступной для расследований, а свежесть признака видна при каждом решении.

2. Functional requirements

Для игрока считать 20 и более уникальных попыток за 60 секунд. В этом упражнении считаются requested attempts, включая текущую. Сохранять решение, версию правила, причину и evidence.

3. Non-functional requirements

Целевой бюджет синхронного решения 200 мс от ingress до durable outcome. Учебное распределение 25 мс routing, 45 мс feature lookup, 35 мс rules, 65 мс commit, 30 мс slack. Перцентили отдельных стадий нельзя складывать как end-to-end p99. Проверять tail на полном пути.

4. Assumptions и вопросы заказчику

Assumption — вход 10 000 событий/с состоит из разных игроков и типов событий. Спросить долю requested, максимальный skew, доверенный timestamp, допустимые late events и какие категории допускают fail-open. UNKNOWN — измеренная цена Redis script и вероятность timeout при failover.

5. Оценка нагрузки и объёма данных

HYPOTHETICAL. Средняя скорость 10000 events/s, пик 20000 events/s, payload mean/p95/p99 900/1400/2400 B. Перцентили заданы отдельно для тестового профиля, среднее используется для объёма. Суточный профиль упрощён до постоянного среднего, накладные расходы протокола, индексов и WAL исключены.

Метрика Формула и подстановка Результат
Объём событий 10000 × 86 400 864000000 событий/сутки
Сырые данные 864000000 × 900 B 777.6 GB/сутки
Retention без сжатия 777.6 GB × 7 суток 5443.2 GB
Broker copies при RF=3 5443.2 GB × 3 16329.6 GB, из них 10886.4 GB дополнительные копии
Producer bandwidth, среднее 10000 × 900 × 8 / 10⁶ 72 Mbit/s
Producer bandwidth, пик 20000 × 900 × 8 / 10⁶ 144 Mbit/s
Partitions при 30% headroom max(ceil(20000 × 1,3 / 1 500), ceil(20000 × 900 × 1,3 / 1 800 000)) 18 partitions
Consumer parallelism ceil(20000 × 1,3 / 1 500) 18 workers на группу при принятой производительности
Backfill одних суток при 3× средней capacity и живом среднем потоке (10000 × 86 400) / (3 × 10000 − 10000) 43 200 с = 12 ч

GB и Mbit здесь десятичные. 1 500 events/s на partition/worker и 1,8 MB/s на partition — допущения теста. Для варианта без брокера строки RF/partitions относятся к альтернативному потоку или downstream outbox. RF не моделирует egress каждого consumer: добавьте по одной копии трафика на активную группу и репликационный трафик до RF−1 дополнительных копий. Пересчитайте отдельно при catch-up на пике, skew и p99 payload. Если capacity ≤ live rate, конечного времени восстановления нет.

При 60-секундном окне и 10 000 уникальных событий/с верхняя оценка 600 000 identities для всего потока, при принятом пике 1 200 000. Реальный объём Redis определяется overhead ZSET, ключей и dedup retention. Архивные 7 суток служат replay, а не TTL hot state.

6. Варианты архитектуры

A — API вызывает Redis script с bounded prune, dedup и count. B — Kafka и Flink ведут keyed event-time windows и публикуют signals. C — inline Redis guards плюс Flink для поздних corrections, device correlation и review.

7. Сравнение вариантов и выбор

Выбрать C при условии, что inline counters и ledger guard выдерживают измеренный tail. B без bounded synchronous lookup не даёт свежего решения на текущий запрос. A дешевле по числу компонентов, но сужает историю признаков и усложняет recovery.

8. Component / data-flow

Иллюстративная схема выбранного варианта.

flowchart LR
 A["Bet API"] --> B["Inline risk gate"]
 B --> C["Atomic feature state"]
 B --> D["Decision journal and outbox"]
 D --> K["Kafka and Flink async windows"]
 K --> S["Versioned signals"]
 S --> B

9. Normal processing sequence

sequenceDiagram
 participant A as Bet API
 participant B as Inline risk gate
 participant C as Atomic feature state
 participant D as Decision journal and stream
 A->>B: Send identified input
 B->>C: Update unique request in player window
 C-->>B: Return count and generation
 B->>D: Persist decision and outbox
 D-->>B: Commit acknowledged
 B-->>A: Return decision under deadline

Ключ player scope и logical request ID поступают в script. API проверяет feature age, применяет policy, сохраняет decision и outbox. Stream обогащает сигналы и помечает signal generation.

10. Failure / recovery sequence

sequenceDiagram
 participant B as Inline risk gate
 participant C as Atomic feature state
 participant O as Recovery owner
 B->>C: Attempt write or advance
 Note over B,C: Counter timeout after possible mutation
 O->>C: Retry atomic script with same identity
 C-->>O: Prior update or explicit unavailability
 O->>B: Use verified count or configured fallback

После timeout исход increment UNKNOWN. Retry с тем же ID и fingerprint. При outage высокорисковые операции отправляются в review либо отклоняются по согласованной policy. Ограниченный fail-open возможен только в утверждённом классе с журналированием. После потери Redis восстановить shadow generation из trusted cut, сравнить coverage и переключить pointer.

11. Data model и contracts

request_id, tenant_id, player_id, received_at, event_time, stake_minor, rule_version. Decision содержит count, window bounds, signal age и generation. Redis window выбирает [t−60с, t] для уже прошедших событий с включением текущей identity, а позднее событие вне policy попадает в async review.

12. Consistency boundaries

Redis atomic script сериализует окно и dedup в одном hash slot. Decision DB фиксирует outcome отдельно. Checkpoint Flink не охватывает произвольный внешний write. Spend commit проверяет ledger независимо от risk signal.

13. Scaling и capacity planning

Hot player ограничивается rate cap и bounded work per call. Дополнительные partitions не разделят один keyed state без изменения семантики окна. Раздельно нагрузить balanced traffic, burst и single-player flood.

14. Observability

Записывать decision latency, queueing, Redis timeout, script duration, fallback и watermark/feature age. Алерт на отсутствие свежей generation связывается с выбранной degraded policy.

15. Data Quality и reconciliation

Сравнить counter на фиксированном cut с уникальными request IDs и time bounds. Проверить missing/duplicate и одинаковые решения на одинаковом rule context. Сверить outbox с decision journal.

16. Security и compliance

Псевдонимизировать player, ограничить доступ к reason evidence, версионировать retention по юрисдикции. Удаление ключа не должно отменять обязательный audit record.

17. Implementation roadmap

Сначала offline replay с labeled cuts, затем shadow online без блокировок, затем ограниченный inline rollout по классу риска с rollback policy.

18. Testing и acceptance criteria

Повторы, одинаковый ключ с изменённым payload, граница окна, timeout после increment, late event и failover. Acceptance — один counted request на logical identity, наблюдаемая degraded policy и подтверждённый e2e tail на согласованном профиле. Тест SLA остаётся NOT_RUN.

19. Trade-offs и ограничения

Гибрид требует двух источников operational evidence и согласования generations. Подтверждение условной цели 200 мс зависит от измерений.

20. Interview questions

Почему Redis timeout опаснее reject? Команда могла примениться, поэтому blind retry увеличит count. Что делать с событием, пришедшим после watermark? Применить allowed-lateness policy к derived state, сохранив исходное решение и отдельную revision для review.

Первичные источники

Проверка источников: 2026-11-03. Документация описывает свойства компонентов. Результаты испытаний выбранной системы требуют отдельного evidence.