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.