B. Betting Event Analytics¶
Тип материала: учебный архитектурный сценарий. Не описывает систему конкретной компании.
Границы и предпосылки¶
Kafka4.1 → ClickHouse25.8; authoritative bet/settlement state хранится upstream. События содержат tenant_id,event_id,event_version,immutable event_month,occurred_at,status,amount_minor. Одинаковый key+version обязан иметь одинаковый payload; ordering version назначается владельцем сущности. HYPOTHETICAL, без заявленных throughput/latency/SLA; совместная runtime compatibility не проверена.
Архитектура¶
Все узлы и связи — HYPOTHETICAL.
flowchart TB
Producer --> Kafka[Partitioned Kafka log]
Kafka --> Consumer[Kafka engine and MV]
Consumer --> Revisions[ReplacingMergeTree revisions]
Revisions --> Latest[Latest version per business key]
Latest --> Analytics[Corrected analytical result]
Consumer --> Quarantine[Malformed event quarantine]
Последовательность операций¶
-
Producer публикует committed domain event через outbox или иной доказанный механизм. Kafka key удерживает revisions сущности в одной partition, но consumer всё равно должен обрабатывать retries и возможный reorder вне этого contract.
-
Ingestion читает сообщения и валидирует schema/version. Malformed events должны попадать в quarantine с coordinates topic/partition/offset и причиной. Политика error handling зависит от engine settings; пример не включает молчаливый skip.
-
Kafka engine с MV преобразует insert blocks в betting_revisions. Commit offsets и table insert не образуют общей транзакции, поэтому delivery рассматривается at-least-once. Restart может повторить уже записанные события.
-
ReplacingMergeTree(event_version) сортирует по (tenant,event_id); partition immutable event_month не меняется при settlement correction. Раскладывать версии по времени получения нельзя: они перестанут сходиться при background merge.
-
Корректный запрос сначала выбирает argMax(tuple(status,amount,occurred_at),version) по key, затем фильтрует latest status и суммирует. Ранний filter status/time может выбрать старую версию; изменяемые поля фильтруются после latest selection.
-
Incremental sumState MV на raw revisions в этом correction pipeline не используется: она посчитает повторы и старые значения. Возможны query-time aggregation, periodic replacement snapshot либо специально спроектированный retract/upsert pipeline.
-
Completeness контролируется source offsets/watermark, invalid events и reconciliation с authoritative settled set. Kafka consumer lag сам по себе не доказывает полноту projection.
Failure modes и recovery¶
| Сбой | Поведение и восстановление |
|---|---|
| Insert завершён, offset commit нет | Повтор; latest-version read и stable identity сохраняют аналитический смысл. |
| Correction приходит раньше original | Version order выбирает correction; equal-version конфликт отправляется в расследование upstream. |
| Версия попала в другую partition | Background convergence нарушена; переиграть в исправленную generation/layout, сверить старую и новую. |
| Диск/merge backlog | Backpressure ingestion; сохранять Kafka retention, затем replay, не удалять offsets ради восстановления. |
| Потеря всех replicas | Restore compatible backup/metadata и replay оставшейся истории; без raw archive полнота может быть UNKNOWN. |
Trade-offs и эксплуатация¶
Batching снижает parts overhead, увеличивая freshness delay. Query-time argMax/FINAL стоит CPU; preaggregation требует корректного correction protocol. TTL revisions нельзя очищать так, чтобы поздняя старая версия воскресила сущность. Retention Kafka/raw должен покрывать rebuild, а source versions не должны переиспользоваться.
Код и границы проверки¶
clickhouse.sql отделяет correction-aware query от отдельной демонстрации AggregatingMergeTree с unique immutable input. Минимальный Kafka DDL ниже требует реальных brokers/topic и отдельного security configuration. ClickHouse/Kafka runtime отсутствует; синтаксический parser не проверяет engine/options/delivery.
CREATE TABLE kafka_bets (
tenant_id UInt64, event_id UUID, event_month Date,
occurred_at DateTime64(3,'UTC'), event_version UInt64,
status String, amount_minor Int64
) ENGINE=Kafka SETTINGS kafka_broker_list='broker:9092',
kafka_topic_list='bet-revisions', kafka_group_name='ch-bets-v1',
kafka_format='JSONEachRow';
CREATE MATERIALIZED VIEW ingest_bets TO betting_revisions AS
SELECT * FROM kafka_bets;
Типичные ошибки проектирования¶
Обещать exactly-once по одному компоненту; не хранить stable identity/version; путать ACK, visible state и восстановимость; не учитывать replay/retention horizon. Таблица отказов задаёт обязательные сценарии дальнейшего тестирования.
Вопросы на собеседовании¶
Где заканчивается транзакционная гарантия? На явно указанной local/engine boundary. Все последующие projections требуют собственного delivery/retry/version contract и контроля полноты.
Практические упражнения¶
В тестовом кластере остановите consumer между insert и offset commit; отправьте повтор и correction. Сравните raw sum, argMax и отдельную sumState MV. Установите, какие queries остаются корректными до merge. Сейчас этот integration/fault experiment не выполнен.
Дополнительные источники¶
Общий отчёт проверок (архив рабочего проекта). Ни статическая проверка, ни model test не подтверждают production reliability.