Склелет учебного даг
This commit is contained in:
@@ -0,0 +1,140 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
"""
|
||||||
|
Учебный DAG для менти: показывает, как устроен поток
|
||||||
|
от источника bookings-db до слоя stg в Greenplum.
|
||||||
|
|
||||||
|
Важно: этот файл специально должен быть хорошо задокументирован —
|
||||||
|
docstring и комментарии помогают студенту понять, «зачем» каждая задача,
|
||||||
|
а не только «что именно она делает».
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
from airflow import DAG
|
||||||
|
from airflow.operators.python import PythonOperator
|
||||||
|
|
||||||
|
from helpers.greenplum import get_gp_conn
|
||||||
|
|
||||||
|
|
||||||
|
default_args = {
|
||||||
|
"owner": "airflow",
|
||||||
|
"retries": 1,
|
||||||
|
"retry_delay": timedelta(seconds=30),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _generate_bookings_day(load_date: str) -> None:
|
||||||
|
"""
|
||||||
|
Генерирует данные за указанный день в bookings-db.
|
||||||
|
|
||||||
|
Важно: функция должна быть идемпотентной:
|
||||||
|
если данные за load_date уже есть в исходной БД,
|
||||||
|
повторно пересобирать день не нужно.
|
||||||
|
"""
|
||||||
|
logging.info(
|
||||||
|
"Генерация учебного дня в bookings-db за дату %s (заглушка)", load_date
|
||||||
|
)
|
||||||
|
# TODO: реализовать проверку наличия дня в bookings.bookings
|
||||||
|
# и генерацию нового дня при его отсутствии.
|
||||||
|
|
||||||
|
|
||||||
|
def _get_last_loaded_ts_from_gp() -> str | None:
|
||||||
|
"""
|
||||||
|
Возвращает максимальное значение src_created_at_ts из stg.bookings.
|
||||||
|
|
||||||
|
Пока функция возвращает None как заглушку, что соответствует
|
||||||
|
режиму полной загрузки (full).
|
||||||
|
"""
|
||||||
|
logging.info(
|
||||||
|
"Чтение последнего загруженного src_created_at_ts из stg.bookings (заглушка)"
|
||||||
|
)
|
||||||
|
# Пример будущей реализации:
|
||||||
|
# with get_gp_conn() as conn, conn.cursor() as cur:
|
||||||
|
# cur.execute("SELECT max(src_created_at_ts) FROM stg.bookings")
|
||||||
|
# row = cur.fetchone()
|
||||||
|
# return row[0]
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _extract_and_load_increment_via_pxf(
|
||||||
|
last_loaded_ts: str | None,
|
||||||
|
load_date: str,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Читает дельту из stg.bookings_ext и вставляет её в stg.bookings.
|
||||||
|
|
||||||
|
Логика:
|
||||||
|
- если last_loaded_ts is None → первая загрузка (full),
|
||||||
|
берём все данные за load_date и ранее;
|
||||||
|
- иначе берём только записи, где src_created_at_ts > last_loaded_ts
|
||||||
|
и не позже конца учебного дня.
|
||||||
|
"""
|
||||||
|
logging.info(
|
||||||
|
"Загрузка инкремента через PXF: last_loaded_ts=%s, load_date=%s (заглушка)",
|
||||||
|
last_loaded_ts,
|
||||||
|
load_date,
|
||||||
|
)
|
||||||
|
# TODO: реализовать INSERT INTO stg.bookings (...) SELECT ... FROM stg.bookings_ext
|
||||||
|
# с учётом инкрементального окна по src_created_at_ts.
|
||||||
|
|
||||||
|
|
||||||
|
def _check_row_counts(load_date: str) -> None:
|
||||||
|
"""
|
||||||
|
Проверяет, что количество строк из источника и в stg.bookings совпадает.
|
||||||
|
|
||||||
|
Эта проверка должна помочь студенту увидеть пример простой DQ‑проверки
|
||||||
|
для инкрементальной загрузки.
|
||||||
|
"""
|
||||||
|
logging.info("Проверка количества строк за %s (заглушка)", load_date)
|
||||||
|
# TODO: реализовать сравнение количества строк,
|
||||||
|
# например через SELECT COUNT(*) в источнике и в stg.bookings.
|
||||||
|
|
||||||
|
|
||||||
|
def _finish_summary() -> None:
|
||||||
|
"""Логирует краткий итог выполнения DAG за один запуск."""
|
||||||
|
logging.info("DAG bookings_to_gp_stage завершён (пока только скелет).")
|
||||||
|
|
||||||
|
|
||||||
|
with DAG(
|
||||||
|
dag_id="bookings_to_gp_stage",
|
||||||
|
start_date=datetime(2024, 1, 1),
|
||||||
|
schedule=None,
|
||||||
|
catchup=False,
|
||||||
|
default_args=default_args,
|
||||||
|
tags=["demo", "bookings", "greenplum", "stg"],
|
||||||
|
description="Учебный DAG: загрузка из bookings-db в stg.bookings (Greenplum)",
|
||||||
|
) as dag:
|
||||||
|
generate_bookings_day = PythonOperator(
|
||||||
|
task_id="generate_bookings_day",
|
||||||
|
python_callable=_generate_bookings_day,
|
||||||
|
op_kwargs={"load_date": "{{ ds }}"},
|
||||||
|
)
|
||||||
|
|
||||||
|
get_last_loaded_ts = PythonOperator(
|
||||||
|
task_id="get_last_loaded_ts_from_gp",
|
||||||
|
python_callable=_get_last_loaded_ts_from_gp,
|
||||||
|
)
|
||||||
|
|
||||||
|
extract_and_load_increment = PythonOperator(
|
||||||
|
task_id="extract_and_load_increment_via_pxf",
|
||||||
|
python_callable=_extract_and_load_increment_via_pxf,
|
||||||
|
op_kwargs={
|
||||||
|
"last_loaded_ts": "{{ ti.xcom_pull(task_ids='get_last_loaded_ts_from_gp') }}",
|
||||||
|
"load_date": "{{ ds }}",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
check_row_counts = PythonOperator(
|
||||||
|
task_id="check_row_counts",
|
||||||
|
python_callable=_check_row_counts,
|
||||||
|
op_kwargs={"load_date": "{{ ds }}"},
|
||||||
|
)
|
||||||
|
|
||||||
|
finish_summary = PythonOperator(
|
||||||
|
task_id="finish_summary",
|
||||||
|
python_callable=_finish_summary,
|
||||||
|
)
|
||||||
|
|
||||||
|
generate_bookings_day >> get_last_loaded_ts >> extract_and_load_increment >> check_row_counts >> finish_summary
|
||||||
@@ -124,3 +124,11 @@ DDL будет добавлен в `sql/ddl_gp.sql` в блоке DDL для Gre
|
|||||||
|
|
||||||
Дальнейшая модель DWH (слои ODS/DDS/DM, факт/измерения, SCD) должна быть спроектирована студентом по статье о моделировании данных, используя `stg.bookings` как входной слой.
|
Дальнейшая модель DWH (слои ODS/DDS/DM, факт/измерения, SCD) должна быть спроектирована студентом по статье о моделировании данных, используя `stg.bookings` как входной слой.
|
||||||
|
|
||||||
|
## 6. Требования к читаемости и комментариям
|
||||||
|
|
||||||
|
- DAG’и `bookings_stg_ddl` и `bookings_to_gp_stage` — это учебный материал для менти.
|
||||||
|
- В коде DAG’ов должны быть:
|
||||||
|
- понятные docstring на русском у всех функций и DAG;
|
||||||
|
- короткие комментарии рядом с нетривиальной логикой (особенно вокруг инкремента и идемпотентности);
|
||||||
|
- говорящие `task_id` и названия функций, отражающие их роль в процессе.
|
||||||
|
- Цель: чтобы по одному только коду DAG студент мог восстановить архитектуру процесса и сопоставить её с теорией из статьи про моделирование DWH.
|
||||||
|
|||||||
Reference in New Issue
Block a user