- Зачем:
- студент реализует 9 объектов DWH без автоматической обратной связи;
- ошибки (NULL в PK, дубли BK, сломанный SCD2) обнаруживались только на слое DM
через 2–3 слоя, где отладка многократно сложнее.
- Что:
- создан DAG bookings_validate.py — три параллельных TaskGroup (validate_ods,
validate_dds, validate_dm), schedule=None (только ручной trigger);
- создано 17 SQL-скриптов в sql/validate/: ODS BK coverage vs STG-батч,
согласованность _load_id между ods.airplanes и ods.seats, дубли BK, NULL в PK,
покрытие ODS→DDS для измерений (dim_routes требует открытой версии valid_to IS NULL),
SCD2 активный тест backup→mutate→student_load→check(5 инвариантов)→restore
(trigger_rule=all_done), проверка «дыр» между SCD2-версиями,
exists-чеки для 4 DM-витрин;
- добавлен класс TestBookingsValidate (5 smoke-тестов) в tests/test_dags_smoke.py.
- Проверка:
- make test — 4 passed, 14 skipped (DAG-тесты скипаются без Airflow, норма);
- ручной trigger bookings_validate в Airflow UI на solution-ветке — все таски зелёные.
151 lines
6.0 KiB
Python
151 lines
6.0 KiB
Python
"""
|
|
Валидационный DAG: самопроверка студенческих заданий.
|
|
|
|
Запускается вручную в Airflow UI после реализации заданий.
|
|
Таски сгруппированы по слоям — студент видит, где именно проблема.
|
|
|
|
Структура:
|
|
validate_ods — проверки ODS: rowcount, дубли BK, NULL в PK
|
|
validate_dds — проверки DDS: SCD1-измерения, SCD2 dim_routes (активный тест!)
|
|
validate_dm — проверки DM-витрин: не пусты, нет NULL в ключах
|
|
|
|
Все три группы запускаются параллельно — инкрементальная обратная связь:
|
|
студент может проверить только ODS, пока DDS/DM ещё не реализованы.
|
|
"""
|
|
|
|
from datetime import timedelta
|
|
from logging import getLogger
|
|
|
|
import pendulum
|
|
from airflow.providers.postgres.operators.postgres import PostgresOperator
|
|
from airflow.utils.task_group import TaskGroup
|
|
|
|
from airflow import DAG
|
|
|
|
GREENPLUM_CONN_ID = "greenplum_conn"
|
|
log = getLogger(__name__)
|
|
|
|
default_args = {
|
|
"owner": "airflow",
|
|
"retries": 0, # Без ретраев — студент должен увидеть ошибку сразу
|
|
"retry_delay": timedelta(seconds=10),
|
|
}
|
|
|
|
with DAG(
|
|
dag_id="bookings_validate",
|
|
start_date=pendulum.datetime(2017, 1, 1, tz="UTC"),
|
|
schedule=None, # Только ручной запуск
|
|
catchup=False,
|
|
max_active_runs=1,
|
|
template_searchpath="/sql",
|
|
default_args=default_args,
|
|
tags=["demo", "bookings", "greenplum", "validate"],
|
|
description="Валидация студенческих заданий: ODS, DDS, DM",
|
|
) as dag:
|
|
|
|
with TaskGroup("validate_ods") as validate_ods:
|
|
check_ods_airplanes = PostgresOperator(
|
|
task_id="check_ods_airplanes_rowcount",
|
|
postgres_conn_id=GREENPLUM_CONN_ID,
|
|
sql="validate/ods_airplanes_rowcount.sql",
|
|
)
|
|
check_ods_seats = PostgresOperator(
|
|
task_id="check_ods_seats_rowcount",
|
|
postgres_conn_id=GREENPLUM_CONN_ID,
|
|
sql="validate/ods_seats_rowcount.sql",
|
|
)
|
|
check_ods_dup_bk = PostgresOperator(
|
|
task_id="check_ods_no_dup_bk",
|
|
postgres_conn_id=GREENPLUM_CONN_ID,
|
|
sql="validate/ods_no_dup_bk.sql",
|
|
)
|
|
check_ods_pks = PostgresOperator(
|
|
task_id="check_ods_no_null_pks",
|
|
postgres_conn_id=GREENPLUM_CONN_ID,
|
|
sql="validate/ods_no_null_pks.sql",
|
|
)
|
|
|
|
with TaskGroup("validate_dds") as validate_dds:
|
|
check_dim_airplanes = PostgresOperator(
|
|
task_id="check_dim_airplanes_exists",
|
|
postgres_conn_id=GREENPLUM_CONN_ID,
|
|
sql="validate/dim_airplanes_exists.sql",
|
|
)
|
|
check_dim_passengers = PostgresOperator(
|
|
task_id="check_dim_passengers_exists",
|
|
postgres_conn_id=GREENPLUM_CONN_ID,
|
|
sql="validate/dim_passengers_exists.sql",
|
|
)
|
|
check_dim_passengers_dup = PostgresOperator(
|
|
task_id="check_dim_passengers_no_dup_bk",
|
|
postgres_conn_id=GREENPLUM_CONN_ID,
|
|
sql="validate/dim_passengers_no_dup_bk.sql",
|
|
)
|
|
check_dim_routes = PostgresOperator(
|
|
task_id="check_dim_routes_exists",
|
|
postgres_conn_id=GREENPLUM_CONN_ID,
|
|
sql="validate/dim_routes_exists.sql",
|
|
)
|
|
|
|
# SCD2 активный тест: цепочка backup → mutate → load → check → restore
|
|
scd2_backup = PostgresOperator(
|
|
task_id="scd2_backup",
|
|
postgres_conn_id=GREENPLUM_CONN_ID,
|
|
sql="validate/dim_routes_scd2_backup.sql",
|
|
)
|
|
scd2_mutate = PostgresOperator(
|
|
task_id="scd2_mutate",
|
|
postgres_conn_id=GREENPLUM_CONN_ID,
|
|
sql="validate/dim_routes_scd2_mutate.sql",
|
|
)
|
|
scd2_run_load = PostgresOperator(
|
|
task_id="scd2_run_student_load",
|
|
postgres_conn_id=GREENPLUM_CONN_ID,
|
|
sql="dds/dim_routes_load.sql", # студенческий load-скрипт!
|
|
)
|
|
scd2_check = PostgresOperator(
|
|
task_id="scd2_check",
|
|
postgres_conn_id=GREENPLUM_CONN_ID,
|
|
sql="validate/dim_routes_scd2_check.sql",
|
|
)
|
|
scd2_restore = PostgresOperator(
|
|
task_id="scd2_restore",
|
|
postgres_conn_id=GREENPLUM_CONN_ID,
|
|
sql="validate/dim_routes_scd2_restore.sql",
|
|
trigger_rule="all_done", # Откат ВСЕГДА, даже если check упал
|
|
)
|
|
|
|
check_dim_routes_gaps = PostgresOperator(
|
|
task_id="check_dim_routes_no_gaps",
|
|
postgres_conn_id=GREENPLUM_CONN_ID,
|
|
sql="validate/dim_routes_no_gaps.sql",
|
|
)
|
|
|
|
# SCD2 цепочка
|
|
scd2_backup >> scd2_mutate >> scd2_run_load >> scd2_check >> scd2_restore
|
|
|
|
# no_gaps запускается после restore (на чистых данных)
|
|
scd2_restore >> check_dim_routes_gaps
|
|
|
|
with TaskGroup("validate_dm") as validate_dm:
|
|
check_airport_traffic = PostgresOperator(
|
|
task_id="check_airport_traffic_exists",
|
|
postgres_conn_id=GREENPLUM_CONN_ID,
|
|
sql="validate/airport_traffic_exists.sql",
|
|
)
|
|
check_route_performance = PostgresOperator(
|
|
task_id="check_route_performance_exists",
|
|
postgres_conn_id=GREENPLUM_CONN_ID,
|
|
sql="validate/route_performance_exists.sql",
|
|
)
|
|
check_monthly_overview = PostgresOperator(
|
|
task_id="check_monthly_overview_exists",
|
|
postgres_conn_id=GREENPLUM_CONN_ID,
|
|
sql="validate/monthly_overview_exists.sql",
|
|
)
|
|
check_passenger_loyalty = PostgresOperator(
|
|
task_id="check_passenger_loyalty_exists",
|
|
postgres_conn_id=GREENPLUM_CONN_ID,
|
|
sql="validate/passenger_loyalty_exists.sql",
|
|
)
|