Files
clickstream-ch-kafka-supers…/generator
Dmitry Dementiev d8da27187e feat(generator): добавлен поток активных визитов по тикам
- Зачем:
  - генератор должен выпускать события визита по запланированным тикам, не приклеивая весь визит к одному запуску.
- Что:
  - добавлен тиковый слой `TickStreamGenerator` с активными визитами и выпуском созревших событий.
  - добавлена валидация потолка активных визитов относительно потолка популяции.
  - сохранён событийный смысл `event_budget` и добавлен регрессионный тест против разгона интенсивности.
- Проверка:
  - `uv run --with-requirements generator/requirements.txt pytest generator/tests -q` — 81 passed.
2026-06-11 15:32:53 +03:00
..

Генератор событий (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_events
    • location_events
    • device_events
    • geo_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 секций:

  1. Overview — ключевые метрики (events/min, tick duration, last success)
  2. Events by Topic — bar chart Events per Hour, rate by topic, total counters
  3. Errors — total errors, error rate, errors by topic
  4. Tick Statistics — duration distribution (p50/p95/p99), events per tick, hour factor
  5. Status — generator status, generator health (heartbeat), time since last tick
  6. 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/error
  • error_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()).

Как работает

  1. После каждого успешного тика состояние сохраняется в generator_state
  2. При старте генератор читает последнее состояние из топика
  3. Если состояние найдено - продолжает с сохранённого tick
  4. Если нет - начинает с 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