- Зачем: - генератор должен выпускать события визита по запланированным тикам, не приклеивая весь визит к одному запуску. - Что: - добавлен тиковый слой `TickStreamGenerator` с активными визитами и выпуском созревших событий. - добавлена валидация потолка активных визитов относительно потолка популяции. - сохранён событийный смысл `event_budget` и добавлен регрессионный тест против разгона интенсивности. - Проверка: - `uv run --with-requirements generator/requirements.txt pytest generator/tests -q` — 81 passed.
14 KiB
Генератор событий (MVP rev5)
Перед использованием как источник витрин прочитать KNOWN_ISSUES.md: часть старого дефекта уже исправлена (один
click_idна визит, путь по страницам, монотонное время, активные визиты между тиками), но популяция возвращающихся пользователей и восстановление активных визитов после рестарта ещё остаются следующими шагами.
Автономный генератор событий для Kafka с режимом steady-stream.
Архитектура
generator-service -> Kafka topics -> (потребители отдельно)
Генератор работает автономно и не зависит от потребителей (Airflow, ClickHouse).
Структура кода
Код разнесён в пакет src/clickstream_generator/. Сам generator.py остаётся
точкой входа и совместимым фасадом для старых импортов из тестов.
| Файл | Назначение |
|---|---|
src/clickstream_generator/config.py |
переменные окружения и валидация настроек |
src/clickstream_generator/dictionary.py |
загрузка и индексы исходных JSONL |
src/clickstream_generator/generation.py |
генерация одного связанного визита |
src/clickstream_generator/intensity.py |
расчёт событийного бюджета тика |
src/clickstream_generator/runtime.py |
тиковый слой: активные визиты и выпуск созревших событий |
src/clickstream_generator/kafka_io.py |
Kafka publisher, история batch, Kafka-state и служебные топики |
src/clickstream_generator/state.py |
сериализуемое состояние генератора |
src/clickstream_generator/metrics.py |
Prometheus-метрики |
src/clickstream_generator/service.py |
основной цикл сервиса |
generator.py |
запуск сервиса и совместимый фасад |
Режим работы: steady-stream
- Публикуем постепенно, короткими тиками (по умолчанию каждые 5 секунд)
- На каждом тике отправляем небольшую порцию сообщений
- Держим целевую интенсивность
events/minбез крупных минутных batch: рассчитанный событийный бюджет тика планирует новые визиты и списывается по фактической длине этих визитов, а события выходят позже по своим запланированным меткам времени - Распределяем события по 4 топикам:
browser_eventslocation_eventsdevice_eventsgeo_events
- Публичный вызов генеративного ядра строит один визит: общий
click_id, разныеevent_id, общий device/geo-контекст, путь по страницам воронки и строго растущие запланированныеevent_timestamp. - Тиковый слой хранит активные визиты между вызовами и выпускает только события,
у которых наступил
event_timestamp; завершённые визиты удаляются из памяти. - Сохраняем связи
event_id <-> location,click_id <-> device/geo.
Конфигурация (env)
| Переменная | Описание | По умолчанию |
|---|---|---|
KAFKA_BOOTSTRAP_SERVERS |
Адрес Kafka | kafka:29092 |
GEN_TICK_SECONDS |
Интервал между тиками | 5 (1-10 сек рекомендуется) |
GEN_LAMBDA_BASE_PER_MIN |
Базовая интенсивность (событий/мин) | 200 |
GEN_JITTER_PCT |
Процент вариативности | 20 |
GEN_MIN_EVENTS_PER_TICK |
Минимум событий за тик | 5 |
GEN_MAX_EVENTS_PER_TICK |
Максимум событий за тик | 50 |
GEN_MAX_SESSION_EVENTS |
Потолок длины одного визита, защита от петель | 30 |
GEN_MAX_ACTIVE_SESSIONS |
Потолок одновременных активных визитов | 200 |
GEN_POPULATION_MAX |
Будущий потолок популяции пользователей; сейчас нужен для валидации активных визитов | 300 |
GEN_DATA_DIR |
Путь к JSONL файлам | /data |
GEN_SEED |
Сид для воспроизводимости | — |
GEN_ENABLED |
Включить генерацию | true |
GEN_METRICS_PORT |
Порт для Prometheus | 9109 |
GEN_STATE_ENABLED |
Сохранять состояние между рестартами | true |
GEN_STATE_RESET |
Сбросить состояние при старте | false |
Режим "раз в минуту" (для демо)
Для контролируемых демо можно установить:
GEN_TICK_SECONDS=60
GEN_MIN_EVENTS_PER_TICK=50
GEN_MAX_EVENTS_PER_TICK=500
Управление через Makefile
# Запустить только генератор
make generator-up
# Остановить генератор
make generator-down
# Смотреть логи
make generator-logs
# Перезапуск с пересборкой
make generator-restart
# Запуск тестов
make generator-test
Метрики Prometheus
Генератор экспортирует метрики на :9109/metrics:
| Метрика | Тип | Описание |
|---|---|---|
generator_events_total |
Counter | Всего отправлено событий (по топикам) |
generator_publish_errors_total |
Counter | Ошибки публикации (по топикам) |
generator_tick_duration_seconds |
Histogram | Длительность тика |
generator_last_success_timestamp |
Gauge | Время последнего успешного тика |
Проверка метрик
curl http://localhost:9109/metrics
curl http://localhost:9090/api/v1/targets | grep generator
Мониторинг в Grafana
Dashboard URL: http://localhost:3000/d/generator-overview
Дашборд "Generator Overview" предоставляет полную визуализацию работы генератора:
Ключевые панели
| Панель | Метрика | Описание |
|---|---|---|
| Events/min | rate(generator_events_total[1m]) * 60 |
Текущая скорость генерации |
| Tick Duration | generator_tick_duration_seconds |
p50 и p99 длительности тика |
| Last Successful Tick | generator_last_success_timestamp |
Время последнего успешного тика |
| Events per Hour | increase(generator_events_total[1h]) |
24-часовое распределение по топикам (bar chart) |
| Errors | generator_publish_errors_total |
Общее число и rate ошибок |
| Generator Status | derived | Активен ли генератор |
Структура дашборда
Дашборд разделён на 6 секций:
- Overview — ключевые метрики (events/min, tick duration, last success)
- Events by Topic — bar chart Events per Hour, rate by topic, total counters
- Errors — total errors, error rate, errors by topic
- Tick Statistics — duration distribution (p50/p95/p99), events per tick, hour factor
- Status — generator status, generator health (heartbeat), time since last tick
- Info — полезные команды и параметры конфигурации
Доступ к дашборду
Дашборд автоматически загружается в Grafana при старте контейнера (provisioning).
# Открыть дашборд
open http://localhost:3000/d/generator-overview
# Перезагрузить provisioning (если дашборд не появился)
curl -s -u admin:admin -X POST http://localhost:3000/api/admin/provisioning/dashboards/reload
История batch
История пишется в Kafka-топик generator_batch_history (JSON).
Контракт топика:
- Название фиксировано:
generator_batch_history(не конфигурируется) - Формат: JSON с ключом
batch_id
Важно: генератор требует работающей Kafka. Без Kafka генератор упадёт при старте или потеряет события. Для мониторинга доступности используйте Prometheus-метрики (generator_last_success_timestamp).
Поля сообщения:
batch_id— идентификатор батчаstarted_at/finished_at— время начала/окончания (ISO format)sent_total— всего отправленоsent_browser/location/device/geo— по топикамstatus— success/partial/errorerror_message— описание ошибки (если есть)
Чтение истории из Kafka
docker compose exec kafka /opt/kafka/bin/kafka-console-consumer.sh \
--bootstrap-server kafka:29092 \
--topic generator_batch_history \
--from-beginning
State Recovery (восстановление состояния)
Генератор сохраняет своё состояние между перезапусками в Kafka-топик generator_state (compact topic). Это позволяет:
- Продолжить нумерацию тиков с места остановки (continuity)
- Сохранить последовательность случайных чисел (RNG state)
- Восстановить интенсивность генерации после рестарта
Важно: восстанавливается continuity по номеру тика и интенсивности, но не гарантируется отсутствие дублирования событий — event_id и click_id всегда генерируются заново (uuid4()).
Как работает
- После каждого успешного тика состояние сохраняется в
generator_state - При старте генератор читает последнее состояние из топика
- Если состояние найдено - продолжает с сохранённого tick
- Если нет - начинает с tick=1
Топик generator_state
- Название: фиксировано
generator_state - Тип: compact topic (хранится только последнее значение для каждого ключа)
- Ключ:
default(для возможности нескольких генераторов в будущем) - Конфигурация:
cleanup.policy=compact, минимальный retention
Просмотр текущего состояния
docker compose exec kafka /opt/kafka/bin/kafka-console-consumer.sh \
--bootstrap-server kafka:29092 \
--topic generator_state \
--from-beginning \
--property print.key=true
Сброс состояния (начать сначала)
# Вариант 1: через env (рекомендуется)
GEN_STATE_RESET=true docker compose up -d generator
# Вариант 2: удалить топик полностью
docker compose exec kafka /opt/kafka/bin/kafka-topics.sh \
--bootstrap-server kafka:29092 \
--delete \
--topic generator_state
Отключение сохранения состояния
GEN_STATE_ENABLED=false docker compose up -d generator
При отключенном state management генератор всегда начинает с tick=1, RNG инициализируется с GEN_SEED (или случайно).
Тестирование
Тесты написаны на pytest.
Запуск тестов
# Через Makefile (рекомендуется)
make generator-test
# Вручную через Docker
docker build -t generator:test .
docker run --rm -v $(PWD):/workspace -w /workspace/generator generator:test pytest tests/ -v
# Конкретный файл тестов
docker run --rm -v $(PWD):/workspace -w /workspace/generator generator:test pytest tests/test_generation.py -v
Структура тестов
generator/tests/
├── conftest.py # Fixtures pytest
├── test_config.py # Тесты конфигурации
├── test_generation.py # Тесты генерации событий
├── test_history.py # Тесты структуры BatchRecord
├── test_kafka_history.py # Тесты KafkaBatchHistory
├── test_service.py # Тесты GeneratorService
├── test_service_cleanup.py # Контракт разбиения сервиса на модули
└── test_state.py # Тесты GeneratorState и KafkaStateManager
Интеграционный тест
# Запустить стек с генератором
make generator-up
# Проверить логи
make generator-logs
# Проверить метрики
curl http://localhost:9109/metrics
# Проверить сообщения в Kafka
docker compose exec kafka /opt/kafka/bin/kafka-console-consumer.sh \
--bootstrap-server kafka:29092 --topic browser_events --from-beginning