refactor(airflow): move DAGs to airflow/dags and update paths

- Why:
  - keep Airflow artifacts under a single airflow/ directory
  - align repository layout with intended project structure
- What:
  - move dags/ to airflow/dags/ and update compose mounts
  - make SQL root resolution work in container and local runs
  - update DAG path references in README, AGENTS, ARCHITECTURE, and plans
  - remove tracked Python cache artifacts from old DAG location
- Check:
  - airflow dags list
  - airflow dags list-import-errors
  - e2e success: ddl_init, kafka_load(limit=50), etl_pipeline
This commit is contained in:
2026-02-08 19:34:10 +03:00
parent 44a75691e8
commit 869c189fe8
14 changed files with 41 additions and 17 deletions
+5 -5
View File
@@ -20,11 +20,11 @@
## Ключевые артефакты ## Ключевые артефакты
### Исполняемые файлы (текущая структура) ### Исполняемые файлы (текущая структура)
- `dags/` — Airflow DAGs для оркестрации ETL: - `airflow/dags/` — Airflow DAGs для оркестрации ETL:
- `dags/ddl_init_dag.py` — инициализация схемы ClickHouse - `airflow/dags/ddl_init_dag.py` — инициализация схемы ClickHouse
- `dags/etl_pipeline_dag.py` — ETL процесс STG → ODS → DDS → DM - `airflow/dags/etl_pipeline_dag.py` — ETL процесс STG → ODS → DDS → DM
- `dags/kafka_load_dag.py` — загрузка данных в Kafka из JSONL - `airflow/dags/kafka_load_dag.py` — загрузка данных в Kafka из JSONL
- `dags/utils/kafka_helpers.py` — helper-функции для работы с Kafka - `airflow/dags/utils/kafka_helpers.py` — helper-функции для работы с Kafka
- `sql/` — SQL по слоям: - `sql/` — SQL по слоям:
- `sql/ddl/00_databases.sql` — создание БД stg/ods/dds/dm - `sql/ddl/00_databases.sql` — создание БД stg/ods/dds/dm
- `sql/ddl/stg/10_stg.sql` — STG слой (Kafka Engine + MV) - `sql/ddl/stg/10_stg.sql` — STG слой (Kafka Engine + MV)
+1 -1
View File
@@ -115,7 +115,6 @@ flowchart LR
``` ```
. .
├── dags/ # Airflow DAGs для оркестрации
├── sql/ ├── sql/
│ ├── ddl/ # DDL по слоям │ ├── ddl/ # DDL по слоям
│ │ ├── 00_databases.sql │ │ ├── 00_databases.sql
@@ -128,6 +127,7 @@ flowchart LR
│ └── dm/ # Batch SQL: DDS -> DM │ └── dm/ # Batch SQL: DDS -> DM
├── scripts/ # Служебные shell-скрипты (legacy fallback, не основной путь) ├── scripts/ # Служебные shell-скрипты (legacy fallback, не основной путь)
├── airflow/ # Конфигурация Airflow ├── airflow/ # Конфигурация Airflow
│ ├── dags/ # Airflow DAGs для оркестрации
│ └── requirements.txt │ └── requirements.txt
├── docs/ # Документация ├── docs/ # Документация
│ └── ARCHITECTURE.md # Подробное описание слоёв │ └── ARCHITECTURE.md # Подробное описание слоёв
@@ -38,7 +38,19 @@ default_args = {
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# SQL-файлы проекта # SQL-файлы проекта
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
SQL_ROOT = Path(__file__).resolve().parents[1] / "sql" def resolve_sql_root() -> Path:
"""Определяет корень SQL для контейнера и локального запуска."""
candidates = (
Path(__file__).resolve().parents[1] / "sql", # /opt/airflow/sql в контейнере
Path(__file__).resolve().parents[2] / "sql", # <repo>/sql при локальном запуске
)
for candidate in candidates:
if candidate.is_dir():
return candidate
return candidates[0]
SQL_ROOT = resolve_sql_root()
def load_sql_statements(relative_path: str) -> tuple[str, ...]: def load_sql_statements(relative_path: str) -> tuple[str, ...]:
@@ -41,7 +41,19 @@ default_args = {
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# SQL-файлы проекта # SQL-файлы проекта
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
SQL_ROOT = Path(__file__).resolve().parents[1] / "sql" def resolve_sql_root() -> Path:
"""Определяет корень SQL для контейнера и локального запуска."""
candidates = (
Path(__file__).resolve().parents[1] / "sql", # /opt/airflow/sql в контейнере
Path(__file__).resolve().parents[2] / "sql", # <repo>/sql при локальном запуске
)
for candidate in candidates:
if candidate.is_dir():
return candidate
return candidates[0]
SQL_ROOT = resolve_sql_root()
def load_sql_statements(relative_path: str) -> tuple[str, ...]: def load_sql_statements(relative_path: str) -> tuple[str, ...]:
Binary file not shown.
Binary file not shown.
+3 -3
View File
@@ -108,7 +108,7 @@ services:
ports: ports:
- "8080:8080" - "8080:8080"
volumes: volumes:
- ./dags:/opt/airflow/dags - ./airflow/dags:/opt/airflow/dags
- ./sql:/opt/airflow/sql:ro - ./sql:/opt/airflow/sql:ro
- ./data:/opt/airflow/data - ./data:/opt/airflow/data
networks: networks:
@@ -133,7 +133,7 @@ services:
airflow scheduler airflow scheduler
" "
volumes: volumes:
- ./dags:/opt/airflow/dags - ./airflow/dags:/opt/airflow/dags
- ./sql:/opt/airflow/sql:ro - ./sql:/opt/airflow/sql:ro
- ./data:/opt/airflow/data - ./data:/opt/airflow/data
networks: networks:
@@ -155,7 +155,7 @@ services:
environment: environment:
<<: *airflow-default-env <<: *airflow-default-env
volumes: volumes:
- ./dags:/opt/airflow/dags - ./airflow/dags:/opt/airflow/dags
- ./sql:/opt/airflow/sql:ro - ./sql:/opt/airflow/sql:ro
- ./data:/opt/airflow/data - ./data:/opt/airflow/data
networks: networks:
+3 -3
View File
@@ -578,9 +578,9 @@ INSERT INTO dm.daily_traffic SELECT * FROM dm.v_daily_traffic;
Инфраструктура Airflow развёрнута и готова к использованию: Инфраструктура Airflow развёрнута и готова к использованию:
```python ```python
# dags/ddl_init_dag.py — создание баз/таблиц (ручной запуск при bootstrap) # airflow/dags/ddl_init_dag.py — создание баз/таблиц (ручной запуск при bootstrap)
# dags/kafka_load_dag.py — загрузка JSONL в Kafka (через kafka-python) # airflow/dags/kafka_load_dag.py — загрузка JSONL в Kafka (через kafka-python)
# dags/etl_pipeline_dag.py — основной ETL (STG→ODS→DDS→DM) # airflow/dags/etl_pipeline_dag.py — основной ETL (STG→ODS→DDS→DM)
# Учебный формат: # Учебный формат:
# - DDL и трансформации выполняются явными SQL-task через ClickHouseOperator; # - DDL и трансформации выполняются явными SQL-task через ClickHouseOperator;
+3 -3
View File
@@ -146,18 +146,18 @@ ddl_init -> kafka_load -> etl_pipeline
- публикации строк из `.jsonl` (`1 строка = 1 message value`). - публикации строк из `.jsonl` (`1 строка = 1 message value`).
### Общие helper-функции ### Общие helper-функции
- `dags/utils/clickhouse_helpers.py`: - `airflow/dags/utils/clickhouse_helpers.py`:
- `execute_sql(sql: str) -> None` - `execute_sql(sql: str) -> None`
- `execute_sql_file(path: str) -> None` - `execute_sql_file(path: str) -> None`
- `fetch_one(sql: str) -> tuple` - `fetch_one(sql: str) -> tuple`
- `dags/utils/kafka_helpers.py`: - `airflow/dags/utils/kafka_helpers.py`:
- `prepare_topics(reset: bool) -> None` - `prepare_topics(reset: bool) -> None`
- `load_jsonl(file_path: str, topic: str, limit: int) -> int` - `load_jsonl(file_path: str, topic: str, limit: int) -> int`
- `check_kafka_ready() -> None` - `check_kafka_ready() -> None`
## Структура файлов ## Структура файлов
```text ```text
dags/ airflow/dags/
├── __init__.py ├── __init__.py
├── ddl_init_dag.py # отдельный DAG для DDL (обязателен) ├── ddl_init_dag.py # отдельный DAG для DDL (обязателен)
├── kafka_load_dag.py # отдельный DAG для ingest в Kafka (обязателен) ├── kafka_load_dag.py # отдельный DAG для ingest в Kafka (обязателен)