- Зачем: - нужен единый штатный путь исполнения SQL и программных запросов ClickHouse из Airflow. - Что: - добавлен официальный провайдер и подключения типа `clickhouse`. - SQL-файлы переданы `SQLExecuteQueryOperator`, а программные запросы — `ClickHouseHook`. - разбор заказов собран в одном файле, DDL разделён по задачам, документация и смоук обновлены. - Проверка: - выполнены `make lint` и `make config-test`. - собран образ, выполнены `pip check`, `make rebuild-storage`, `make smoke` (21/21), `make check-clickhouse` (10/10) и `make check-services` (7/7).
68 lines
3.3 KiB
Python
68 lines
3.3 KiB
Python
"""Приём заказов: пакетный забор слепка из Kafka в хранилище.
|
|
|
|
Путь Kafka → STG → ODS у заказов один и живёт одним дагом: сначала забор
|
|
порции в сырьё, затем разбор того же среза в типизированный ODS и в таблицу
|
|
брака. Расписания у дага нет: его дёргает тот, кто положил слепок в топик, —
|
|
работник пульта мира после проигрыша дня, — и ждёт конца.
|
|
|
|
Контраст с приёмом событий и есть урок. События тянет матвью, навсегда
|
|
подписанная на чтеца: приём идёт сам, пока идёт поток. Слепок заказов
|
|
приезжает раз в модельный день целой выгрузкой, у которой есть начало и конец,
|
|
и забирает её запрос по команде. Push против pull — два режима на одном стенде,
|
|
каждый там, где ему место по природе источника.
|
|
|
|
Решения и доводы целиком — ADR 0008 (забор) и ADR 0010 (версии в ODS); форма
|
|
перехода — docs/architecture/orders/ingestion.md.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import datetime
|
|
|
|
from airflow.providers.common.sql.operators.sql import SQLExecuteQueryOperator
|
|
from airflow.sdk import dag
|
|
|
|
START_DATE = datetime.datetime(2026, 1, 1, tzinfo=datetime.UTC)
|
|
|
|
# Запросы лежат файлами в sql/ репозитория, разложенные по слоям хранилища;
|
|
# сюда их монтирует compose — всем службам Airflow сразу.
|
|
SQL_ROOT = "/opt/airflow/sql"
|
|
|
|
|
|
@dag(
|
|
dag_id="orders_ingest",
|
|
schedule=None,
|
|
start_date=START_DATE,
|
|
is_paused_upon_creation=False,
|
|
# Чтец у топика один, и группа потребителей у него одна. Два прогона разом
|
|
# дрались бы за неё, а слепок разъехался бы по двум _load_id; второй
|
|
# прогон подождёт своей очереди.
|
|
max_active_runs=1,
|
|
template_searchpath=SQL_ROOT,
|
|
tags=["заказы"],
|
|
)
|
|
def orders_ingest():
|
|
"""Забрать приехавший слепок заказов и разложить его по слоям."""
|
|
pull_batch = SQLExecuteQueryOperator(
|
|
task_id="pull_batch",
|
|
conn_id="clickhouse_default",
|
|
sql="stg/orders_raw_load.sql",
|
|
parameters={"load_id": "{{ run_id }}"},
|
|
do_xcom_push=False,
|
|
)
|
|
parse_batch = SQLExecuteQueryOperator(
|
|
task_id="parse_batch",
|
|
conn_id="clickhouse_default",
|
|
sql="ods/order_parse.sql",
|
|
parameters={"load_id": "{{ run_id }}"},
|
|
split_statements=True,
|
|
do_xcom_push=False,
|
|
)
|
|
|
|
# ClickHouse не даёт транзакции между двумя вставками parse_batch. Один
|
|
# task_id сохраняет их порядок и одну точку повтора для неизменного load_id.
|
|
pull_batch >> parse_batch
|
|
|
|
|
|
orders_ingest()
|