# Operations Runbook Операционный runbook для локального запуска и проверки пайплайна. ## Локальный запуск Базовые команды: - `make up` (поднимает базовый стенд без Superset init; пересобирает Airflow-образ) - `make down` (остановить и удалить контейнеры/сети проекта) - `make clean` (полная очистка: `down -v --remove-orphans`) - `make generated-history-analytics` (штатный чистый прогон: стартовая история генератора -> Kafka/STG -> ODS -> DDS -> DM -> Superset) - `CHECK_LIVE_SEAM=0 make generated-history-check` (повторяемая проверка ClickHouse и Superset после прогона только стартовой истории) - `make generated-history-runtime-check` (короткая проверка стыка backfill/live) - `make generated-history-chain-check` (проверка завершённых стыков между порциями истории) - `make ddl` (применяет SQL из `sql/ddl/00_databases.sql` и `sql/ddl/*/*.sql` в ClickHouse) - `make data` (архивный путь: заливает `data/*.jsonl` в Kafka; не основной источник аналитики) - `make transform` (запускает batch-процесс ODS -> DDS -> DM) - `make superset-init` (повторная инициализация Superset: подключение к ClickHouse, датасеты, дашборд) - `make test` (быстрый предкоммитный набор: тесты генератора, верхние контрактные тесты и проверка compose-конфигурации) - `make lint` (статические проверки: синтаксис Python и Bash, compose-конфиг, пробелы в diff) - `docker compose ps` - `docker compose logs -f --tail=200 ` - `docker compose down` (сохраняет named volumes, включая `clickhouse-data`) - `docker compose down -v` (удаляет named volumes, использовать осознанно) `make test` и `make lint` не заменяют стендовые проверки, которые управляют volumes или live-генератором. Для стыка backfill/live отдельно запускайте `make generated-history-runtime-check`. ## Порты Порты задаются в `docker-compose.yml`: - ClickHouse native: `localhost:8002` (пользователь `default`, пароль `123456`) - ClickHouse HTTP / play-консоль: `http://localhost:9123/play` (`default` / `123456`) - Kafka: `localhost:9092` - Kafka UI: `http://localhost:8082` - Airflow: `http://localhost:8080` (`admin/admin`) - Superset: `http://localhost:8088` (`admin/admin`) - Prometheus: `http://localhost:9090` - Grafana: `http://localhost:3000` (`admin/admin`) ## Airflow DAGs Штатный ручной путь начинается с `generator_control`: чистый стенд получает стартовую историю генератора, затем этот же DAG запускает ETL и проверку. `kafka_load` остаётся для экспериментов и совместимости учебного стенда. ### `generator_control` - Запуск: ручной (`Trigger DAG`). - Назначение: пульт стартовой истории генератора. - Операции: - `backfill` — создать стартовую историю, затем запустить `etl_pipeline` и дождаться `success`; - `import` — импортировать портативный артефакт, затем запустить `etl_pipeline` и дождаться `success`; - `next-day` — восстановить мир из state, добавить 24 модельных часа, затем запустить `etl_pipeline` и дождаться `success`; - `check` — сверить ClickHouse с manifest из Kafka. - Параметры: - `operation` (`backfill` / `import` / `next-day` / `check`); - `profile` — список берётся из `PROFILES` генератора; - `duration` — `6h`, `2d` и т.п.; пусто означает длительность профиля; - `seed`, `model_time_speed` — необязательные переопределения мира; - `artifact_path` — для `backfill` путь сохранения, для `import` путь чтения; - `expected_t_end` — необязательная ожидаемая граница перед `next-day`. При расхождении запуск показывает ожидаемое и фактическое значения. Backfill/import требуют чистый стенд: пустые data-топики Kafka и пустые `stg.*_raw`. При отказе очистите стенд через `make clean`. Операции `continue` в DAG нет: live-генератор — долгоживущий сервис, его запускают с консоли через `make generator-continue`. `next-day` работает на непустом стенде и не использует проверку чистоты. Перед записью пульт требует manifest, state ровно на его `T_end` и остановленный live-генератор. Настройки мира берутся из manifest; поля `profile`, `duration`, `seed` и `model_time_speed` формы для этой операции не применяются. Один запуск добавляет полуоткрытый диапазон `[T_end, T_end + 24h)` в UTC. Новая граница появляется в `boundaries`; старый manifest без поля читается как `[T0, T_end]`. Расписание остаётся выключенным (`schedule=None`), а `max_active_runs=1` не даёт двум доливкам выполняться параллельно. После двух доливок проверьте завершённые стыки: ```bash make generated-history-chain-check ``` Проверка проходит по внутренним границам `boundaries`, ищет непарные строки и смену browser/referer/utm внутри переходящих визитов. Она не меняет `make generated-history-runtime-check` для стыка backfill/live. Точка фиксации `next-day` — новый manifest. Порядок записи: data-топики, state, manifest. Автоматического отката нет. Если запуск упал до публикации manifest, не повторяйте доливку поверх возможного хвоста. Очистите стенд и переимпортируйте последний исправный портативный артефакт, затем повторите `next-day`. Текущая версия пересчитывает накопительные счётчики и контрольные суммы по всей доступной истории data-топиков Kafka. Поэтому время выполнения и расход памяти каждой доливки растут вместе с историей. Стандартный срок хранения Kafka тоже ограничивает долгую работу стенда. Для ручного учебного цикла запускайте соседние дни без долгих пауз. Перед включением расписания отдельная задача должна выбрать одно из решений: бессрочное хранение data-топиков или новый формат накопительного состояния manifest. Перед запуском `etl_pipeline` пульт проверяет, что DAG не стоит на паузе. Если стоит, задача падает сразу с подсказкой снять паузу в UI или командой: ```bash docker compose exec -T airflow-webserver airflow dags unpause etl_pipeline ``` После обновления репозитория выполните `make up`, чтобы Airflow получил новые зависимости и DAG-и через пересборку образа. Superset metadata собирается после готового DM: через `make generated-history-analytics` или `make superset-init`. ### `ddl_init` - Запуск: ручной (`Trigger DAG`) - Параметр: `verify_only` (`bool`, default `false`) - Назначение: создаёт БД и таблицы в ClickHouse от `00_databases` до `40_dm` ### `kafka_load` - Архивный путь, не основной источник аналитики. - Запуск: ручной (`Trigger DAG with config`) - Параметры: - `limit` (`int`, default `0`) — количество строк (`0` = все) - `reset_topics` (`bool`, default `true`) — пересоздать топики - Примеры: ```json {} ``` ```json {"limit": 100} ``` ### `etl_pipeline` - Запуск: ручной (`Trigger DAG with config`) - Параметры: - `full_refresh` (`bool`, default `true`) — очистить DDS перед загрузкой - `wait_stg_timeout_sec` (`int`, default `600`, minimum `30`) — сколько секунд задача `wait_for_stg_data` ждёт появления данных в STG, прежде чем упасть по таймауту - Зависимость: требует наличия данных в STG. В штатном сценарии STG наполняет `make generated-history-analytics` через backfill генератора. - Архивные `kafka_load` и `make data` оставлены только для ручных экспериментов и старых проверок. - Гейт целостности DDS: `check_dds_integrity` считает события без клика, а `assert_dds_integrity` роняет DAG при `orphan_events > 0`. Проверка идёт после `load_dds` и до `load_dm_summary`, чтобы DM не собирался поверх нарушенной связи `dds.event -> dds.click`. Для `assert_dds_integrity` задано `retries=0`: повтор не чинит уже собранную сироту и только задерживает явный failed-статус. ## Генератор событий (автономный стриминг) Автономный сервис для непрерывной генерации событий в Kafka. Работает независимо от Airflow DAGs. `make up` его не запускает: live включается только явной командой. ### Управление ```bash # Продолжить live-поток из state make generator-continue # Запустить генератор с текущими env напрямую make generator-up # Остановить генератор make generator-down # Перезапуск с пересборкой make generator-restart # Логи make generator-logs ``` ### Конфигурация (env) | Переменная | Описание | По умолчанию | |------------|----------|--------------| | `GEN_TICK_SECONDS` | Интервал между тиками | `5` | | `GEN_LAMBDA_BASE_PER_MIN` | Базовая интенсивность (событий/мин) | `30` | | `GEN_JITTER_PCT` | Процент вариативности | `20` | | `GEN_MIN_EVENTS_PER_TICK` | Минимальный событийный бюджет тика | `1` | | `GEN_MAX_EVENTS_PER_TICK` | Максимальный событийный бюджет тика | `50` | | `GEN_MAX_SESSION_EVENTS` | Потолок длины одного визита | `30` | | `GEN_MAX_ACTIVE_SESSIONS` | Потолок одновременных активных визитов | `200` | | `GEN_POPULATION_MAX` | Потолок активной популяции пользователей | `300` | | `GEN_P_NEW_USER` | Доля визитов новых пользователей | `0.15` | | `GEN_MIN_RETURN_MINUTES` | Минимальная пауза перед возвратом пользователя | `30` | | `GEN_MODEL_T0` | Стартовая модельная точка, ISO 8601 с часовым поясом | `2026-01-01T00:00:00+00:00` | | `GEN_MODEL_T_END` | Правая граница стартовой истории для `backfill` | пусто | | `GEN_MODEL_TIMEZONE` | Часовой пояс модельных часов для дневного коэффициента | `UTC` | | `GEN_MODEL_TIME_SPEED` | Сколько модельных секунд проходит за одну настенную секунду | `1` | | `GEN_RUN_MODE` | Режим: `live`, `backfill` или `next-day` | `live` | | `GEN_LAUNCH_PROFILE` | Имя профиля запуска для логов | `daily-wave` | | `GEN_STARTUP_HISTORY_ARTIFACT` | JSON-файл для экспорта стартовой истории в режиме `backfill` | пусто | | `GEN_STATE_ENABLED` | Сохранять state v3 между рестартами | `true` | | `GEN_STATE_RESET` | Сбросить state при старте | `false` | В `docker-compose.yml` через окружение переопределяются демо-параметры модели и режима, например: ```bash GEN_STATE_RESET=true GEN_LAMBDA_BASE_PER_MIN=60 docker compose up -d generator ``` Контейнерные `KAFKA_BOOTSTRAP_SERVERS` и `GEN_DATA_DIR` в compose оставлены внутренними значениями `kafka:29092` и `/data`. ### Стартовая история через backfill Штатная команда чистого прогона: ```bash make generated-history-analytics ``` Она выполняет полный сброс volumes, поднимает ClickHouse и Kafka, применяет DDL, выполняет глагол `backfill`, прогоняет batch STG -> ODS -> DDS -> DM, инициализирует Superset и запускает техническую проверку. Для координатора или CI короткая повторная проверка такого готового стенда: ```bash CHECK_LIVE_SEAM=0 make generated-history-check ``` По умолчанию команда требует live-строки после `T_end` и проверяет стык backfill/live. Чистый `make generated-history-analytics` отключает эту часть явно, потому что внутри него live-продолжение не запускается. Для ручной проверки только стартовой истории используйте: ```bash CHECK_LIVE_SEAM=0 make generated-history-check ``` Для проверки другого live-окна: ```bash CHECK_LIVE_SEAM=1 GEN_LIVE_CHECK_MINUTES=10 make generated-history-check ``` Для commit gate issue 17 есть короткий runtime-путь без полного `daily-wave` на 2 суток и без Superset UI: ```bash make generated-history-runtime-check ``` По умолчанию он берёт профиль `daily-wave`, но сжимает историю до `1h` и до 25 секунд ждёт в STG визит с browser-событиями по обе стороны границы. Затем он делает второй batch и проверяет стык с `CHECK_LIVE_SEAM=1`. При остановке generator получает SIGTERM и завершает текущий пакет. Параметр `stop_grace_period: 1m` даёт время дописать четыре топика, сбросить буферы и записать историю пакета до принудительной остановки контейнера. Если машина медленная, можно увеличить только ожидания: ```bash LIVE_SECONDS=45 WAIT_STG_SECONDS=10 make generated-history-runtime-check ``` По умолчанию команда использует учебный профиль `daily-wave`: 2 суток с суточной волной. В live-продолжении он идёт с ×60 и тикает раз в секунду, поэтому модельные сутки проходят примерно за 24 настенные минуты. Плоский профиль `ci` на 6 часов остаётся служебным для автоматических тестов. Разовую длительность можно задать без ручного расчёта `GEN_MODEL_T_END`: ```bash GEN_HISTORY_DURATION=2d make generated-history-analytics ``` `GEN_RUN_MODE=backfill` быстро проматывает модельное прошлое от `GEN_MODEL_T0` до `GEN_MODEL_T_END` без сна. В Kafka попадают события только за полуоткрытый отрезок `[T0, T_end)`. В compact-topic `generator_state` сохраняется state на `T_end`, а в `generator_startup_history_manifest` — manifest с настройками и контрольными числами. При live-запуске с теми же настройками генератор видит, что state совпадает с manifest, и стартует ровно с `T_end` без настенной дельты. Новый backfill записывает `boundaries=[T0, T_end]`. Каждый успешный `next-day` добавляет одну границу, сдвигает `model_t_end` и пересчитывает накопительные счётчики и контрольные суммы по всей истории. Если историю нужно сохранить в файл и восстановить на чистом стенде без новой генерации, используйте [runbook стартовой истории](./runbooks/startup-history.md). Для чистого повтора пересоздавайте volumes. Это сбрасывает ClickHouse, Kafka-топики данных, state и manifest генератора. `make generated-history-analytics` делает это по умолчанию (`CLEAN_START=1`). Ручной backfill без всего аналитического контура: ```bash make generator-backfill ``` Перед записью команда проверяет с хоста, что Kafka data-топики и STG пустые. Если там уже есть данные, она останавливается с подсказкой `make clean`. Старый способ через `GEN_RUN_MODE`, `GEN_STATE_RESET` и `GEN_MODEL_T_END` остаётся низкоуровневым путём для отладки прямого `docker compose run`. В обычной проверке используйте `make generated-history-analytics`, чтобы не забыть Superset и итоговый check. Manifest можно посмотреть так: ```bash docker compose exec -T kafka /opt/kafka/bin/kafka-console-consumer.sh \ --bootstrap-server kafka:29092 \ --topic generator_startup_history_manifest \ --from-beginning \ --property print.key=true \ --timeout-ms 5000 ``` Live-продолжение стартует с этого state. Используйте тот же профиль или ту же длительность, что были у backfill. Команда сама выставит `GEN_STATE_RESET=false`. Если state записан старой версией генератора, запуск теперь падает громко: сначала очистите стенд через `make clean` и пересоздайте историю. Команда `make generator-reset` подходит только для осознанного нового live-мира на чистом стенде: она тоже проверяет, что Kafka data-топики и STG пустые. ```bash make generator-continue sleep 5 make generator-down sleep 10 bash scripts/run_batch.sh ``` Базовая сверка формы данных после backfill: ```sql WITH toDateTime64('2026-01-01 00:00:00', 6) AS t0, toDateTime64('2026-01-01 06:00:00', 6) AS t_end SELECT uniqExact(user_domain_id) AS users, uniqExact(click_id) AS visits, count() AS events, users < visits AND visits < events AS pyramid_ok, min(event_ts) AS min_event_ts, max(event_ts) AS max_event_ts, min_event_ts >= t0 AND max_event_ts < t_end AS half_open_ok FROM dm.v_events_enriched WHERE event_ts >= t0 AND event_ts < t_end; ``` Повторяемость чистого прогона удобно сверять коротким digest по ключевым полям ClickHouse. Запускайте запрос после `bash scripts/run_batch.sh`; при одинаковых `GEN_SEED`, `T0`, `T_end` и настройках значение должно повторяться. ```bash docker compose exec -T clickhouse clickhouse-client \ --user=default \ --password=123456 \ --query " WITH toDateTime64('2026-01-01 00:00:00', 6) AS t0, toDateTime64('2026-01-01 06:00:00', 6) AS t_end SELECT hex(sipHash128(groupArray(tuple( event_id, click_id, user_domain_id, event_ts, page_url_path )))) AS digest FROM ( SELECT event_id, click_id, user_domain_id, event_ts, page_url_path FROM dm.v_events_enriched WHERE event_ts >= t0 AND event_ts < t_end ORDER BY event_id )" ``` Возвраты пользователей: ```sql WITH toDateTime64('2026-01-01 00:00:00', 6) AS t0, toDateTime64('2026-01-01 06:00:00', 6) AS t_end, users AS ( SELECT user_domain_id, uniqExact(click_id) AS visits FROM dm.v_events_enriched WHERE event_ts >= t0 AND event_ts < t_end AND user_domain_id IS NOT NULL GROUP BY user_domain_id ) SELECT count() AS users, countIf(visits > 1) AS returning_users, returning_users / users AS returning_share FROM users; ``` Форма длины визита: проверяем не только среднее, а долю коротких визитов, медиану и долю визитов, срезанных потолком `GEN_MAX_SESSION_EVENTS`. ```sql WITH toDateTime64('2026-01-01 00:00:00', 6) AS t0, toDateTime64('2026-01-01 06:00:00', 6) AS t_end, 30 AS max_session_events, sessions AS ( SELECT click_id, count() AS events_count, dateDiff('second', min(event_ts), max(event_ts)) AS duration_sec FROM dm.v_events_enriched WHERE event_ts >= t0 AND event_ts < t_end GROUP BY click_id ) SELECT count() AS visits, countIf(events_count <= 2) / visits AS short_visit_share, quantileExact(0.5)(events_count) AS median_events_per_visit, avg(events_count) AS avg_events_per_visit, countIf(events_count = max_session_events) / visits AS capped_visit_share, quantileExact(0.5)(duration_sec) AS median_duration_sec FROM sessions; ``` Воронка должна монотонно убывать, а доля дошедших до `/confirmation` должна быть в согласованном коридоре для текущих настроек генератора. Для review gate `confirmation_share` сравнивается по калибровочной форме «страница была в визите». Строгий SQL ниже проверяет отдельное свойство: упорядоченный путь `/home -> товары -> /cart -> /payment -> /confirmation`. ```sql WITH toDateTime64('2026-01-01 00:00:00', 6) AS t0, toDateTime64('2026-01-01 06:00:00', 6) AS t_end, sessions AS ( SELECT click_id, minIf(event_ts, page_url_path = '/home') AS home_ts, minIf(event_ts, page_url_path IN ('/product_a', '/product_b')) AS product_ts, minIf(event_ts, page_url_path = '/cart') AS cart_ts, minIf(event_ts, page_url_path = '/payment') AS payment_ts, minIf(event_ts, page_url_path = '/confirmation') AS confirmation_ts FROM dm.v_events_enriched WHERE event_ts >= t0 AND event_ts < t_end GROUP BY click_id ) SELECT countIf(home_ts IS NOT NULL) AS home, countIf(home_ts IS NOT NULL AND product_ts > home_ts) AS products, countIf(home_ts IS NOT NULL AND product_ts > home_ts AND cart_ts > product_ts) AS cart, countIf(home_ts IS NOT NULL AND product_ts > home_ts AND cart_ts > product_ts AND payment_ts > cart_ts) AS payment, countIf(home_ts IS NOT NULL AND product_ts > home_ts AND cart_ts > product_ts AND payment_ts > cart_ts AND confirmation_ts > payment_ts) AS confirmation, products <= home AND cart <= products AND payment <= cart AND confirmation <= payment AS monotonic_ok, confirmation / home AS confirmation_share FROM sessions; ``` Калибровочная форма воронки проверяет, что страница была в визите, без строгого порядка событий. Именно эту форму используем для сравнения `confirmation_share` в review gate. ```sql WITH toDateTime64('2026-01-01 00:00:00', 6) AS t0, toDateTime64('2026-01-01 06:00:00', 6) AS t_end, sessions AS ( SELECT click_id, countIf(page_url_path = '/home') > 0 AS has_home, countIf(page_url_path IN ('/product_a', '/product_b')) > 0 AS has_product, countIf(page_url_path = '/cart') > 0 AS has_cart, countIf(page_url_path = '/payment') > 0 AS has_payment, countIf(page_url_path = '/confirmation') > 0 AS has_confirmation FROM dm.v_events_enriched WHERE event_ts >= t0 AND event_ts < t_end GROUP BY click_id ) SELECT countIf(has_home) AS home, countIf(has_home AND has_product) AS products, countIf(has_home AND has_product AND has_cart) AS cart, countIf(has_home AND has_product AND has_cart AND has_payment) AS payment, countIf(has_home AND has_product AND has_cart AND has_payment AND has_confirmation) AS confirmation, products <= home AND cart <= products AND payment <= cart AND confirmation <= payment AS monotonic_ok, confirmation / home AS confirmation_share FROM sessions; ``` Стык backfill + live проверяется после короткого live-продолжения: ```sql WITH toDateTime64('2026-01-01 00:00:00', 6) AS t0, toDateTime64('2026-01-01 06:00:00', 6) AS t_end, toDateTime64('2026-01-01 06:10:00', 6) AS t_live_end SELECT count() AS events, uniqExact(event_id) AS unique_events, events - unique_events AS duplicate_events, countIf(event_ts = t_end) AS boundary_events, min(event_ts) AS min_event_ts, max(event_ts) AS max_event_ts FROM dm.v_events_enriched WHERE event_ts >= t0 AND event_ts < t_live_end; ``` Однородность визитов, переходящих через `T_end`: ```sql WITH toDateTime64('2026-01-01 06:00:00', 6) AS t_end, crossing AS ( SELECT click_id, min(event_ts) AS first_ts, max(event_ts) AS last_ts, groupUniqArray(user_domain_id) AS users, groupUniqArray(device_type) AS devices, groupUniqArray(os_name) AS os_names, groupUniqArray(geo_country) AS countries FROM dm.v_events_enriched WHERE event_ts >= t_end - INTERVAL 30 MINUTE AND event_ts < t_end + INTERVAL 30 MINUTE GROUP BY click_id HAVING first_ts < t_end AND last_ts >= t_end ) SELECT count() AS crossing_visits, countIf( length(users) = 1 AND length(devices) = 1 AND length(os_names) = 1 AND length(countries) = 1 ) AS homogeneous_visits, crossing_visits = homogeneous_visits AS context_ok FROM crossing; ``` ### Проверка модельного времени в ClickHouse Для повторяемой проверки используйте чистый стенд и явный сброс состояния генератора. `event_timestamp` в событиях — модельное время от `GEN_MODEL_T0`, а не настенное время запуска процесса. При `GEN_MODEL_TIME_SPEED=K` один тик покрывает `GEN_TICK_SECONDS * K` модельных секунд, и событийный бюджет считается по этой модельной длительности. ```bash make clean docker compose up -d clickhouse kafka make ddl GEN_SEED=4242 \ GEN_MODEL_T0=2026-01-01T10:00:00+00:00 \ GEN_MODEL_TIMEZONE=UTC \ GEN_STATE_RESET=true \ GEN_TICK_SECONDS=60 \ GEN_LAMBDA_BASE_PER_MIN=60 \ GEN_JITTER_PCT=0 \ docker compose up -d --build generator # Убедитесь по логам, что Tick 1 завершился, а Tick 2 ещё не стартовал. sleep 9 docker compose logs --tail=40 generator docker compose stop generator sleep 5 bash scripts/run_batch.sh docker compose exec -T clickhouse clickhouse-client --user=default --password=123456 --query " SELECT count() AS events, min(event_ts) AS min_event_ts, max(event_ts) AS max_event_ts, uniqExact(event_id) AS unique_events, uniqExact(click_id) AS unique_clicks FROM ods.browser_event FORMAT Vertical" ``` Повторите блок с теми же значениями. При чистом стенде и `GEN_STATE_RESET=true` контрольные числа должны совпасть, а `min_event_ts` должен начинаться от `2026-01-01 10:00:00`. ### Топик истории Генератор пишет историю батчей в топик `generator_batch_history` (JSON, ключ `batch_id`). **Важно:** генератор требует работающей Kafka. Без Kafka генератор упадёт при старте или потеряет события. ```bash # Чтение истории из Kafka docker compose exec kafka /opt/kafka/bin/kafka-console-consumer.sh \ --bootstrap-server kafka:29092 \ --topic generator_batch_history \ --from-beginning ``` ### Метрики Prometheus метрики доступны на `http://localhost:9109/metrics`: - `generator_events_total` — счётчик отправленных событий - `generator_publish_errors_total` — ошибки публикации - `generator_tick_duration_seconds` — длительность тика - `generator_last_success_timestamp` — время последнего успешного или частично успешного тика ### Мониторинг через Grafana **Dashboard URL:** `http://localhost:3000/d/generator-overview` Дашборд "Generator Overview" автоматически загружается при старте Grafana и содержит: | Раздел | Панели | Описание | |--------|--------|----------| | **Overview** | Events/min | Скорость генерации событий в минуту | | | Tick Duration | Медиана и p99 длительности тика | | | Last Successful Tick | Время последнего успешного тика | | **Events by Topic** | Events per Hour (24h bar chart) | Распределение событий по часам и топикам | | | Events Rate by Topic | График по 4 топикам (browser, location, device, geo) | | | Total Events by Topic | Суммарные счётчики по каждому топику | | **Errors** | Total Errors | Общее число ошибок публикации | | | Error Rate | Скорость ошибок (err/min) | | | Errors by Topic | Ошибки разбиты по топикам | | **Tick Statistics** | Tick Duration Distribution | p50, p95, p99 длительности тиков | | | Events per Tick | Среднее число событий на тик | | | Hour Factor | Текущий временной множитель (0.7/1.0/1.2) | | **Status** | Generator Status | Статус работы (enabled/disabled) | | | Generator Health | Статус активности (heartbeat last tick), не проверяет state save | | | Time Since Last Tick | Время с последнего тика | | **Info** | Полезные команды и параметры конфигурации | #### Troubleshooting генератора **Нет данных на дашборде:** 1. Проверить, что генератор запущен: `docker compose ps generator` 2. Проверить метрики напрямую: `curl http://localhost:9109/metrics` 3. Проверить target в Prometheus: `http://localhost:9090/targets` (job: generator) **Высокий error rate:** - Проверить доступность Kafka: `docker compose ps kafka` - Смотреть логи: `make generator-logs` - Проверить consumer lag: дашборд Kafka Overview **Длительные тики (p99 > 1s):** - Проверить CPU/ресурсы контейнера - Возможно, высокая нагрузка на Kafka — проверить дашборд Kafka **Dashboard не загрузился:** ```bash # Перезагрузить provisioning Grafana curl -s -u admin:admin -X POST http://localhost:3000/api/admin/provisioning/dashboards/reload # Или пересоздать контейнер docker compose restart grafana ``` ## Рекомендуемый сценарий ```bash # Полный чистый путь: генерация -> STG -> ODS -> DDS -> DM -> Superset make generated-history-analytics # Повторная проверка данных стартовой истории без пересоздания CHECK_LIVE_SEAM=0 make generated-history-check # Проверка стыка backfill/live make generated-history-runtime-check ``` Обычный `make generated-history-check` корректен только после `make generator-continue` и повторного `make transform`, когда в витринах уже есть строки после `T_end`. ## Быстрые проверки - Kafka ingest: наличие данных генератора в `stg.*` и типизированных строк в `ods.*`. - Airflow UI: `http://localhost:8080` показывает DAG `ddl_init`, `generator_control`, `kafka_load`, `etl_pipeline`; основной ручной пульт генератора — `generator_control`. - BI: витрина `dm.v_events_enriched` отвечает за разумное время при фильтре по дате. --- ## Мониторинг ### TL;DR после `git pull` ```bash # Быстрый вариант (make) make reload-monitoring # Если мониторинг "залип" (No data/out of bounds) — жесткое восстановление: make recover-monitoring # Или вручную: docker compose up -d prometheus grafana kafka-exporter statsd-exporter docker compose restart prometheus statsd-exporter curl -s -u admin:admin -X POST http://localhost:3000/api/admin/provisioning/datasources/reload curl -s -u admin:admin -X POST http://localhost:3000/api/admin/provisioning/dashboards/reload curl -s -u admin:admin -X POST http://localhost:3000/api/admin/provisioning/alerting/reload ``` Если менялся `configs/prometheus_ch.xml`: `docker compose restart clickhouse`. ### Prometheus + Grafana для ClickHouse, Kafka и Airflow Стек мониторинга поднимается вместе с остальной инфраструктурой: ```bash # Проверить статус сервисов мониторинга docker compose ps prometheus grafana # Проверить скрейп ClickHouse в Prometheus curl -s http://localhost:9090/api/v1/targets | grep -o '"health":"[^"]*"' ``` ### Конфигурация - **ClickHouse**: встроенный Prometheus endpoint (`/metrics` на порту `9126`) - **Kafka**: через `kafka-exporter` (порт `9308`) - **Airflow**: через `statsd-exporter` (StatsD → Prometheus, порт `9102`) - Airflow отправляет метрики в StatsD-формате на `statsd-exporter:8125` - Mapping конфигурация: `configs/statsd_mapping.yml` - **Grafana provisioning** (`configs/grafana/provisioning/`): - Дашборды: ClickHouse Overview, Kafka Overview, Airflow Overview, Generator Overview - Алерты: ClickHouse, Kafka, Airflow ### После `git pull`: быстрый апдейт мониторинга Если прилетели изменения в `configs/grafana/provisioning/*` или `configs/prometheus.yml`, примените их так: ```bash # Рекомендуемый способ (через make) make reload-monitoring # Если метрики пропали/залипли: make recover-monitoring # Или вручную: docker compose rm -sf prometheus statsd-exporter docker compose up -d prometheus grafana kafka-exporter statsd-exporter docker compose restart airflow-scheduler airflow-webserver curl -s -u admin:admin -X POST http://localhost:3000/api/admin/provisioning/datasources/reload curl -s -u admin:admin -X POST http://localhost:3000/api/admin/provisioning/dashboards/reload curl -s -u admin:admin -X POST http://localhost:3000/api/admin/provisioning/alerting/reload ``` Проверка результата: ```bash # Дашборды и алерты curl -s -u admin:admin http://localhost:3000/api/v1/provisioning/alert-rules | grep -o '"title":"[^"]*"' # Kafka метрики curl -s http://localhost:9090/api/v1/targets | grep kafka curl -s http://localhost:9308/metrics | grep "^kafka_brokers" ``` Если в пулле изменился `configs/prometheus_ch.xml`, дополнительно перезапустите ClickHouse: ```bash docker compose restart clickhouse ``` Если дашборд Kafka не загрузился (ошибка "Dashboard title cannot be empty" в логах), пересоздайте контейнер Grafana: ```bash docker compose stop grafana && docker compose rm -f grafana && docker compose up -d grafana ``` ### Дашборд ClickHouse Overview URL: `http://localhost:3000/d/clickhouse-overview/clickhouse-overview` | Раздел | Метрики | |--------|---------| | System Health | CPU Usage, Memory Resident, Memory Code | | Query Performance | Queries per Second, Active Queries, Failed Queries (total), Total Queries, Inserted Rows/sec | | MergeTree Storage | Total Parts, Parts by State, Total Merges, Merges per Second | Принятое решение по метрикам: сверили naming через Context7 (`/clickhouse/clickhouse-docs`, раздел Prometheus interface) и заменили недоступные в `25.1` серии на фактически экспортируемые (`ClickHouseProfileEvents_InsertedRows`, `ClickHouseAsyncMetrics_TotalPartsOfMergeTreeTables`, `ClickHouseMetrics_Parts*`). ### Дашборд Kafka Overview URL: `http://localhost:3000/d/kafka-overview/kafka-overview` | Раздел | Метрики | |--------|---------| | Cluster Health | Brokers Up, Topics, Total Partitions, Consumer Groups | | Throughput | Messages In / sec by Topic | | Consumers | Consumer Lag by Group | | Partitions | Partition Offsets (Current) | **Источник метрик:** `kafka-exporter` (danielqsj/kafka-exporter), формат конфигурации подтверждён через Context7 (`/danielqsj/kafka_exporter`, `/prometheus/docs`). ### Проверка метрик ```bash # Prometheus собирает метрики ClickHouse curl -s "http://localhost:9090/api/v1/query?query=ClickHouseAsyncMetrics_MemoryResident" curl -s "http://localhost:9090/api/v1/query?query=ClickHouseProfileEvents_Query" # Prometheus собирает метрики Kafka curl -s "http://localhost:9090/api/v1/query?query=kafka_brokers" curl -s "http://localhost:9090/api/v1/query?query=kafka_consumergroup_lag" # Прямая проверка kafka-exporter curl -s http://localhost:9308/metrics | grep "^kafka_" ``` ```bash # Проверить, что Prometheus собирает метрики curl -s "http://localhost:9090/api/v1/query?query=ClickHouseAsyncMetrics_MemoryResident" # Проверить счётчик запросов curl -s "http://localhost:9090/api/v1/query?query=ClickHouseProfileEvents_Query" ``` ### Алерты Grafana **ClickHouse Alerts** — provisioning-файл: `configs/grafana/provisioning/alerting/clickhouse-alert-rules.yml` Настроены правила: - `ClickHouse Failed Queries Rate` — `rate(ClickHouseProfileEvents_FailedQuery[5m]) > 0` в течение `2m` - `ClickHouse Memory Resident High` — `MemoryResident / OSMemoryTotal * 100 > 85` в течение `5m` - `ClickHouse Parts Active High` — `ClickHouseMetrics_PartsActive > 500` в течение `10m` **Kafka Alerts** — provisioning-файл: `configs/grafana/provisioning/alerting/kafka-alert-rules.yml` Настроены правила: - `Kafka Broker Down` — `kafka_brokers < 1` в течение `1m` - `Kafka Consumer Lag High` — `kafka_consumergroup_lag > 10000` в течение `5m` - `Kafka No Messages Produced` — `rate(kafka_topic_partition_current_offset[5m]) < 0.1` в течение `10m` - `Kafka Consumer Group Missing` не включён: для демо-стенда даёт шум на стартовых прогонах и не повышает диагностику по сравнению с lag/throughput. Проверка и reload без рестарта контейнера: ```bash # Список правил unified alerting curl -s -u admin:admin http://localhost:3000/api/v1/provisioning/alert-rules # Принудительно перечитать provisioning alerting curl -s -X POST -u admin:admin http://localhost:3000/api/admin/provisioning/alerting/reload ``` ### Troubleshooting мониторинга **Общие проблемы:** - **"No data" в Grafana**: проверить, что Prometheus видит target (`Status -> Targets` в UI) - **Dashboard не загрузился**: проверить логи Grafana — provisioning работает при первом старте контейнера - **Prometheus spam `out of bounds` и дашборды пустые**: выполнить `make recover-monitoring` **ClickHouse:** - **Метрики не обновляются**: ClickHouse экспортирует метрики на `0.0.0.0:9126` внутри сети Docker **Kafka:** - **`connection refused` к Kafka**: проверить, что kafka-exporter использует `kafka:29092` (внутренняя сеть), не `localhost:9092` - **Метрики Kafka не появляются**: проверить, что kafka-exporter подключился к Kafka — `docker compose logs kafka-exporter` - **Нет консьюмер-групп**: kafka-exporter показывает lag только при наличии активных консьюмеров с закоммиченными offset ## Troubleshooting - `etl_pipeline` падает с ошибкой схемы: сначала запустить `ddl_init`. - `git pull` падает с `Permission denied` на `data/*` или `configs/grafana/provisioning/*`: - Причина: локально есть файлы/каталоги не вашего пользователя (часто после запуска контейнеров с root-пользователем). - Диагностика: ```bash ls -ld data configs/grafana/provisioning ls -l data | head -n 20 ``` - Быстрое восстановление: ```bash # Владелец и права для рабочей копии репозитория sudo chown -R "$USER:$USER" . find . -type d -exec chmod u+rwx {} \; find . -type f -exec chmod u+rw {} \; ``` - После восстановления повторить `git pull --ff-only`. - Не запускать `git` через `sudo`. - `grafana` перезапускается с ошибкой `attempt to write a readonly database`: - Причина: старый `grafana_lib` содержит `grafana.db`, созданный root-пользователем. - Простой recovery (сбросить только volume Grafana): ```bash docker compose stop grafana docker volume ls | grep grafana_lib docker volume rm _grafana_lib docker compose up -d grafana ``` - В Superset ошибки `DB engine Error` и `Cannot load filter`, а в логах есть `Can't load plugin: sqlalchemy.dialects:clickhouse.connect`: - Причина: некорректный URI диалекта ClickHouse (`clickhouse+connect://...`). - Используйте URI `clickhousedb://...` и пересоберите сервисы Superset: ```bash docker compose build superset superset-init docker compose up -d clickhouse docker compose up -d --force-recreate superset-init superset ``` - После `docker compose down -v` нужно повторно прогнать `make generated-history-analytics`. - После `make clean`/`down -v` Superset стартует, но витрины `dm.*` ещё пустые или отсутствуют до прогона стартовой истории; используйте `make generated-history-analytics`. - Архивную загрузку `make data` использовать только для ручных экспериментов.