From 39df4f2469879dd0defe4ca7375c10bfbab5f0cf Mon Sep 17 00:00:00 2001 From: Dmitry Dementev Date: Sun, 8 Feb 2026 23:00:32 +0300 Subject: [PATCH 1/5] fix(monitoring): add missing ClickHouse Prometheus port and fix dashboard queries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add port 9126 mapping for ClickHouse Prometheus metrics endpoint (was configured in prometheus_ch.xml but not exposed in docker-compose.yml) - Fix CPU Usage panel: use delta() instead of rate() for gauge metric ClickHouseProfileEvents_OSCPUVirtualTimeMicroseconds is a gauge, not counter - Add explicit datasource blocks to dashboard queries for consistency ClickHouse ProfileEvents metrics correctly use rate() — they are counters. Warning about missing _total suffix is expected (ClickHouse naming convention). --- .../provisioning/dashboards/clickhouse-overview.json | 12 ++++++++++++ docker-compose.yml | 1 + 2 files changed, 13 insertions(+) diff --git a/configs/grafana/provisioning/dashboards/clickhouse-overview.json b/configs/grafana/provisioning/dashboards/clickhouse-overview.json index bc863dc..1a01e64 100644 --- a/configs/grafana/provisioning/dashboards/clickhouse-overview.json +++ b/configs/grafana/provisioning/dashboards/clickhouse-overview.json @@ -385,6 +385,10 @@ }, "targets": [ { + "datasource": { + "type": "prometheus", + "uid": "prometheus_uid" + }, "expr": "rate(ClickHouseProfileEvents_Query[1m])", "refId": "A" } @@ -671,6 +675,10 @@ }, "targets": [ { + "datasource": { + "type": "prometheus", + "uid": "prometheus_uid" + }, "expr": "rate(ClickHouseProfileEvents_InsertedRows[1m])", "refId": "A" } @@ -967,6 +975,10 @@ }, "targets": [ { + "datasource": { + "type": "prometheus", + "uid": "prometheus_uid" + }, "expr": "rate(ClickHouseProfileEvents_Merge[1m])", "refId": "A" } diff --git a/docker-compose.yml b/docker-compose.yml index 12d07be..f4dd504 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -35,6 +35,7 @@ services: ports: - "8002:9000" - "9123:8123" + - "9126:9126" # ClickHouse Prometheus metrics endpoint kafka: image: apache/kafka:3.8.0 From df173c00f7f434d2be77b7b62f8d5f7b15b351cd Mon Sep 17 00:00:00 2001 From: Dmitry Dementev Date: Sun, 8 Feb 2026 23:19:54 +0300 Subject: [PATCH 2/5] fix(monitoring): revert incorrect clickhouse dashboard query edits --- .../dashboards/clickhouse-overview.json | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/configs/grafana/provisioning/dashboards/clickhouse-overview.json b/configs/grafana/provisioning/dashboards/clickhouse-overview.json index 1a01e64..454acd4 100644 --- a/configs/grafana/provisioning/dashboards/clickhouse-overview.json +++ b/configs/grafana/provisioning/dashboards/clickhouse-overview.json @@ -116,11 +116,7 @@ }, "targets": [ { - "datasource": { - "type": "prometheus", - "uid": "prometheus_uid" - }, - "expr": "delta(ClickHouseProfileEvents_OSCPUVirtualTimeMicroseconds[1m]) / 60 / 1000000 / scalar(count({__name__=~\"ClickHouseAsyncMetrics_CPUFrequencyMHz_.*\"})) * 100", + "expr": "rate(ClickHouseProfileEvents_OSCPUVirtualTimeMicroseconds[1m]) / 1000000 / scalar(count({__name__=~\"ClickHouseAsyncMetrics_CPUFrequencyMHz_.*\"})) * 100", "refId": "A" } ], @@ -385,10 +381,6 @@ }, "targets": [ { - "datasource": { - "type": "prometheus", - "uid": "prometheus_uid" - }, "expr": "rate(ClickHouseProfileEvents_Query[1m])", "refId": "A" } @@ -675,10 +667,6 @@ }, "targets": [ { - "datasource": { - "type": "prometheus", - "uid": "prometheus_uid" - }, "expr": "rate(ClickHouseProfileEvents_InsertedRows[1m])", "refId": "A" } @@ -975,10 +963,6 @@ }, "targets": [ { - "datasource": { - "type": "prometheus", - "uid": "prometheus_uid" - }, "expr": "rate(ClickHouseProfileEvents_Merge[1m])", "refId": "A" } From e851ef97885ed1708e86c83c64c24833d788131e Mon Sep 17 00:00:00 2001 From: Dmitry Dementev Date: Mon, 9 Feb 2026 09:28:13 +0300 Subject: [PATCH 3/5] fix(monitoring): add recover flow for stuck monitoring stack - Why: - during intensive development monitoring can get stuck (No data, out of bounds) - regular reload is not always enough to recover Prometheus + StatsD pipeline - What: - add make target recover-monitoring for hard recovery path - recreate prometheus and statsd-exporter, restart airflow scheduler/webserver - keep Grafana provisioning reload and target checks in one command - document when to use recover-monitoring in OPERATIONS runbook - Check: - run make recover-monitoring - verify Prometheus targets for airflow/clickhouse/kafka are up --- Makefile | 17 ++++++++++++++++- docs/OPERATIONS.md | 10 ++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 2b3de66..ec600e8 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: up ddl data transform reload-monitoring +.PHONY: up ddl data transform reload-monitoring recover-monitoring COMPOSE ?= docker compose @@ -26,3 +26,18 @@ reload-monitoring: @curl -s -u admin:admin -X POST http://localhost:3000/api/admin/provisioning/alerting/reload && echo " [alerting]" @echo "=== Проверка ===" @curl -s http://localhost:9090/api/v1/targets | grep -o '"job":"[^"]*"' | sort | uniq + +# Восстановление мониторинга после сбоев (например, out of bounds / пустые дашборды) +recover-monitoring: + @echo "=== Восстановление мониторинга (жесткий режим) ===" + $(COMPOSE) rm -sf prometheus statsd-exporter + $(COMPOSE) up -d prometheus grafana kafka-exporter statsd-exporter + $(COMPOSE) restart airflow-scheduler airflow-webserver + @echo "=== Перезагрузка provisioning Grafana ===" + @sleep 2 + @curl -s -u admin:admin -X POST http://localhost:3000/api/admin/provisioning/datasources/reload && echo " [datasources]" + @curl -s -u admin:admin -X POST http://localhost:3000/api/admin/provisioning/dashboards/reload && echo " [dashboards]" + @curl -s -u admin:admin -X POST http://localhost:3000/api/admin/provisioning/alerting/reload && echo " [alerting]" + @echo "=== Проверка targets ===" + @curl -s http://localhost:9090/api/v1/targets | grep -o '"job":"[^"]*"' | sort | uniq + @curl -s http://localhost:9090/api/v1/targets | grep -o '"health":"[^"]*"' | sort | uniq -c diff --git a/docs/OPERATIONS.md b/docs/OPERATIONS.md index b5ffe85..efcc33d 100644 --- a/docs/OPERATIONS.md +++ b/docs/OPERATIONS.md @@ -95,6 +95,8 @@ docker compose exec -T clickhouse clickhouse-client --user=default --password=12 ```bash # Быстрый вариант (make) make reload-monitoring +# Если мониторинг "залип" (No data/out of bounds) — жесткое восстановление: +make recover-monitoring # Или вручную: docker compose up -d prometheus grafana kafka-exporter statsd-exporter @@ -136,10 +138,13 @@ curl -s http://localhost:9090/api/v1/targets | grep -o '"health":"[^"]*"' ```bash # Рекомендуемый способ (через make) make reload-monitoring +# Если метрики пропали/залипли: +make recover-monitoring # Или вручную: -docker compose up -d prometheus grafana kafka-exporter -docker compose restart prometheus +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 @@ -248,6 +253,7 @@ curl -s -X POST -u admin:admin http://localhost:3000/api/admin/provisioning/aler **Общие проблемы:** - **"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 From 0e470604feb3b6fe2c3648ea9fd9081b0b662ef4 Mon Sep 17 00:00:00 2001 From: Dmitry Dementev Date: Mon, 9 Feb 2026 09:33:54 +0300 Subject: [PATCH 4/5] chore(scripts): add make down and clean targets - Why: - intensive development needs quick cluster stop/cleanup commands - current Makefile had only up and pipeline/monitoring targets - What: - add make target down for standard docker compose shutdown - add make target clean for full cleanup with volumes and orphans - update OPERATIONS runbook with new make commands - Check: - make -n down clean --- Makefile | 10 +++++++++- docs/OPERATIONS.md | 2 ++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index ec600e8..8b066ef 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,18 @@ -.PHONY: up ddl data transform reload-monitoring recover-monitoring +.PHONY: up down clean ddl data transform reload-monitoring recover-monitoring COMPOSE ?= docker compose up: $(COMPOSE) up -d +# Остановить и удалить контейнеры/сети текущего проекта +down: + $(COMPOSE) down + +# Полная очистка окружения проекта (включая volumes) +clean: + $(COMPOSE) down -v --remove-orphans + ddl: bash ./scripts/apply_clickhouse_ddl.sh diff --git a/docs/OPERATIONS.md b/docs/OPERATIONS.md index efcc33d..ac69d8c 100644 --- a/docs/OPERATIONS.md +++ b/docs/OPERATIONS.md @@ -7,6 +7,8 @@ Базовые команды: - `make up` (или `docker compose up -d`) +- `make down` (остановить и удалить контейнеры/сети проекта) +- `make clean` (полная очистка: `down -v --remove-orphans`) - `make ddl` (применяет SQL из `sql/ddl/00_databases.sql` и `sql/ddl/*/*.sql` в ClickHouse) - `make data` (пересоздаёт топики и заливает небольшой срез данных в Kafka; полный режим — `FULL=1 make data`) - `make transform` (запускает batch-процесс ODS -> DDS -> DM) From f411a46aea0aa1a6baa1a7e9a017bd8173c7e572 Mon Sep 17 00:00:00 2001 From: Dmitry Dementev Date: Mon, 9 Feb 2026 09:54:31 +0300 Subject: [PATCH 5/5] docs(docs): add comprehensive system test plan - Why: - formalize complete end-to-end verification for the demo DWH stack - provide fast regression checks and full validation before demo/release - What: - add new TEST_PLAN.md with two execution contours: Smoke and Full - include checks for infra bootstrap, Airflow DAG flow, STG/ODS/DDS/DM data quality, monitoring and alert provisioning - add dedicated scenario proving dirty records are captured in ods.*_errors without breaking ETL - Check: - aligned steps with current DAG parameters/tasks and SQL transformation flow - validated expected alert names against Grafana provisioning files --- docs/TEST_PLAN.md | 310 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 310 insertions(+) create mode 100644 docs/TEST_PLAN.md diff --git a/docs/TEST_PLAN.md b/docs/TEST_PLAN.md new file mode 100644 index 0000000..3a9ea5d --- /dev/null +++ b/docs/TEST_PLAN.md @@ -0,0 +1,310 @@ +# План тестирования стенда (Smoke + Full) + +Документ описывает два контура проверки системы: +- быстрый `smoke` для регрессий после изменений; +- полный `full` для финальной валидации end-to-end. + +## Цель + +Проверить, что стек `Kafka + ClickHouse + Airflow + Superset + Prometheus/Grafana`: +- стабильно поднимается; +- загружает данные по пути `kafka_load -> STG -> etl_pipeline -> ODS/DDS/DM`; +- корректно обрабатывает «грязные» записи (ошибки фиксируются в ODS, пайплайн не падает); +- отдает метрики и дашборды мониторинга. + +## Общие принципы + +- По умолчанию используем малый срез (`limit=50`) для быстрых и повторяемых проверок. +- Полный прогон (`limit=0`) выполняем отдельно как long-run сценарий. +- Основной путь запуска — через Airflow DAG. +- Критерий успеха: не только `Success` DAG, но и проверки данных/ошибок/мониторинга. + +--- + +## Контур A: Smoke (быстрый) + +Ожидаемая длительность: ~10-20 минут. + +### A.1 Подготовка окружения + +```bash +# Полная очистка стенда +make clean + +# Запуск инфраструктуры +make up + +# Проверка контейнеров +docker compose ps +``` + +Ожидаем: +- `airflow-init` в `Exited (0)`; +- остальные сервисы в `Up` (включая `superset`, `prometheus`, `grafana`, `kafka-exporter`, `statsd-exporter`). + +### A.2 DDL и минимальная загрузка данных + +```bash +# Инициализация схемы +docker compose exec -T airflow-webserver airflow dags trigger ddl_init + +# Быстрый ingest: по 50 строк на поток +docker compose exec -T airflow-webserver airflow dags trigger kafka_load \ + --conf '{"limit": 50, "reset_topics": true}' +``` + +Проверки: + +```bash +# STG не пустой + +docker compose exec -T clickhouse clickhouse-client --user=default --password=123456 \ + --query " +SELECT 'browser_raw' AS table, count() AS cnt FROM stg.browser_raw +UNION ALL +SELECT 'location_raw', count() FROM stg.location_raw +UNION ALL +SELECT 'device_raw', count() FROM stg.device_raw +UNION ALL +SELECT 'geo_raw', count() FROM stg.geo_raw +" +``` + +Ожидаем: во всех 4 таблицах `cnt > 0`. + +### A.3 ETL и проверки слоев + +```bash +docker compose exec -T airflow-webserver airflow dags trigger etl_pipeline \ + --conf '{"full_refresh": true}' +``` + +Проверки: + +```bash +# ODS / DDS / DM +docker compose exec -T clickhouse clickhouse-client --user=default --password=123456 \ + --query " +SELECT 'ods.browser_event' AS table, count() AS cnt FROM ods.browser_event +UNION ALL +SELECT 'dds.event', count() FROM dds.event +UNION ALL +SELECT 'dds.click', count() FROM dds.click +UNION ALL +SELECT 'dm.dq_summary', count() FROM dm.dq_summary +" + +# Базовая целостность DDS (ожидаем 0 orphan-событий) +docker compose exec -T clickhouse clickhouse-client --user=default --password=123456 \ + --query " +SELECT countIf(click_id IS NOT NULL AND click_id NOT IN (SELECT click_id FROM dds.click)) AS orphan_events +FROM dds.event +" +``` + +Ожидаем: +- `ods.browser_event`, `dds.event`, `dm.dq_summary` > 0; +- `orphan_events = 0`. + +### A.4 Smoke мониторинга + +```bash +# Prometheus targets +curl -s http://localhost:9090/api/v1/targets | grep -o '"health":"[^"]*"' + +# Grafana health +curl -s -u admin:admin http://localhost:3000/api/health + +# Дашборды по UID +curl -s -u admin:admin "http://localhost:3000/api/dashboards/uid/clickhouse-overview" | grep -o '"title":"[^"]*"' +curl -s -u admin:admin "http://localhost:3000/api/dashboards/uid/kafka-overview" | grep -o '"title":"[^"]*"' +curl -s -u admin:admin "http://localhost:3000/api/dashboards/uid/airflow-overview" | grep -o '"title":"[^"]*"' +``` + +Критерий успеха smoke: +- сервисы подняты; +- 3 DAG (`ddl_init`, `kafka_load`, `etl_pipeline`) успешны; +- данные проходят до DM; +- мониторинг и дашборды доступны. + +--- + +## Контур B: Full (полный) + +Ожидаемая длительность: ~30-60 минут. + +### B.1 Полная загрузка и полный ETL + +```bash +# Чистый старт +make clean && make up + +# DDL +docker compose exec -T airflow-webserver airflow dags trigger ddl_init + +# Полный ingest (все строки) +docker compose exec -T airflow-webserver airflow dags trigger kafka_load \ + --conf '{"limit": 0, "reset_topics": true}' + +# Полный ETL +docker compose exec -T airflow-webserver airflow dags trigger etl_pipeline \ + --conf '{"full_refresh": true}' +``` + +### B.2 Проверка объемов и DQ + +```bash +# Фактические размеры входа +wc -l data/*.jsonl + +# Сводка по слоям +docker compose exec -T clickhouse clickhouse-client --user=default --password=123456 --query " +SELECT 'STG' as layer, sum(rows) as total_rows FROM ( + SELECT count() as rows FROM stg.browser_raw UNION ALL + SELECT count() FROM stg.location_raw UNION ALL + SELECT count() FROM stg.device_raw UNION ALL + SELECT count() FROM stg.geo_raw +) UNION ALL +SELECT 'ODS', sum(rows) FROM ( + SELECT count() FROM ods.browser_event UNION ALL + SELECT count() FROM ods.location_event UNION ALL + SELECT count() FROM ods.device_by_click UNION ALL + SELECT count() FROM ods.geo_by_click +) UNION ALL +SELECT 'DDS', sum(rows) FROM ( + SELECT count() FROM dds.event UNION ALL + SELECT count() FROM dds.click +)" + +# DQ summary + +docker compose exec -T clickhouse clickhouse-client --user=default --password=123456 \ + --query "SELECT * FROM dm.dq_summary ORDER BY layer, table_name, check_name" +``` + +Критерии успеха full: +- STG заполнен по всем 4 потокам; +- ODS/DDS/DM заполнены; +- `dm.dq_summary` не пуста и содержит метрики всех слоев. + +### B.3 Тест восстановления stop/start + +```bash +# Остановить без удаления данных +docker compose stop + +# Проверить volumes + +docker volume ls | grep -E 'clickhouse-data|kafka-data|pgmeta|grafana_lib|superset_data|superset_config' + +# Поднять обратно +docker compose start + +# Проверить, что данные сохранились + +docker compose exec -T clickhouse clickhouse-client --user=default --password=123456 \ + --query "SELECT count() FROM dds.event" +``` + +--- + +## Сценарий C: «Грязные» данные не валят пайплайн + +Цель: доказать, что невалидные записи фиксируются в `ods.*_errors`, а ETL завершается успешно. + +Предусловие: выполнен `Контур A` или `Контур B` (в STG уже есть валидные данные). + +### C.1 Инъекция невалидных записей в STG + +```bash +# browser: невалидные UUID и timestamp + +docker compose exec -T clickhouse clickhouse-client --user=default --password=123456 \ + --query "INSERT INTO stg.browser_raw (raw) VALUES ('{\"event_id\":\"bad-uuid\",\"event_timestamp\":\"bad-ts\",\"click_id\":\"bad-click\",\"event_type\":\"pageview\"}')" + +# location: невалидный event_id + +docker compose exec -T clickhouse clickhouse-client --user=default --password=123456 \ + --query "INSERT INTO stg.location_raw (raw) VALUES ('{\"event_id\":\"bad-uuid\",\"page_url\":\"https://example.com\"}')" + +# device: невалидные click_id и user_domain_id + +docker compose exec -T clickhouse clickhouse-client --user=default --password=123456 \ + --query "INSERT INTO stg.device_raw (raw) VALUES ('{\"click_id\":\"bad-uuid\",\"user_domain_id\":\"bad-uuid\",\"device_type\":\"Mobile\"}')" + +# geo: невалидные click_id и координаты + +docker compose exec -T clickhouse clickhouse-client --user=default --password=123456 \ + --query "INSERT INTO stg.geo_raw (raw) VALUES ('{\"click_id\":\"bad-uuid\",\"geo_latitude\":\"abc\",\"geo_longitude\":\"def\"}')" +``` + +### C.2 Повторный ETL + +```bash +docker compose exec -T airflow-webserver airflow dags trigger etl_pipeline \ + --conf '{"full_refresh": true}' +``` + +Ожидаем: DAG `etl_pipeline` завершен в `Success`. + +### C.3 Ассерты по ошибкам + +```bash +# Ошибки должны попасть в *_errors таблицы + +docker compose exec -T clickhouse clickhouse-client --user=default --password=123456 \ + --query " +SELECT 'browser_event_errors' AS table, count() AS cnt FROM ods.browser_event_errors +UNION ALL +SELECT 'location_event_errors', count() FROM ods.location_event_errors +UNION ALL +SELECT 'device_by_click_errors', count() FROM ods.device_by_click_errors +UNION ALL +SELECT 'geo_by_click_errors', count() FROM ods.geo_by_click_errors +" + +# При этом пайплайн продолжает давать бизнес-слой + +docker compose exec -T clickhouse clickhouse-client --user=default --password=123456 \ + --query "SELECT count() FROM dds.event" +``` + +Критерии успеха сценария C: +- `etl_pipeline` не падает; +- хотя бы одна `*_errors` таблица увеличилась; +- `dds.event` остается непустой (валидные данные продолжают обрабатываться). + +--- + +## Проверка алертов (фактический набор) + +Проверяем, что в Grafana загружены именно текущие provisioned-правила: + +```bash +curl -s -u admin:admin http://localhost:3000/api/v1/provisioning/alert-rules | grep -o '"title":"[^"]*"' +``` + +Ожидаемые правила: + +- ClickHouse: + - `ClickHouse Failed Queries Rate` + - `ClickHouse Memory Resident High` + - `ClickHouse Parts Active High` +- Airflow: + - `Airflow Scheduler Down` + - `Airflow Queue Backlog` + - `High Task Failure Rate` + - `High DAG Parse Time` +- Kafka: + - `Kafka Broker Down` + - `Kafka Consumer Lag High` + - `Kafka No Messages Produced` + +--- + +## Финальный чек-лист приемки + +- `Smoke` проходит стабильно после изменений в коде. +- `Full` проходит перед демонстрацией/релизом. +- Сценарий `Грязные данные` подтверждает, что ошибки локализуются в ODS и не ломают ETL. +- Метрики и дашборды доступны, алерты совпадают с текущей конфигурацией provisioning.