Files
clickstream-ch-kafka-supers…/plans/airflow_dags_plan.md
T
ddadmin 4da6a34e4c docs(airflow): update dag implementation plan for mvp
Consolidate the orchestration strategy by merging `kafka_load` and
`etl_batch_transform` into a unified `etl_pipeline`. Replace BashOperator
dependencies on Kafka CLI with PythonOperators utilizing `kafka-python`.
Add detailed technical specifications for helper functions, MVP stages,
and validation checks to align with current infrastructure constraints.
2026-02-07 00:03:32 +03:00

187 lines
9.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# План развития Airflow DAG'ов
## Цель
Перевести оркестрацию ETL на Airflow так, чтобы пайплайн оставался устойчивым к "грязным" данным и соответствовал текущей цели проекта: Kafka → ClickHouse (STG → ODS → DDS → DM) → витрины для BI.
## Что важно учесть в текущем репозитории
- В `AGENTS.md` как quick check ожидается DAG `etl_pipeline`.
- В Airflow-контейнере сейчас нет Kafka CLI, поэтому `kafka-topics.sh` и `kafka-console-producer.sh` из `BashOperator` использовать нельзя без донастройки образа.
- DDL должен выполняться строго последовательно: `00 -> 10 -> 20 -> 30 -> 40`.
- Файл `jobs/30_dds_refresh.sql` уже включает обе загрузки (`dds.click` и `dds.event`), поэтому в MVP это одна task.
## Архитектура оркестрации
### DAG 1 (обязательный): `ddl_init`
- `schedule`: `None` (только ручной запуск).
- `catchup`: `False`.
- `max_active_runs`: `1`.
- `is_paused_upon_creation`: `True`.
- `tags`: `["ddl", "bootstrap", "clickhouse"]`.
### DAG 2 (обязательный): `etl_pipeline`
- `schedule`: `None` (ручной запуск для демо).
- `catchup`: `False`.
- `max_active_runs`: `1`.
- `tags`: `["etl", "clickhouse", "kafka", "demo"]`.
### DAG 3 (опциональный): `dq_monitor`
- `schedule`: `0 * * * *`.
- `catchup`: `False`.
- `tags`: `["dq", "monitoring"]`.
## Дизайн DAG `ddl_init`
### Params
- `verify_only`: bool, default `false` (прогон только проверок без применения DDL).
### Tasks
| Task ID | Что делает | Источник SQL/реализация |
|---------|------------|--------------------------|
| `check_clickhouse` | Проверка доступности CH (`SELECT 1`) | `PythonOperator` + `clickhouse-connect` |
| `ddl_00_databases` | Создание БД | `ddl/00_databases.sql` |
| `ddl_10_stg` | STG + Kafka Engine + MV | `ddl/10_stg.sql` |
| `ddl_20_ods` | ODS + MV STG→ODS + *_errors | `ddl/20_ods.sql` |
| `ddl_30_dds` | Таблицы DDS | `ddl/30_dds.sql` |
| `ddl_40_dm` | VIEW витрины DM | `ddl/40_dm.sql` |
| `verify_schema` | Проверка ключевых таблиц/VIEW | SQL-check |
Зависимости:
```text
check_clickhouse >> ddl_00_databases >> ddl_10_stg >> ddl_20_ods >> ddl_30_dds >> ddl_40_dm >> verify_schema
```
Примечание:
- DDL DAG запускается вручную: при первом bootstrap, при изменении схемы, после `docker compose down -v`.
## Дизайн DAG `etl_pipeline`
### Params (через Trigger DAG with config)
- `run_ingest`: bool, default `true`.
- `limit`: int, default `50`.
- `full_load`: bool, default `false`.
- `reset_topics`: bool, default `true`.
- `full_refresh`: bool, default `true`.
### TaskGroup `precheck`
| Task ID | Что делает | Реализация |
|---------|------------|------------|
| `check_clickhouse` | Проверка доступности CH (`SELECT 1`) | `PythonOperator` + `clickhouse-connect` |
| `check_schema_ready` | Проверка, что DDL уже применён (наличие `stg.browser_raw`, `ods.browser_event`, `dds.event`, `dm.v_events_enriched`) | SQL-check, fail fast |
| `check_input_files` | Проверка наличия `data/*_events.jsonl` | `PythonOperator` |
### TaskGroup `ingest` (выполняется только при `run_ingest=true`)
| Task ID | Что делает | Реализация |
|---------|------------|------------|
| `kafka_prepare_topics` | reset/create топиков по параметру `reset_topics` | `PythonOperator` + `kafka-python` AdminClient |
| `load_browser` | Публикация строк из `browser_events.jsonl` | `PythonOperator` + `KafkaProducer` |
| `load_location` | Публикация строк из `location_events.jsonl` | `PythonOperator` + `KafkaProducer` |
| `load_device` | Публикация строк из `device_events.jsonl` | `PythonOperator` + `KafkaProducer` |
| `load_geo` | Публикация строк из `geo_events.jsonl` | `PythonOperator` + `KafkaProducer` |
| `wait_for_stg_data` | Ожидание появления данных в `stg.*_raw` | `PythonSensor`/poll SQL |
Зависимости:
```text
kafka_prepare_topics >> [load_browser, load_location, load_device, load_geo] >> wait_for_stg_data
```
Примечание:
- Для демо соблюдать ограничение на малый срез данных: `limit=20..50` по умолчанию.
### TaskGroup `transform`
| Task ID | Что делает | Источник SQL |
|---------|------------|--------------|
| `wait_for_ods_data` | Ожидание строк в `ods.browser_event` | SQL-check |
| `check_ods_quality` | Базовые DQ-метрики ODS (ошибки/total) | SQL-check |
| `truncate_dds_click` | Очистка `dds.click` при `full_refresh=true` | inline SQL |
| `truncate_dds_event` | Очистка `dds.event` при `full_refresh=true` | inline SQL |
| `refresh_dds` | ODS → DDS | `jobs/30_dds_refresh.sql` |
| `check_dds_integrity` | Проверка orphan событий | inline SQL |
| `refresh_dm_summary` | DDS → DM DQ summary | `jobs/40_dm_refresh.sql` |
| `validate_dm_summary` | Проверка, что `dm.dq_summary` не пуста | SQL-check |
Зависимости:
```text
wait_for_ods_data >> check_ods_quality >> [truncate_dds_click, truncate_dds_event] >> refresh_dds >> check_dds_integrity >> refresh_dm_summary >> validate_dm_summary
```
### Итоговая цепочка `etl_pipeline`
```text
precheck >> ingest(optional) >> transform
```
## Техническая реализация (приземленно)
### ClickHouse в Airflow
- Использовать `clickhouse-connect` напрямую в Python helper, а не `SQLExecuteQueryOperator`.
- Брать параметры подключения из `conn_id = clickhouse_default` через `BaseHook.get_connection`.
### Kafka в Airflow
- Добавить зависимость `kafka-python` в `airflow/requirements.txt`.
- Использовать Python-код для:
- reset/create топиков;
- публикации строк из `.jsonl` (1 строка = 1 message value).
### Общие helper-функции
- `dags/utils/clickhouse_helpers.py`:
- `execute_sql(sql: str) -> None`
- `execute_sql_file(path: str) -> None`
- `fetch_one(sql: str) -> tuple`
- `dags/utils/kafka_helpers.py`:
- `prepare_topics(reset: bool) -> None`
- `load_jsonl(file_path: str, topic: str, limit: int, full_load: bool) -> int`
## Структура файлов
```text
dags/
├── __init__.py
├── ddl_init_dag.py # отдельный DAG для DDL (обязателен)
├── etl_pipeline_dag.py # основной ETL DAG (обязателен)
├── dq_monitor_dag.py # опциональный DAG мониторинга
└── utils/
├── __init__.py
├── clickhouse_helpers.py
└── kafka_helpers.py
```
## Этапы внедрения
1. Этап 1 (MVP, обязательно):
- Реализовать `ddl_init` и `etl_pipeline`.
- В `etl_pipeline` оставить `precheck + transform`, ingest пока выполнять внешней командой `make data`.
- Проверить путь: `ddl_init` -> ODS -> DDS -> DM после ручной загрузки в Kafka.
2. Этап 2:
- Добавить в `etl_pipeline` ingest внутри DAG через `kafka-python`.
- Добавить ветвление `run_ingest=false` для сценария "только transform".
3. Этап 3:
- Добавить `dq_monitor` и alert callback (email/Slack/webhook).
## Критерии готовности
- В Airflow UI видны DAG `ddl_init` и `etl_pipeline`.
- `etl_pipeline` падает с понятной ошибкой, если схема не применена.
- Ручной запуск DAG с `limit=50` завершает pipeline без падений.
- После прогона:
- в `ods.browser_event` есть строки;
- в `dds.click` и `dds.event` есть строки;
- `dm.dq_summary` заполнена.
- При повторном запуске с `full_refresh=true` нет неконтролируемых дублей в DDS.
## Минимальные smoke-checks
```bash
# 1) Запуск инфраструктуры
make up
# 2) Открыть Airflow UI
# http://localhost:8080 (admin/admin)
# 3) Один раз запустить ddl_init
# Trigger DAG ddl_init (без config или {"verify_only": false})
# 4) Trigger DAG etl_pipeline с config:
# {"run_ingest": true, "limit": 50, "full_load": false, "reset_topics": true, "full_refresh": true}
# 5) Проверка результатов
docker compose exec -T clickhouse clickhouse-client --user=default --password=123456 --query "SELECT count() FROM ods.browser_event"
docker compose exec -T clickhouse clickhouse-client --user=default --password=123456 --query "SELECT count() FROM dds.click"
docker compose exec -T clickhouse clickhouse-client --user=default --password=123456 --query "SELECT count() FROM dds.event"
docker compose exec -T clickhouse clickhouse-client --user=default --password=123456 --query "SELECT count() FROM dm.dq_summary"
```
## Следующий шаг после MVP
- Для ускорения можно разделить `jobs/30_dds_refresh.sql` на два файла и распараллелить `refresh_dds_click` и `refresh_dds_event` в DAG.
- Для продакшн-режима перейти с `full_refresh` на watermark-инкремент.