From 623d5861c1b32bc16690ac83b53d4d0b4e032bf3 Mon Sep 17 00:00:00 2001 From: Dmitry Dementev Date: Mon, 28 Sep 2026 08:00:01 -0400 Subject: [PATCH 1/6] =?UTF-8?q?docs(practice):=20=D0=B4=D0=BE=D0=B1=D0=B0?= =?UTF-8?q?=D0=B2=D0=B8=D1=82=D1=8C=20=D0=BF=D0=BB=D0=B0=D0=BD=20=D0=BE?= =?UTF-8?q?=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BF?= =?UTF-8?q?=D1=80=D0=B0=D0=BA=D1=82=D0=B8=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 3 + airflow-docker/README.md | 4 + .../2026-09-28-practice-improvement-plan.md | 108 ++++++++++++++++++ 3 files changed, 115 insertions(+) create mode 100644 docs/specs/2026-09-28-practice-improvement-plan.md diff --git a/AGENTS.md b/AGENTS.md index 994938c..5dea9cd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -11,6 +11,9 @@ This repository contains an educational manual for Apache Airflow with runnable - `data/`: sample input data for exercises. - Note: `airflow-docker/AGENTS.md` adds extra, folder-specific guidance and takes precedence there. +## Practice Improvement Work +При переработке практических заданий, учебного поведения DAG или маршрута курса сначала прочитайте [план обновления практики](docs/specs/2026-09-28-practice-improvement-plan.md): согласованные решения, текущий этап и критерии его завершения. + ## Build, Test, and Development Commands Prerequisite: Docker + Docker Compose. - Build images: `cd airflow-docker && docker-compose build` diff --git a/airflow-docker/README.md b/airflow-docker/README.md index a08140f..444fc85 100644 --- a/airflow-docker/README.md +++ b/airflow-docker/README.md @@ -271,3 +271,7 @@ docker-compose up -d --- **Примечание**: Этот стенд предназначен исключительно для учебных целей. Для production использования требуется дополнительная настройка безопасности. + +## Для авторов курса + +[План обновления практики](../docs/specs/2026-09-28-practice-improvement-plan.md) — согласованный подход, найденные несогласованности, порядок доработки и проверки заданий. diff --git a/docs/specs/2026-09-28-practice-improvement-plan.md b/docs/specs/2026-09-28-practice-improvement-plan.md new file mode 100644 index 0000000..7bdbd41 --- /dev/null +++ b/docs/specs/2026-09-28-practice-improvement-plan.md @@ -0,0 +1,108 @@ +# План обновления практики по Airflow + +Дата: 2026-09-28. Подход согласован с автором курса. Реализация не начата; продолжение — первый незакрытый пункт в разделе «Порядок работы». + +Этот документ сохраняет решения и порядок дальнейшей работы. Текущие задания находятся в [educational-tasks.md](../../airflow-docker/educational-tasks.md), запуск стенда описан в [README](../../airflow-docker/README.md). + +## Цель и границы + +Стенд знакомит студентов с Apache Airflow и входит в [программу Data Engineering](https://de.dementev.space/). Перед ним студенты проходят Python, pandas, SQL и Docker. Самостоятельное построение цельных процессов обработки данных относится к последующим курсовым работам. + +Результат этого блока: студент умеет запустить готовый DAG, найти его граф и логи, внести небольшое изменение по образцу и объяснить наблюдаемое поведение. Практика остаётся набором небольших экспериментов с инструментом. + +Обновление сохраняет ознакомительный формат и версию Airflow 2.9.x. Основной маршрут выполняется на существующем стенде; внешние интеграции относятся к заданиям по желанию. Учебная имитация отдельных операций допустима, если описание и логи честно называют её имитацией. + +## Порядок работы + +Перед продолжением сверить чек-лист с текущими файлами и историей Git. Прочитать согласованные решения ниже, затем выполнить первый незавершённый этап. Локальные решения в этих границах принимать и записывать в рабочей таблице; противоречия с согласованной целью выносить на обсуждение. + +- [x] Зафиксировать подход и находки в этом документе; добавить ссылки из README стенда и AGENTS.md. +- [ ] **1. Разобрать все исходные задания.** Создать здесь рабочую таблицу с колонками: исходное задание (DAG/раздел и номер), изучаемая возможность, исходное состояние и зависимости, решение с обоснованием, место в будущем маршруте, наблюдаемый результат. Решения: оставить основным, разделить, перенести в дополнительные или переформулировать. Исходный набор — 25 заданий в `educational-tasks.md` на коммите `13e3102`; после переработки число упражнений может измениться. **Готово:** учтены 25 из 25 исходных заданий, у каждой части разделённого задания определена обязательность, а все зависимости основного маршрута обеспечены без выполнения дополнительных упражнений. Сопоставление с главами учебника объясняет выбор тем; новые темы добавляются только при выявленной необходимости для цели блока. +- [ ] **2. Проверить и исправить несогласованности.** Сверить каждую строку раздела «Найденные несогласованности и препятствия» с актуальным кодом. Для каждой записать итог: исправлено, не подтвердилось с объяснением или оставлено как явно описанный учебный сценарий. **Готово:** все находки разобраны; подтверждённые ошибки устранены, изменённое поведение проверено на версии стенда. Невыполненная проверка остаётся открытой. +- [ ] **3. Переработать показательный блок обработки ошибок.** Подготовить основной управляемый эксперимент с повтором, наблюдаемые результаты и отдельное усложнение по желанию. **Готово:** исходное состояние и возврат к нему описаны; воспроизведены успех после повтора и окончательная ошибка; логи, состояния задач и итог DAG Run совпадают с описанием. Этот блок служит образцом объёма и оформления остальных упражнений. +- [ ] **4. Переработать остальные блоки.** Применить выбранное распределение и образец к остальным заданиям. **Готово:** каждая строка рабочей таблицы ссылается на итоговое упражнение или содержит причину объединения; у каждого упражнения есть обязательность и проверяемый результат; подготовка и восстановление общих данных описаны там, где нужны. +- [ ] **5. Согласовать навигацию.** Обновить маршрут и оценку прогресса в `educational-tasks.md`, корневом README и README стенда. Сверить `dag-specifications.md` с итоговым содержанием. **Готово:** ссылки ведут к актуальным упражнениям; документы согласованно описывают порядок и условия завершения; устаревшие прототипы и необоснованные оценки времени удалены или исправлены. +- [ ] **6. Пройти маршрут двумя способами.** Выполнить только основную практику, затем дополнительные упражнения с переходами обратно к основной. **Готово:** обе последовательности пройдены в изолированном окружении; указанные результаты подтверждены в данных и UI; сохранены условия запусков, результаты и ограничения по правилам раздела «Как проверять результат доработки». + +По мере выполнения обновлять рабочую таблицу и чек-лист. У закрытого этапа указывать ссылку на коммит или запись о проверке. Если часть проверок недоступна, записать причину и продолжать независимую работу; соответствующий этап остаётся незавершённым. + +## Согласованные решения + +1. **Последовательность внутри блока.** Студент запускает исходный пример, меняет одну существенную настройку или часть графа и наблюдает результат. Изменения основных заданий внутри блока сохраняются. +2. **Ясное начало каждого блока.** Указаны исходный DAG, необходимые файлы, таблицы и настройки, а также изменения, которые остаются от предыдущей работы. Блок определяется учебным смыслом: загрузка CSV и последующая DQ-проверка могут объединять два DAG. +3. **Основная практика и задания по желанию.** Обязательность отделена от сложности. Дополнительные задания расположены в конце своего блока; их результаты не нужны для продолжения основной практики. Пропуск дополнительного задания не препятствует завершению блока. +4. **Разделение смешанных заданий.** Если одно упражнение объединяет знакомство с возможностью Airflow и значительное усложнение, оно разделяется. Например, TaskGroup и настройка настоящей отправки писем не должны быть одним обязательным заданием. +5. **Наблюдаемый результат.** У каждого задания есть раздел «Что должно получиться»: конкретное поведение в UI, сообщение в логах или результат в файле/таблице. Преднамеренная ошибка может быть правильным результатом упражнения. +6. **Управляемые учебные сценарии.** Основные примеры ветвления и повторных попыток позволяют гарантированно увидеть нужный случай. Случайность допустима в данных, если она не мешает наблюдать изучаемое поведение и проверять результат. +7. **Понятное завершение.** Основная практика даёт достаточное знакомство для перехода дальше по программе. Выполнение всех дополнительных упражнений не является скрытым условием прохождения курса. + +Дополнительное задание, меняющее общую таблицу или настройку, должно описывать возврат к состоянию, необходимому следующему блоку. Перенос задания в конец раздела сам по себе эту проблему не решает. Восстановление касается только объектов упражнения, без общего сброса стенда или чужих изменений. + +## Как оформлять задания + +Сохраняется один основной документ для студентов — `educational-tasks.md`. Не нужно дублировать инструкции в отдельном сборнике дополнительных упражнений. + +В начале блока: связь с главами учебника, необходимые исходные условия и короткий запуск готового примера. У задания: + +- пометка «Основная практика» или «По желанию»; сложность указывается отдельно; +- конкретное изменение, позволяющее познакомиться с одной возможностью Airflow; +- подсказка по необходимости; +- «Что должно получиться» с указанием места проверки; +- для экспериментов с ошибками — способ вызвать ошибку и вернуть нормальное поведение; +- при необходимости один короткий вопрос о причине наблюдаемого результата. + +Для ветвления и обработки ошибок проверяются статусы отдельных задач и всего DAG Run. Формулировка «всё зелёное» не подходит для сценария с ожидаемыми пропусками или ошибками. Проверка результата не должна требовать ожидания недельного расписания или неограниченного числа случайных запусков. + +## Предварительное распределение содержания + +Это начальная гипотеза для этапа 1. Окончательное распределение определяется рабочей таблицей после проверки зависимостей; правила прохождения находятся в разделе «Согласованные решения». + +| Блок | Основная практика | Возможное усложнение по желанию | +|---|---|---| +| Первый DAG | Запуск, граф, логи, добавление задачи по образцу, зависимости, знакомство с расписанием | Дополнительные эксперименты с ограничениями запусков | +| SQL и Connections | Выполнение SQL, смена подключения, наблюдение результата и ошибки подключения | Дополнительные отчёты и расширение модели данных | +| Файлы и преобразования | Проследить путь файла между задачами, изменить простое преобразование или проверку | Новые поля, графики, отчёты, обработка по отделам | +| CSV → PostgreSQL → DQ | Проследить загрузку, найти путь к файлу в XCom, добавить проверку, увидеть корректные и ошибочные данные | Расширение схемы с согласованным изменением загрузчика и DQ, общая сводка проверок | +| Пример ETL | Прочитать граф с параллельными задачами, найти промежуточные и итоговые результаты | Новый источник, расширенная аналитика, архивирование | +| Ветвление и Variables | Управляемо выбрать ветку, увидеть `skipped`, понять слияние веток | Третья ветка и полноценная обработка разных форматов | +| Ошибки и повторы | Вызвать ошибку, увидеть повтор и успешное завершение, найти причину в логах | Экспоненциальная задержка, разные исключения, эскалация | +| Пулы, TaskGroup и оповещения | Короткие отдельные эксперименты: ожидание слотов, группировка существующих задач, сообщение обработчика ошибки | Настоящая отправка писем и более сложные сценарии | + +Чтение и изменение XCom не должно зависеть от выполнения дополнительного задания по резервному копированию. Для знакомства с пулами достаточно готовых задач с наблюдаемой длительностью; написание механизма резервного копирования не является целью такого эксперимента. + +## Найденные несогласованности и препятствия + +Ниже зафиксированы результаты чтения кода и заданий. Это не отчёт о полном прохождении стенда: Docker, DAG и UI в рамках обсуждения не запускались. Семантика статусов и зависимостей сверялась с документацией Airflow 2.9.2. + +| Место | Наблюдение | Направление исправления | +|---|---|---| +| [SQL-задание](../../airflow-docker/educational-tasks.md), [sql_basic_dag.py](../../airflow-docker/dags/sql_basic_dag.py) | Совет заменить завершающий `TRUNCATE` на `DROP` не готовит уже существующую таблицу к вставке в новые колонки: выполнение может упасть раньше завершающей задачи. | Описать подготовку учебной таблицы до запуска и момент проверки данных до очистки. Согласовать имя задачи очистки с её действием. | +| [csv_to_postgres.py](../../airflow-docker/dags/csv_to_postgres.py) | Подсказка про добавление `status` упоминает генератор, DDL и COPY, но не итоговый `INSERT … SELECT`. | Провести поле через все этапы и проверять его значения в целевой таблице. | +| [csv_to_postgres_dq.py](../../airflow-docker/dags/csv_to_postgres_dq.py) | Строгая проверка схемы ожидает исходные четыре колонки и конфликтует с предыдущим расширением таблицы. | Согласовать порядок упражнений и исходное состояние DQ. Расширение схемы можно выполнять после знакомства с базовой загрузкой и проверками. | +| DQ-задание про Trigger Rules | После ошибки при стандартном `all_success` обещан `skipped`, хотя в описанном сценарии ожидается `upstream_failed`. | Исправить ожидаемые состояния и проверить их на стенде. | +| [error_handling_dag.py](../../airflow-docker/dags/error_handling_dag.py) | `chain` связывает два списка попарно: обработчик успеха зависит от одной рабочей задачи, обработчик ошибки — от другой. Комментарии описывают общий результат. | Согласовать граф с учебным сценарием; проверить варианты успеха и отказа каждой рабочей задачи, а также итог DAG Run. | +| DQ-сводка и [пример алертинга](../../09%20-%20Пулы,%20XCom,%20TaskGroup%20и%20алертинг%20в%20Airflow.md) | В исходном DQ используется `all_success`; риск возникает при предлагаемом в упражнении переходе на `all_done`. Успешная единственная завершающая задача с этим правилом может сделать DAG Run успешным при ошибке внутри. Пример письма также безусловно сообщает об успехе. | Явно определить желаемый итог и проверить согласованность статусов с сообщением. Отправку уведомления и успешность обработки рассматривать отдельно. | +| [data_processing_dag.py](../../airflow-docker/dags/data_processing_dag.py) | Загрузка в БД имитируется, но лог сообщает о загрузке. Transform читает исходные файлы, а не результаты Extract. | Честно назвать имитацию и согласовать показанный путь данных с фактическими входами задач. Полноценную загрузку ради этого примера добавлять необязательно. | +| Задание про пулы | Для пула из двух слотов и двух задач с потреблением два и один предлагается наблюдать «не более двух задач». Это не показывает существенное ограничение: эти две задачи одновременно не помещаются. | Описать ожидаемое ожидание одной задачи и дать достаточную длительность для наблюдения в UI. | +| Ветвление, повторы и дополнительные упражнения | Случайный выбор мешает гарантированно наблюдать нужный случай. Сравнение файлов двух взаимоисключающих веток не определяет, от каких запусков брать результаты. Некоторые задания требуют полей, добавленных раньше. | Задать управляемые сценарии, понятные входы и зависимости; исключить случайное использование результатов старого запуска. | + +Источники для технической сверки: [chain](https://airflow.apache.org/docs/apache-airflow/2.9.2/_api/airflow/models/baseoperator/index.html#airflow.models.baseoperator.chain), [статусы задач](https://airflow.apache.org/docs/apache-airflow/2.9.2/core-concepts/tasks.html#task-instances), [итог DAG Run](https://airflow.apache.org/docs/apache-airflow/2.9.2/core-concepts/dag-run.html#dag-run-status), [пулы](https://airflow.apache.org/docs/apache-airflow/2.9.2/administration-and-deployment/pools.html). + +## Как проверять результат доработки + +Результаты проверок сохранять в этом документе рядом с закрываемым этапом: проверенная версия/коммит, сценарий и исходное состояние, ожидаемый и фактический результат, способ проверки. Для длинных логов или снимков экрана достаточно ссылки на доступный артефакт. Версию Airflow сверять с фактическим окружением, а не только с тегом образа в Dockerfile. + +Проверки разделяются по уровню доказательств: + +- **Документы и исходники:** все 25 исходных заданий учтены, ссылки работают, имена файлов/задач/таблиц совпадают, обязательность и зависимости согласованы. +- **Стенд:** подтверждены версия Airflow и загрузка DAG без ошибок импорта; выполнены затронутые сценарии и проверены файлы/таблицы. Проверка синтаксиса Python не заменяет выполнение задач. +- **UI:** студент действительно может найти указанные состояния, логи, XCom, граф и настройки; показанные результаты совпадают с текстом. +- **Прохождение:** отдельно проверены основной маршрут и переходы после дополнительных заданий. Не выполненные проверки так и отмечаются; предполагаемое время не выдаётся за измеренное на студентах. + +Эксперименты с изменением данных выполняются в изолированном учебном окружении. Проверка не должна затрагивать другие работающие стенды или несвязанные изменения пользователя. + +## Почему выбран этот формат + +Одни метки сложности не разделяют смешанные упражнения и не объясняют условия продолжения. Полностью независимые задания увеличивают повторную подготовку. Отдельный сборник усложнений разрывает связь с исходным примером. Демонстрации без изменения кода дают меньше практики, а самостоятельный большой пайплайн дублирует цель последующих курсовых. + +Выбранный формат сохраняет практику изменения кода и позволяет переходить к следующему блоку после знакомства с нужными возможностями Airflow. -- 2.54.0 From cf1b770a65c952b178708326cadeef36b40f4d3e Mon Sep 17 00:00:00 2001 From: Dmitry Dementev Date: Mon, 28 Sep 2026 08:46:50 -0400 Subject: [PATCH 2/6] =?UTF-8?q?fix(dags):=20=D1=81=D0=B4=D0=B5=D0=BB=D0=B0?= =?UTF-8?q?=D1=82=D1=8C=20=D1=83=D1=87=D0=B5=D0=B1=D0=BD=D1=8B=D0=B5=20?= =?UTF-8?q?=D1=81=D1=86=D0=B5=D0=BD=D0=B0=D1=80=D0=B8=D0=B8=20=D0=B2=D0=BE?= =?UTF-8?q?=D1=81=D0=BF=D1=80=D0=BE=D0=B8=D0=B7=D0=B2=D0=BE=D0=B4=D0=B8?= =?UTF-8?q?=D0=BC=D1=8B=D0=BC=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Управляемые ветки и ошибки, зависимости общих обработчиков, сохранение SQL-результата и фактическое чтение результатов Extract. Добавлен пример пулов и XCom; DQ-сводка сохраняет отказ при all_done. Проверено на Airflow 2.9.2: импорт и запуск девяти исходных DAG, выбор веток, повтор и окончательные отказы обеих рабочих задач, отрицательный DQ-сценарий и восстановление, фильтрация Extract и ожидание слотов. --- airflow-docker/dags/branching_dag.py | 145 ++++++-------- airflow-docker/dags/csv_to_postgres_dq.py | 26 ++- airflow-docker/dags/data_processing_dag.py | 17 +- airflow-docker/dags/error_handling_dag.py | 181 ++++++++---------- .../dags/resource_management_dag.py | 61 ++++++ airflow-docker/dags/sql_basic_dag.py | 6 +- 6 files changed, 225 insertions(+), 211 deletions(-) create mode 100644 airflow-docker/dags/resource_management_dag.py diff --git a/airflow-docker/dags/branching_dag.py b/airflow-docker/dags/branching_dag.py index 3b7b2e2..4be49bf 100644 --- a/airflow-docker/dags/branching_dag.py +++ b/airflow-docker/dags/branching_dag.py @@ -1,103 +1,70 @@ +"""Выбор ветки через Variable branch_format: csv или json. + +Обработка файлов имитируется. В основной практике изучаются граф и статусы. """ -DAG для демонстрации условного выполнения задач в Airflow -Уровень: Продвинутый -""" -from datetime import datetime, timedelta +import logging +from datetime import datetime + from airflow import DAG -from airflow.operators.python import PythonOperator, BranchPythonOperator -from airflow.operators.dummy import DummyOperator -import random +from airflow.models import Variable +from airflow.operators.empty import EmptyOperator +from airflow.operators.python import BranchPythonOperator, PythonOperator -# Определение DAG -default_args = { - 'owner': 'student', - 'depends_on_past': False, - 'start_date': datetime(2023, 1, 1), - 'email_on_failure': False, - 'email_on_retry': False, - 'retries': 1, - 'retry_delay': timedelta(minutes=5) -} -dag = DAG( - 'branching_dag', - default_args=default_args, - description='DAG для изучения условного выполнения задач в Airflow', - schedule_interval=None, - catchup=False, - tags=['educational', 'branching', 'advanced'] -) +def choose_format(): + """Возвращает task_id выбранной ветки.""" + file_format = Variable.get("branch_format", default_var="csv") + branches = {"csv": "process_csv_branch", "json": "process_json_branch"} + if file_format not in branches: + raise ValueError(f"branch_format должен быть csv или json, получено: {file_format}") + logging.info("Выбран формат: %s", file_format) + return branches[file_format] -def check_data_quality(): - """Проверка качества данных - случайным образом определяет, какие данные использовать""" - # В реальном сценарии здесь будет проверка качества данных - # Для учебных целей просто случайное решение - quality_score = random.random() # случайное число от 0 до 1 - - if quality_score > 0.5: - print(f"Качество данных хорошее (оценка: {quality_score:.2f}), используем CSV") - return 'process_csv_branch' - else: - print(f"Качество данных требует внимания (оценка: {quality_score:.2f}), используем JSON") - return 'process_json_branch' def process_csv_data(): - """Обработка CSV данных""" - print("Обработка CSV файла...") - # Здесь будет логика обработки CSV файла - return "CSV данные обработаны" + """Имитирует обработку CSV.""" + logging.info("Учебная CSV-ветка выполнена; файлы не читались") + return "csv" + def process_json_data(): - """Обработка JSON данных""" - print("Обработка JSON файла...") - # Здесь будет логика обработки JSON файла - return "JSON данные обработаны" + """Имитирует обработку JSON.""" + logging.info("Учебная JSON-ветка выполнена; файлы не читались") + return "json" -def merge_results(): - """Объединение результатов из разных веток""" - print("Объединение результатов из разных веток...") - return "Результаты объединены" -# Определение задач -start_task = DummyOperator( - task_id='start_task', - dag=dag -) +def merge_results(ti): + """Читает результат выполненной ветки текущего запуска.""" + results = ti.xcom_pull(task_ids=["process_csv_branch", "process_json_branch"]) + selected = [value for value in results if value is not None] + logging.info("Результат выбранной ветки: %s", selected) + return selected -check_quality_task = BranchPythonOperator( - task_id='check_data_quality', - python_callable=check_data_quality, - dag=dag -) -process_csv_task = PythonOperator( - task_id='process_csv_branch', - python_callable=process_csv_data, - dag=dag -) +with DAG( + "branching_dag", + start_date=datetime(2023, 1, 1), + schedule=None, + catchup=False, + default_args={"owner": "student", "retries": 0}, + tags=["educational", "branching"], +) as dag: + start_task = EmptyOperator(task_id="start_task") + choose_task = BranchPythonOperator( + task_id="choose_format", python_callable=choose_format, + ) + process_csv_task = PythonOperator( + task_id="process_csv_branch", python_callable=process_csv_data, + ) + process_json_task = PythonOperator( + task_id="process_json_branch", python_callable=process_json_data, + ) + merge_task = PythonOperator( + task_id="merge_results", + python_callable=merge_results, + trigger_rule="none_failed_min_one_success", + ) + end_task = EmptyOperator(task_id="end_task") -process_json_task = PythonOperator( - task_id='process_json_branch', - python_callable=process_json_data, - dag=dag -) - -merge_task = PythonOperator( - task_id='merge_results', - python_callable=merge_results, - trigger_rule='none_failed_or_skipped', # Выполняется, когда одна из веток завершена - dag=dag -) - -end_task = DummyOperator( - task_id='end_task', - dag=dag -) - -# Установка зависимостей -# BranchPythonOperator автоматически пропускает (skips) задачи в невыбранной ветке -# Например, если check_data_quality возвращает 'process_csv_branch', -# то задача process_json_branch будет пропущена (статус skipped) -start_task >> check_quality_task -check_quality_task >> [process_csv_task, process_json_task] -[process_csv_task, process_json_task] >> merge_task >> end_task \ No newline at end of file + start_task >> choose_task >> [process_csv_task, process_json_task] + [process_csv_task, process_json_task] >> merge_task >> end_task diff --git a/airflow-docker/dags/csv_to_postgres_dq.py b/airflow-docker/dags/csv_to_postgres_dq.py index 83822ed..30ee234 100644 --- a/airflow-docker/dags/csv_to_postgres_dq.py +++ b/airflow-docker/dags/csv_to_postgres_dq.py @@ -2,7 +2,7 @@ DAG для проверки качества данных (Data Quality) после загрузки в PostgreSQL Уровень: Средний -Этот DAG запускается после csv_to_postgres и проверяет таблицу public.orders: +Запустите этот DAG вручную после csv_to_postgres. Он проверяет public.orders: 1. Существование таблицы 2. Соответствие схемы (колонки и типы данных) 3. Наличие данных (таблица не пуста) @@ -14,12 +14,11 @@ DAG для проверки качества данных (Data Quality) пос from __future__ import annotations import logging -from datetime import datetime, timedelta - -from airflow.operators.python import PythonOperator -from airflow.providers.postgres.hooks.postgres import PostgresHook +from datetime import datetime from airflow import DAG +from airflow.operators.python import PythonOperator +from airflow.providers.postgres.hooks.postgres import PostgresHook POSTGRES_CONN_ID = "postgres_training" @@ -106,13 +105,20 @@ def _check_no_duplicates(): conn.close() -def _log_dq_summary(): - """Логирует итоговую сводку по качеству данных.""" - logging.info("Все проверки качества данных пройдены успешно!") - logging.info("Качество данных в таблице orders соответствует требованиям.") +def _log_dq_summary(dag_run, ti): + """Проверяет итог, в том числе при учебном переходе на all_done.""" + states = { + task.task_id: task.state + for task in dag_run.get_task_instances() + if task.task_id != ti.task_id + } + logging.info("Результаты проверок: %s", states) + if any(state != "success" for state in states.values()): + raise ValueError("Не все проверки качества данных прошли успешно") + logging.info("Все проверки качества данных пройдены") -default_args = {"owner": "airflow", "retries": 1, "retry_delay": timedelta(seconds=30)} +default_args = {"owner": "student", "retries": 0} with DAG( dag_id="csv_to_postgres_dq", diff --git a/airflow-docker/dags/data_processing_dag.py b/airflow-docker/dags/data_processing_dag.py index 4086e84..a2dbec8 100644 --- a/airflow-docker/dags/data_processing_dag.py +++ b/airflow-docker/dags/data_processing_dag.py @@ -6,7 +6,7 @@ DAG для демонстрации ETL процессов в Airflow 1. Создание тестовых данных (клиенты + заказы) в CSV 2. Параллельное извлечение (Extract) клиентов и заказов 3. Трансформация (Transform) — объединение таблиц, вычисляемые поля -4. Загрузка (Load) — подготовка SQL для вставки в БД +4. Имитация загрузки: число подготовленных строк и пример DDL в логах 5. Генерация текстового отчёта со статистикой Результат: файлы в /opt/airflow/data/output/ (extracted_*, transformed_data.csv, report.txt). @@ -14,7 +14,6 @@ DAG для демонстрации ETL процессов в Airflow from datetime import datetime, timedelta from airflow import DAG from airflow.operators.python import PythonOperator -from airflow.providers.postgres.operators.postgres import PostgresOperator # Определение DAG default_args = { @@ -85,8 +84,8 @@ def extract_orders(): def transform_data(): """Преобразование данных - объединение клиентов и заказов""" import pandas as pd - customers_df = pd.read_csv('/opt/airflow/data/input/customers.csv') - orders_df = pd.read_csv('/opt/airflow/data/input/orders.csv') + customers_df = pd.read_csv('/opt/airflow/data/output/extracted_customers.csv') + orders_df = pd.read_csv('/opt/airflow/data/output/extracted_orders.csv') # Объединяем данные merged_df = pd.merge(orders_df, customers_df, on='customer_id', how='left') @@ -101,14 +100,14 @@ def transform_data(): return f"Преобразованы {len(merged_df)} записей" -def load_to_database(): +def simulate_load(): """Загрузка данных в базу данных (симуляция)""" import pandas as pd df = pd.read_csv('/opt/airflow/data/output/transformed_data.csv') # В реальном сценарии здесь был бы код для загрузки в базу данных # Для учебных целей просто логируем - print(f"Загружено в базу данных: {len(df)} записей") + print(f"Имитация загрузки: подготовлено {len(df)} записей. Записи в БД нет.") # Создаем SQL для создания таблицы (в реальном сценарии) create_table_sql = """ @@ -185,8 +184,8 @@ transform_task = PythonOperator( ) load_task = PythonOperator( - task_id='load_to_database', - python_callable=load_to_database, + task_id='simulate_load', + python_callable=simulate_load, dag=dag ) @@ -197,4 +196,4 @@ report_task = PythonOperator( ) # Установка зависимостей -create_data_task >> [extract_customers_task, extract_orders_task] >> transform_task >> load_task >> report_task \ No newline at end of file +create_data_task >> [extract_customers_task, extract_orders_task] >> transform_task >> load_task >> report_task diff --git a/airflow-docker/dags/error_handling_dag.py b/airflow-docker/dags/error_handling_dag.py index 012f978..3fb7b2c 100644 --- a/airflow-docker/dags/error_handling_dag.py +++ b/airflow-docker/dags/error_handling_dag.py @@ -1,108 +1,89 @@ +"""Повторы и обработчики результата. Режим задается Variable error_mode. + +retry_once: вторая задача падает при первой попытке, затем выполняется. +success: обе задачи выполняются сразу. +fail_first / fail_second / fail_both: выбранные задачи падают при каждой попытке. +Меняйте режим после завершения предыдущего запуска. """ -DAG для демонстрации обработки ошибок в Airflow -Уровень: Продвинутый -""" +import logging from datetime import datetime, timedelta + from airflow import DAG +from airflow.models import Variable +from airflow.operators.empty import EmptyOperator from airflow.operators.python import PythonOperator -from airflow.operators.dummy import DummyOperator -from airflow.models.baseoperator import chain -import random -# Определение DAG -default_args = { - 'owner': 'student', - 'depends_on_past': False, - 'start_date': datetime(2023, 1, 1), - 'email_on_failure': False, # Отключаем email уведомления для простоты - 'email_on_retry': False, - 'retries': 3, # Количество попыток при ошибке - 'retry_delay': timedelta(seconds=10) # Задержка между попытками -} -dag = DAG( - 'error_handling_dag', - default_args=default_args, - description='DAG для изучения обработки ошибок в Airflow', - schedule_interval=None, +def get_error_mode(): + """Читает режим во время выполнения задачи.""" + mode = Variable.get("error_mode", default_var="retry_once") + if mode not in {"success", "retry_once", "fail_first", "fail_second", "fail_both"}: + raise ValueError(f"Неизвестный error_mode: {mode}") + return mode + + +def run_first_task(): + """Имитирует первую рабочую задачу.""" + if get_error_mode() in {"fail_first", "fail_both"}: + raise ValueError("Учебная ошибка первой задачи") + logging.info("Первая задача выполнена") + + +def run_retry_task(ti): + """Имитирует временную или постоянную ошибку второй задачи.""" + mode = get_error_mode() + logging.info("Режим: %s; попытка: %s", mode, ti.try_number) + if mode in {"fail_second", "fail_both"}: + raise ValueError("Постоянная учебная ошибка второй задачи") + if mode == "retry_once" and ti.try_number == 1: + raise ValueError("Временная учебная ошибка: следующая попытка выполнится") + logging.info("Вторая задача выполнена") + + +def handle_success(): + """Сообщает об успехе обеих рабочих задач.""" + logging.info("Обе рабочие задачи выполнены") + + +def handle_failure(): + """Сообщает об окончательном отказе хотя бы одной задачи.""" + logging.error("Рабочая задача исчерпала повторы. Откройте ее лог") + + +with DAG( + "error_handling_dag", + start_date=datetime(2023, 1, 1), + schedule=None, catchup=False, - tags=['educational', 'error_handling', 'advanced'] -) - -def unreliable_task(): - """Задача, которая может завершиться с ошибкой""" - # В реальном сценарии это может быть задача, зависящая от внешних факторов - # Для учебных целей случайным образом генерируем ошибку - if random.random() < 0.3: # 30% вероятность ошибки - print("Ошибка: задача не выполнена успешно!") - raise Exception("Случайная ошибка в задаче") - - print("Задача выполнена успешно!") - return "Задача выполнена" - -def success_handler(): - """Обработчик успешного выполнения""" - print("Поздравляем! Все задачи выполнены успешно!") - return "Успешно завершено" - -def failure_handler(): - """Обработчик ошибок""" - print("Одна или несколько задач завершились с ошибкой!") - print("Проверьте логи для получения дополнительной информации") - return "Ошибка обработана" - -def retry_task(): - """Задача с механизмом повторных попыток""" - # Имитируем задачу, которая может завершиться с ошибкой, но со временем исправляется - import time - time.sleep(2) # Имитация работы - - # С вероятностью 50% задача завершится с ошибкой - if random.random() < 0.5: - print("Ошибка в retry_task!") - raise Exception("Ошибка в задаче с повторными попытками") - - print("retry_task выполнена успешно!") - return "retry_task завершена" - -# Определение задач -start_task = DummyOperator( - task_id='start_task', - dag=dag -) - -unreliable_task = PythonOperator( - task_id='unreliable_task', - python_callable=unreliable_task, - dag=dag -) - -retry_task = PythonOperator( - task_id='retry_task', - python_callable=retry_task, - dag=dag -) - -success_handler_task = PythonOperator( - task_id='success_handler', - python_callable=success_handler, - trigger_rule='all_success', # Выполняется только если все предыдущие задачи успешны - dag=dag -) - -failure_handler_task = PythonOperator( - task_id='failure_handler', - python_callable=failure_handler, - trigger_rule='one_failed', # Выполняется если хотя бы одна предыдущая задача завершилась с ошибкой - dag=dag -) - -end_task = DummyOperator( - task_id='end_task', - dag=dag -) - -# Установка зависимостей -# Используем chain, чтобы наглядно показать ученикам построение ветвящихся зависимостей без ручного перечисления операторов. -chain(start_task, [unreliable_task, retry_task], [success_handler_task, failure_handler_task], end_task) + default_args={ + "owner": "student", + "retries": 1, + "retry_delay": timedelta(seconds=10), + }, + tags=["educational", "errors"], +) as dag: + start_task = EmptyOperator(task_id="start_task") + unreliable_task = PythonOperator( + task_id="unreliable_task", python_callable=run_first_task, + ) + retry_task = PythonOperator( + task_id="retry_task", python_callable=run_retry_task, + ) + success_handler_task = PythonOperator( + task_id="success_handler", + python_callable=handle_success, + trigger_rule="all_success", + retries=0, + ) + failure_handler_task = PythonOperator( + task_id="failure_handler", + python_callable=handle_failure, + trigger_rule="one_failed", + retries=0, + ) + start_task >> [unreliable_task, retry_task] + [unreliable_task, retry_task] >> success_handler_task + [unreliable_task, retry_task] >> failure_handler_task + # Оба обработчика завершающие. При отказе success_handler получает + # upstream_failed, поэтому успешное уведомление не скрывает ошибку DAG Run. diff --git a/airflow-docker/dags/resource_management_dag.py b/airflow-docker/dags/resource_management_dag.py new file mode 100644 index 0000000..7ad4fd7 --- /dev/null +++ b/airflow-docker/dags/resource_management_dag.py @@ -0,0 +1,61 @@ +"""Пулы и XCom на двух задачах с наблюдаемой длительностью. + +До запуска создайте пул training_pool с двумя слотами. +Чтение источников имитируется; подключений и входных файлов не требуется. +""" +import logging +import time +from datetime import datetime + +from airflow import DAG +from airflow.operators.python import PythonOperator + + +def read_source(source, rows): + """Имитирует чтение источника в течение 20 секунд.""" + logging.info("Начало чтения %s", source) + time.sleep(20) + logging.info("Чтение %s завершено: %s строк (имитация)", source, rows) + return rows + + +def calculate_metrics(ti): + """Получает небольшие результаты двух задач через XCom.""" + customers = ti.xcom_pull(task_ids="read_customers") + orders = ti.xcom_pull(task_ids="read_orders") + return {"customers": customers, "orders": orders} + + +def log_metrics(ti): + """Печатает словарь, возвращенный предыдущей задачей.""" + logging.info("Метрики: %s", ti.xcom_pull(task_ids="calculate_metrics")) + + +with DAG( + "resource_management_dag", + start_date=datetime(2023, 1, 1), + schedule=None, + catchup=False, + default_args={"owner": "student", "retries": 0}, + tags=["educational", "pools", "xcom"], +) as dag: + customers_task = PythonOperator( + task_id="read_customers", + python_callable=read_source, + op_kwargs={"source": "customers", "rows": 20}, + pool="training_pool", + pool_slots=2, + ) + orders_task = PythonOperator( + task_id="read_orders", + python_callable=read_source, + op_kwargs={"source": "orders", "rows": 30}, + pool="training_pool", + pool_slots=1, + ) + metrics_task = PythonOperator( + task_id="calculate_metrics", python_callable=calculate_metrics, + ) + log_task = PythonOperator(task_id="log_metrics", python_callable=log_metrics) + + [customers_task, orders_task] >> metrics_task >> log_task diff --git a/airflow-docker/dags/sql_basic_dag.py b/airflow-docker/dags/sql_basic_dag.py index fd7b655..4c1ff54 100644 --- a/airflow-docker/dags/sql_basic_dag.py +++ b/airflow-docker/dags/sql_basic_dag.py @@ -4,7 +4,6 @@ DAG для демонстрации работы с SQL в Airflow """ from datetime import datetime, timedelta from airflow import DAG -from airflow.operators.python import PythonOperator from airflow.providers.postgres.operators.postgres import PostgresOperator # Определение DAG @@ -76,11 +75,12 @@ query_data_task = PostgresOperator( ) truncate_table_task = PostgresOperator( - task_id='drop_table', + task_id='truncate_table', postgres_conn_id='postgres_training', sql=truncate_table_sql, dag=dag ) # Установка зависимостей -create_table_task >> insert_data_task >> query_data_task >> truncate_table_task +# Очистка перед вставкой: после запуска в таблице остаются три строки. +create_table_task >> truncate_table_task >> insert_data_task >> query_data_task -- 2.54.0 From d8dfe8365180037b3098e5e307ef14d951e27e8d Mon Sep 17 00:00:00 2001 From: Dmitry Dementev Date: Mon, 28 Sep 2026 08:49:02 -0400 Subject: [PATCH 3/6] =?UTF-8?q?docs(practice):=20=D1=80=D0=B0=D0=B7=D0=B4?= =?UTF-8?q?=D0=B5=D0=BB=D0=B8=D1=82=D1=8C=20=D0=BE=D1=81=D0=BD=D0=BE=D0=B2?= =?UTF-8?q?=D0=BD=D0=BE=D0=B9=20=D0=BC=D0=B0=D1=80=D1=88=D1=80=D1=83=D1=82?= =?UTF-8?q?=20=D0=B8=20=D0=B7=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8=D1=8F=20=D0=BF?= =?UTF-8?q?=D0=BE=20=D0=B6=D0=B5=D0=BB=D0=B0=D0=BD=D0=B8=D1=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Восемь блоков с явным началом, последовательными изменениями и наблюдаемыми результатами. Дополнительные упражнения имеют отдельные зависимости и условия возврата. Обновлены README, справочник DAG и пример уведомлений. Проверены ссылки, якоря, отрисовка Markdown и основные сценарии в Airflow 2.9.2. Проверки дополнительных упражнений продолжаются и фиксируются в плане. --- ...ы, XCom, TaskGroup и алертинг в Airflow.md | 128 ++-- README.md | 52 +- airflow-docker/README.md | 313 ++------- airflow-docker/dag-specifications.md | 344 +--------- airflow-docker/educational-tasks.md | 641 ++++++++---------- 5 files changed, 455 insertions(+), 1023 deletions(-) diff --git a/09 - Пулы, XCom, TaskGroup и алертинг в Airflow.md b/09 - Пулы, XCom, TaskGroup и алертинг в Airflow.md index 8e1443b..f197074 100644 --- a/09 - Пулы, XCom, TaskGroup и алертинг в Airflow.md +++ b/09 - Пулы, XCom, TaskGroup и алертинг в Airflow.md @@ -8,9 +8,9 @@ В системах с высокой нагрузкой, где одновременно запускается множество задач и DAG-ов, может возникнуть чрезмерная нагрузка на исполнителей и серверную часть. Это может привести к ошибкам выполнения и даже к отказу системы, если не установить соответствующие ограничения. -В Airflow для решения этой проблемы существует механизм управления ресурсами — **пулы задач** (pools). По умолчанию в Airflow настроен один пул задач — `default_pool` с 128 слотами, что означает возможность параллельного выполнения 128 задач одновременно. Пул `default_pool` нельзя удалить, но можно изменить его размер — увеличить или уменьшить количество слотов. +В Airflow для решения этой проблемы существует механизм управления ресурсами — **пулы задач** (pools). По умолчанию в Airflow настроен один пул задач — `default_pool` с 128 слотами. При стандартном `pool_slots=1` это верхняя граница в 128 одновременно работающих задач; ограничения executor и DAG могут уменьшить ее. Пул `default_pool` нельзя удалить, но можно изменить его размер — увеличить или уменьшить количество слотов. -Когда планировщик обнаруживает, что наступило время выполнения DAG, он запускает задачу согласно заданной последовательности. При этом задача занимает один слот в пуле и освобождает его после завершения. +Когда планировщик обнаруживает, что наступило время выполнения DAG, он запускает задачу согласно заданной последовательности. По умолчанию задача занимает один слот в пуле и освобождает его после завершения. Создать новый пул задач и установить его размер можно через веб-интерфейс Airflow. Рассмотрим пример пула `data_processing_pool` для тяжёлых задач: @@ -29,7 +29,7 @@ - Организовать запуск процессов в системе - Предотвратить перегрузку системы при выполнении большого количества ресурсоемких задач -Вы можете задать "вес" задачи через параметр `pool_slots`, чтобы оптимизировать распределение нагрузки. Если общее количество задач превышает доступные слоты, планировщик поставит задачу в очередь и запустит ее, как только появятся свободные ресурсы. +Вы можете задать "вес" задачи через параметр `pool_slots`, чтобы оптимизировать распределение нагрузки. Планировщик учитывает сумму занятых слотов. Например, в пуле из двух слотов задачи с `pool_slots=2` и `pool_slots=1` не смогут работать одновременно: одна ждет завершения другой. Пример настройки веса задач: @@ -58,7 +58,7 @@ BashOperator( ) ``` -Более подробную информацию о механизме пулов можно найти в [официальной документации](https://airflow.apache.org/docs/apache-airflow/stable/concepts/pools.html#pools). +Более подробную информацию о механизме пулов можно найти в [официальной документации](https://airflow.apache.org/docs/apache-airflow/2.9.2/administration-and-deployment/pools.html). ## Обмен данными между задачами: XCom и контекст выполнения @@ -220,100 +220,64 @@ TaskGroup — это удобный способ логической групп ## Система оповещений (алертинг) -Алертинг — один из ключевых компонентов системы оркестрации, так как важно своевременно получать уведомления об ошибках для их оперативного анализа и решения. - -В Airflow есть встроенная поддержка отправки уведомлений на электронную почту (при условии, что в конфигурации настроен SMTP-сервер). При создании DAG указываются email-адреса, на которые будут отправляться сообщения. С помощью параметров можно настроить различные сценарии оповещений. - -Давайте модифицируем наш первый DAG так, чтобы получать уведомления на почту при возникновении ошибок. При этом настроим перезапуск задач в случае неудачи (например, 2 попытки), но без уведомлений о самих перезапусках: - -В приведенном примере создан DAG с идентификатором 'customer_analysis_pipeline', который настроен на отправку уведомлений по электронной почте только при ошибках (email_on_failure=True), но не при повторных попытках (email_on_retry=False). Также установлено 2 попытки повторного запуска задач при ошибках с задержкой 2 минуты между попытками. +Для первого опыта достаточно callback с записью в лог. Он получает контекст задачи и помогает связать сообщение с конкретным запуском: ```python -import os -import datetime as dt -import pandas as pd -from airflow.models import DAG -from airflow.operators.python import PythonOperator -from airflow.operators.bash import BashOperator -from sqlalchemy import create_engine +import logging -# основные параметры DAG -args = { - 'owner': 'data_engineering_team', - 'start_date': dt.datetime(2021, 6, 15), - 'retries': 2, - 'retry_delay': dt.timedelta(minutes=2), - 'email': ["data-team@example.com"], - 'email_on_failure': True, - 'email_on_retry': False, -} -dag = DAG( - dag_id='customer_analysis_pipeline', - schedule_interval=None, - default_args=args, -) +def notify_failure(context): + ti = context["ti"] + logging.error( + "Ошибка: dag=%s task=%s run=%s причина=%s", + ti.dag_id, ti.task_id, context["run_id"], context["exception"], + ) ``` -Теперь вы будете получать email-уведомления при ошибках выполнения. +Укажите `on_failure_callback=notify_failure` в рабочем операторе. Функция вызывается после окончательного отказа, когда повторы исчерпаны. Для сообщения о предстоящем повторе существует `on_retry_callback`. Например, при `retries=2` задача может выполниться три раза: исходная попытка и два повтора. -Пример функции для отправки уведомлений с использованием параметров из контекста: +Callback запускается при реальном выполнении задачи. Ручная смена статуса в UI его не проверяет. Смотрите лог самой задачи и логи scheduler; ошибки callback ищите в логах scheduler. [Документация Airflow 2.9.2 о callbacks](https://airflow.apache.org/docs/apache-airflow/2.9.2/administration-and-deployment/logging-monitoring/callbacks.html). + +### Отправка почты по желанию + +В учебном стенде SMTP не настроен. Для отправки нужен SMTP-сервер и адрес получателя, которому можно отправлять тестовые сообщения. Укажите настройки в общем окружении сервисов Airflow: `AIRFLOW__SMTP__SMTP_HOST`, `AIRFLOW__SMTP__SMTP_PORT`, `AIRFLOW__SMTP__SMTP_MAIL_FROM`, параметры TLS/SSL и учетные данные вашего сервера. Секреты храните локально, вне Git. После изменения окружения пересоздайте сервисы командой `docker compose up -d`. Полный список параметров находится в [конфигурации Airflow 2.9.2](https://airflow.apache.org/docs/apache-airflow/2.9.2/configurations-ref.html#smtp). + +После настройки замените запись в лог отправкой сообщения: ```python -from datetime import datetime, timedelta, timezone -import dateutil +from html import escape + from airflow.utils.email import send_email_smtp -from airflow.operators.python import get_current_context -MAIL_LIST = [ - "email_1@gmail.ru", - "email_2@gmail.ru" -] +MAIL_TO = ["student@example.com"] # Замените своим тестовым адресом. -def notify_email(calculation_dt: str, dagrun_begin_time: str): - # dag_run date is utc timezone, so add `timezone.utc` to calculation_end to combat 3 hour diff. - context = get_current_context() - calculation_start = dateutil.parser.isoparse(dagrun_begin_time.replace('Z', '+00:00')) - calculation_end = datetime.now(timezone.utc) - duration = str(timedelta(seconds=(calculation_end - calculation_start).seconds)) - - title = f"Ежедневные расчёты завершились успешно (dag: {context['task_instance'].dag_id})." - - body = f""" - Привет,
- Я закончил работу над расчётом за "{calculation_dt}". Это заняло {duration} часов/минут/секунд.
- Логи и запуски тоже можно посмотреть тут. -
- -
-
- Навеки твой,
- Airflow бот
- """ - - send_email_smtp(";".join(MAIL_LIST), title, body) + +def notify_failure(context): + ti = context["ti"] + send_email_smtp( + to=MAIL_TO, + subject=f"Ошибка {ti.dag_id}: {ti.task_id}", + html_content=( + f"

Запуск: {escape(context['run_id'])}

" + f"

Причина: {escape(str(context['exception']))}

" + f'

Лог задачи

' + ), + ) + + +def notify_success(context): + send_email_smtp( + to=MAIL_TO, + subject=f"DAG {context['dag'].dag_id} завершен успешно", + html_content=f"

Запуск: {escape(context['run_id'])}

", + ) ``` -Такую функцию можно разместить в отдельном файле (например, `utils.py`), импортировать как модуль в нужных DAG и вызывать отдельной задачей: +Для примера с ошибками задайте `on_failure_callback=notify_failure` у двух рабочих задач, а `on_success_callback=notify_success` у самого `DAG(...)`. Если упадут обе задачи, придут два сообщения об отказе. Сообщение об успехе отправится только после успешного DAG Run. Не включайте одновременно `email_on_failure=True`, если не хотите получать еще и встроенное письмо о той же ошибке. -```python -from airflow.operators.python import PythonOperator -from airflow.utils.trigger_rule import TriggerRule -from utils import notify_email +Не добавляйте ради уведомления единственную завершающую задачу с `trigger_rule="all_done"`: если она успешна, Airflow может признать весь запуск успешным, несмотря на отказ внутри графа. Callback не меняет граф и итог обработки. Ошибка отправки означает проблему уведомления; она не превращает успешно обработанные данные в неуспешные и не скрывает исходный отказ. Подробности определения результата - в [описании DAG Run](https://airflow.apache.org/docs/apache-airflow/2.9.2/core-concepts/dag-run.html#dag-run-status). -LOCAL_CALCULATION_DT = '{{ dag.timezone.convert(execution_date).strftime("%Y-%m-%d") }}' -DAG_RUN_BEGIN_TIME = "{{ dag_run.start_date }}" - -email_notification_task = PythonOperator( - task_id="send_email_notification", - python_callable=notify_email, - dag=dag, - trigger_rule=TriggerRule.ALL_DONE, - op_args=[LOCAL_CALCULATION_DT, DAG_RUN_BEGIN_TIME], -) - -... >> email_notification_task -``` +Проверка: сначала воспроизведите окончательный отказ, затем успешный запуск. Сверьте текст каждого письма с состоянием соответствующего DAG Run. Упражнения находятся в [блоке ошибок](airflow-docker/educational-tasks.md#errors) и [задании на почту](airflow-docker/educational-tasks.md#resources). ## Практическое применение: улучшенный DAG diff --git a/README.md b/README.md index 66660a9..3c7f871 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,8 @@ # Учебник по Apache Airflow для начинающих -Добро пожаловать в учебник по Apache Airflow! Этот курс создан специально для тех, кто уже освоил Python и SQL и готов познакомиться со своим первым инструментом для ETL-процессов. К учебнику прилагается готовый практический стенд, позволяющий сразу применить полученные знания на практике. +Учебник знакомит с Apache Airflow 2.9: устройством DAG, запуском задач, интерфейсом и поиском ошибок. К нему прилагается Docker-стенд с готовыми примерами. Перед началом нужны Python, pandas, SQL и основы Docker. -## 📚 Введение - -Apache Airflow — это мощный оркестратор рабочих процессов с открытым исходным кодом, который стал стандартом де-факто в мире обработки данных. Если вы работаете с автоматизацией процессов обработки информации — будь то подготовка данных для машинного обучения, создание отчетов или интеграция данных из различных источников — Airflow поможет вам организовать эти процессы эффективно и надежно. - -Этот учебник разработан с учетом того, что вы: -- Имеете базовые знания Python и SQL -- Хотите освоить свой первый ETL-инструмент -- Нуждаетесь в практическом руководстве с понятными объяснениями - -Мы начнем с основ и постепенно перейдем к продвинутым возможностям, чтобы вы могли уверенно использовать Airflow в реальных проектах. Курс ориентирован на версию Airflow 2.9 и использует практический подход с множеством примеров и визуальных материалов. - -## 🎯 Что вы узнаете +## Что вы узнаете - **Основы Airflow**: что это такое, для каких задач используется и почему он стал таким популярным - **Ключевые концепции**: DAG, задачи, операторы и другие фундаментальные понятия @@ -23,7 +12,7 @@ Apache Airflow — это мощный оркестратор рабочих п - **Отладка и мониторинг**: как понимать статусы задач и быстро находить проблемы - **Продвинутые возможности**: шаблоны, параметры, управление временем, пулы, XCom, TaskGroup и алертинг -## 📖 Содержание курса +## Содержание курса ### [01. Введение в Airflow](01%20-%20Введение%20в%20Airflow.md) - Почему Apache Airflow стал незаменимым инструментом для работы с данными @@ -73,34 +62,25 @@ Apache Airflow — это мощный оркестратор рабочих п - Логическая группировка задач с TaskGroup - Настройка системы оповещений и уведомлений -## 🛠️ Практический стенд +## Практический стенд -К этому учебнику прилагается полноценный стенд Apache Airflow, который позволяет сразу приступить к практике и закрепить полученные знания. В папке [`airflow-docker`](airflow-docker/) вы найдете готовую среду для экспериментов с 6 готовыми DAG'ами разной сложности и практическими заданиями для каждого из них. +В [`airflow-docker`](airflow-docker/) находятся Airflow 2.9.2, учебная PostgreSQL и девять готовых DAG. [Инструкция запуска](airflow-docker/README.md) поможет поднять стенд, а [маршрут практики](airflow-docker/educational-tasks.md) связывает восемь блоков заданий с главами учебника. -Стенд включает настроенную среду с PostgreSQL для тренировок, образцы данных и детальные инструкции для практических занятий. Подробную информацию о возможностях стенда и инструкции по запуску смотрите в [`README.md`](airflow-docker/README.md) папки `airflow-docker`. +Здесь вы запускаете готовый DAG, меняете одну часть и проверяете результат в графе, логах или данных. Самостоятельные процессы обработки данных относятся к курсовым работам дальше по [программе](https://de.dementev.space/). -## 🚀 Как использовать этот учебник +## Как использовать этот учебник -1. **Изучайте теорию**: начните с Введения в Airflow и переходите к следующим главам по порядку -2. **Практикуйтесь на стенде**: после каждой главы выполняйте соответствующие задания в практическом стенде -3. **Создавайте свои DAG**: применяйте полученные знания для решения реальных задач -4. **Используйте визуальные материалы**: в учебнике много изображений и диаграмм для лучшего понимания -5. **Экспериментируйте**: не бойтесь пробовать разные настройки и сценарии на учебном стенде +1. Прочитайте главы 1-5 и запустите стенд. Перед каждым блоком практики сверяйтесь с колонкой "Перед практикой": некоторые задания используют материал следующих глав. +2. Выполняйте основные задания блока по порядку и сохраняйте изменения. Каждый новый блок описывает, с какого состояния начать. +3. Проверяйте раздел "Что должно получиться". В опыте с ошибкой результатом может быть `failed`; важно найти причину и восстановить успешный запуск. +4. Задания "По желанию" можно пропустить. Они не нужны для продолжения основного маршрута, а высокая сложность отмечена отдельно. -## 📝 Технические требования +Практика завершена, когда пройдены основные задания всех восьми блоков и вы можете объяснить наблюдаемое поведение. Выполнение всех усложнений для этого не требуется. -- **Python**: базовое знание языка +## Технические требования + +- **Python и pandas**: чтение функций, работа с таблицами - **SQL**: понимание основных запросов и концепций - **Airflow**: версия 2.9 (на которой ориентирован курс) +- **Docker и Docker Compose**: запуск учебного стенда - **Среда разработки**: любой текстовый редактор или IDE с поддержкой Python - -## 💡 Советы для успешного обучения - -- **Не торопитесь**: лучше глубоко понять каждый концепт, чем быстро пройти весь курс -- **Задавайте вопросы**: если что-то непонятно, вернитесь к предыдущим материалам -- **Делайте заметки**: записывайте важные моменты и примеры кода -- **Применяйте на практике**: сразу пробуйте создавать свои DAG для реальных задач - ---- - -*Этот учебник создан для начинающих специалистов в области данных и инженерии. Все материалы ориентированы на практическое применение и пошаговое освоение Apache Airflow.* diff --git a/airflow-docker/README.md b/airflow-docker/README.md index 444fc85..e616b9b 100644 --- a/airflow-docker/README.md +++ b/airflow-docker/README.md @@ -1,277 +1,106 @@ -# Educational Airflow Setup for Beginners +# Учебный стенд Apache Airflow -Простой учебный стенд Apache Airflow для начинающих, изучающих SQL и Python. +Стенд содержит Airflow 2.9.2, учебную PostgreSQL и девять DAG. Он нужен для знакомства с инструментом: запустить пример, изменить его и посмотреть результат. Для работы понадобятся Docker с Compose, браузер и знания Python, pandas и SQL. -## 🎯 Цель проекта +## Запуск -Создать максимально простую среду для изучения Apache Airflow. -Для удобства все переменные окружения захардкожены в docker скрипты. На проде так делать не надо :) - -## 📋 Предварительные требования - -- Docker и Docker Compose -- Базовые знания Python и SQL -- Веб-браузер для доступа к интерфейсу Airflow - -## 🚀 Быстрый старт - -### 1. Запуск стенда +Из корня репозитория: ```bash -# Перейдите в директорию проекта cd airflow-docker - -# Запустите все сервисы -docker-compose up -d +docker compose up -d --build ``` -### 2. Доступ к интерфейсам +Команды подходят для Linux и PowerShell в Windows. При первом запуске Docker собирает образ, поднимает две БД, выполняет `airflow-init`, затем запускает webserver и scheduler. Инициализация создает учетную запись и подключение `postgres_training`. -- **Airflow UI**: http://localhost:8080 - - Логин: `admin` - - Пароль: `admin` +Проверьте состояние: -- **PostgreSQL для тренировок**: `localhost:5432` - - База данных: `training` - - Пользователь: `student` - - Пароль: `student` - -- **PostgreSQL для метаданных Airflow**: `localhost:5434` - - База данных: `airflow` - - Пользователь: `airflow` - - Пароль: `airflow` - -## 🏗️ Архитектура стенда - -```mermaid -graph LR - - %% Локальная файловая система - subgraph local["Локальная файловая система"] - dags["Каталог
Educational DAGs"] - fs["Local Filesystem"] - dags --> fs - end - - %% Docker-контейнеры - subgraph docker["Контейнеры Docker"] - subgraph airflow["Airflow"] - ws["Airflow Webserver"] - sch["Airflow Scheduler"] - meta["PostgreSQL Metadata"] - sch --> meta - ws --> meta - end - - subgraph training["Учебная БД"] - train["PostgreSQL Training"] - end - end - - %% Клиенты - subgraph clients["Клиенты"] - user["Пользователь
(браузер)"] - sql["SQL клиент"] - end - - %% Монтирование DAG'ов в контейнеры Airflow - dags -. "том с DAG-файлами" .- ws - dags -. "том с DAG-файлами" .- sch - - %% Airflow-задачи работают с учебной БД - sch -- "задачи DAG" --> train - - %% Внешние подключения - user -- "порт 8080" --> ws - sql -- "порт 5432" --> train +```bash +docker compose ps -a +docker compose logs airflow-init ``` -## 📁 Структура проекта +Завершение `airflow-init` с кодом 0 нормально: это разовая подготовка. Остальные четыре сервиса должны работать. Откройте http://localhost:8080 и войдите как `admin` / `admin`. -``` +| Сервис | Адрес с компьютера | База | Пользователь / пароль | +|---|---|---|---| +| Airflow UI | `http://localhost:8080` | - | `admin` / `admin` | +| Учебная PostgreSQL | `localhost:5432` | `training` | `student` / `student` | +| Метаданные Airflow | `localhost:5434` | `airflow` | `airflow` / `airflow` | + +В задачах используется адрес `postgres-training:5432`: это имя сервиса внутри Docker. Базу метаданных для упражнений не используйте. Учебные пароли заданы в Compose; для публичного сервера такая конфигурация не подходит. + +## Прохождение практики + +Откройте [задания](educational-tasks.md). Их восемь блоков проходят последовательно; перед каждым указаны нужные главы [учебника](../README.md), исходные файлы и настройки. + +Основные задания внутри блока сохраняют изменения предыдущих шагов. Дополнительные находятся в конце блока и помечены "По желанию"; их можно пропустить. Для завершения практики достаточно основных заданий и проверки их результатов. + +| Блок | DAG | +|---|---| +| [Первый DAG](educational-tasks.md#first-dag) | `hello_world_dag` | +| [SQL и подключения](educational-tasks.md#sql) | `sql_basic_dag` | +| [Работа с файлами](educational-tasks.md#files) | `file_operations_dag` | +| [Загрузка CSV и DQ](educational-tasks.md#load-dq) | `csv_to_postgres`, `csv_to_postgres_dq` | +| [Разбор ETL](educational-tasks.md#etl) | `data_processing_dag` | +| [Ветвление](educational-tasks.md#branching) | `branching_dag` | +| [Ошибки и повторы](educational-tasks.md#errors) | `error_handling_dag` | +| [Пулы, XCom и TaskGroup](educational-tasks.md#resources) | `resource_management_dag` | + +Описание поведения исходных примеров находится в [справочнике DAG](dag-specifications.md). Почта нужна только для отдельного задания по желанию; SMTP в стенде не настроен. + +## Файлы и данные + +```text airflow-docker/ -├── docker-compose.yml # Конфигурация Docker -├── .env # Файл не используется: переменные заданы в docker-compose.yml -├── dags/ # DAG файлы для обучения -│ ├── hello_world_dag.py # Базовый пример -│ ├── sql_basic_dag.py # Работа с SQL -│ ├── file_operations_dag.py # Обработка файлов -│ ├── csv_to_postgres.py # Загрузка CSV в Postgres (ETL) -│ ├── csv_to_postgres_dq.py # Проверки качества данных (DQ) -│ ├── data_processing_dag.py # Сложный ETL пайплайн -│ ├── branching_dag.py # Условная логика -│ └── error_handling_dag.py # Обработка ошибок -├── data/ # Данные и артефакты прогонов -│ ├── input/ # Входные данные -│ └── output/ # Сгенерированные CSV, отчеты и результаты обработки -├── logs/ # Логи Airflow -├── README.md # Эта инструкция -└── educational-tasks.md # Практические задания для студентов +├── docker-compose.yml # Сервисы, порты и учебные подключения +├── Dockerfile # Airflow 2.9.2 +├── requirements.txt # Дополнительные Python-пакеты +├── dags/ # Девять учебных DAG +├── data/input/ # Входные CSV и данные упражнений +├── data/output/ # Результаты задач +├── init-postgres.sql # Начальная подготовка учебной БД +└── educational-tasks.md # Маршрут и условия заданий ``` -## 🎓 Учебные материалы +`dags/` и `data/` смонтированы в контейнеры как `/opt/airflow/dags/` и `/opt/airflow/data/`. Правки DAG подхватываются планировщиком; перед новым запуском дождитесь обновления вкладки Code. Резервные копии Python-файлов храните вне `dags/`, чтобы не создать два определения одного `dag_id`. -### Основы Airflow +Таблицы PostgreSQL сохраняются в именованных томах `pg_data` и `pgmeta`. Обычная остановка их не удаляет. Логи Airflow хранятся внутри контейнеров; важные логи опыта сохраните до удаления контейнеров. -**Цели:** -- Понимание структуры DAG -- Создание простых задач -- Настройка зависимостей между задачами - -**Примеры DAG:** -- `hello_world_dag.py` - базовые операторы Python -- `sql_basic_dag.py` - работа с базами данных - -### Интеграция с данными - -**Цели:** -- Подключение к PostgreSQL -- Выполнение SQL запросов -- Обработка файлов CSV - -**Примеры DAG:** -- `file_operations_dag.py` - работа с файлами -- `csv_to_postgres.py` - загрузка данных из CSV в PostgreSQL -- `csv_to_postgres_dq.py` - автоматизированные проверки качества (Data Quality) -- `data_processing_dag.py` - ETL процессы - -### Продвинутые возможности - -**Цели:** -- Условное выполнение задач -- Обработка ошибок -- Параметризация workflows - -**Примеры DAG:** -- `branching_dag.py` - условная логика -- `error_handling_dag.py` - обработка ошибок - -## 🎯 Практические задания - -Для закрепления знаний по каждому DAG мы подготовили практические задания разного уровня сложности: - -### 📋 Учебные задания - -В файле **[educational-tasks.md](educational-tasks.md)** вы найдете детальные задания для каждого DAG: - -- 🌱 **Начальный уровень**: Базовые задачи по созданию и настройке DAG'ов -- 📚 **Средний уровень**: Работа с данными, файлами и SQL операциями -- 🚀 **Продвинутый уровень**: ETL процессы, ветвление, обработка ошибок - -### 🎓 Рекомендуемый путь обучения - -1. **Начните с `hello_world_dag.py`** - освоите основы Airflow -2. **Перейдите к `sql_basic_dag.py`** - изучите работу с базами данных -3. **Попрактикуйтесь на `file_operations_dag.py`** - работа с файлами -4. **Освойте ETL и DQ на `csv_to_postgres.py` и `csv_to_postgres_dq.py`** - загрузка и валидация данных -5. **Разберите сложный ETL на `data_processing_dag.py`** - обработка данных -6. **Изучите продвинутые темы** - ветвление и обработка ошибок - -Каждое задание содержит: -- Цель и сложность выполнения -- Конкретные требования к модификации кода -- Ожидаемый результат -- Время на выполнение - -## 🔧 Технические детали - -### Переменные окружения - -В этом проекте мы не используем `.env`: все значения заданы напрямую в `docker-compose.yml`. - -Сервисы Airflow получают: -- `POSTGRES_TRAINING_HOST=postgres-training` -- `POSTGRES_TRAINING_PORT=5432` -- `POSTGRES_TRAINING_DB=training` -- `POSTGRES_TRAINING_USER=student` -- `POSTGRES_TRAINING_PASSWORD=student` -- `AIRFLOW_CONN_POSTGRES_TRAINING=postgresql://student:student@postgres-training:5432/training` - -`docker-compose run --rm airflow-init` запускает `airflow connections add postgres_training`, поэтому соединение доступно сразу после инициализации. Проверить наличие можно через: +Для SQL-запросов из заданий: ```bash -docker-compose exec airflow-webserver airflow connections get postgres_training +docker compose exec postgres-training psql -U student -d training ``` -`csv_to_postgres.py` по умолчанию складывает сгенерированные CSV в `/opt/airflow/data/output`, то есть в локальный каталог `airflow-docker/data/output/`. +Выход из psql - `\q`. Готовое подключение Airflow можно проверить командой: - -### Порты - -- `8080` - Airflow Webserver -- `5432` - PostgreSQL для тренировок -- `5434` - PostgreSQL для метаданных Airflow - -## 🛠️ Управление стендом - -### Запуск сервисов ```bash -docker-compose up -d +docker compose exec airflow-webserver airflow connections get postgres_training ``` -### Остановка сервисов +## Управление и поиск ошибок + +Все команды ниже выполняются из `airflow-docker`. + ```bash -docker-compose down +docker compose exec airflow-webserver airflow version +docker compose exec airflow-webserver airflow dags list +docker compose exec airflow-webserver airflow dags list-import-errors +docker compose logs airflow-scheduler +docker compose logs airflow-webserver ``` -### Просмотр логов +Если DAG не появился, проверьте путь файла и ошибки импорта. Если задача упала, сначала откройте ее лог в UI. При ошибке подключения проверьте `docker compose ps` и Conn Id задачи. Задачи из блока 8 ждут, пока вы создадите указанный в задании пул. + +Остановка с сохранением данных: + ```bash -# Логи Airflow -docker-compose logs airflow-webserver -docker-compose logs airflow-scheduler - -# Логи PostgreSQL -docker-compose logs postgres-training -docker-compose logs postgres-metadata +docker compose down ``` -### Перезапуск конкретного сервиса -```bash -docker-compose restart airflow-webserver -``` - -## 🐛 Решение проблем - -### DAG не появляется в интерфейсе -- Проверьте, что файл находится в папке `dags/` -- Убедитесь в правильности синтаксиса Python -- Проверьте логи планировщика: `docker-compose logs airflow-scheduler` - -### Ошибки подключения к базе данных -- Убедитесь, что PostgreSQL запущен: `docker-compose ps` -- Проверьте логи PostgreSQL: `docker-compose logs postgres-training` - -### Задачи завершаются с ошибкой -- Проверьте логи задачи в интерфейсе Airflow -- Убедитесь в наличии необходимых Python пакетов - -## 📚 Дополнительные ресурсы - -- [Официальная документация Airflow](https://airflow.apache.org/docs/) -- [Учебные материалы в родительской папке](../) -- [Практические задания для студентов](educational-tasks.md) - ---- - -## 🌟 Начало работы с заданиями - -После запуска стенда Airflow, откройте файл [educational-tasks.md](educational-tasks.md) и выберите задание, соответствующее вашему уровню подготовки. Каждое задание содержит подробные инструкции и ожидаемые результаты. - -**Быстрый старт с заданиями:** -```bash -# Запустите стенд -docker-compose up -d - -# Откройте задания в браузере или редакторе -# И следуйте инструкциям из educational-tasks.md -``` - -Удачи в изучении Apache Airflow! - ---- - -**Примечание**: Этот стенд предназначен исключительно для учебных целей. Для production использования требуется дополнительная настройка безопасности. +Повторный запуск - `docker compose up -d`. Для полного сброса обеих БД используйте `docker compose down -v`: эта команда удалит учебные таблицы, историю запусков и настройки Airflow в томах. Файлы в `data/` останутся. ## Для авторов курса -[План обновления практики](../docs/specs/2026-09-28-practice-improvement-plan.md) — согласованный подход, найденные несогласованности, порядок доработки и проверки заданий. +[План обновления практики](../docs/specs/2026-09-28-practice-improvement-plan.md) сохраняет согласованные решения, разбор исходных заданий и результаты проверок. diff --git a/airflow-docker/dag-specifications.md b/airflow-docker/dag-specifications.md index ab674da..e6f917c 100644 --- a/airflow-docker/dag-specifications.md +++ b/airflow-docker/dag-specifications.md @@ -1,327 +1,29 @@ -# Educational DAG Specifications for Airflow Learning +# Что делают исходные DAG -## Learning Progression Structure +Это справочник примеров для Airflow 2.9.2. Порядок прохождения, изменения кода и условия завершения находятся в [практических заданиях](educational-tasks.md). -### Level 1: Basic Concepts (Week 1) +| DAG и файл в `dags/` | Подготовка | Поведение и результат | +|---|---|---| +| `hello_world_dag` (`hello_world_dag.py`) | Не требуется | Три Python-задачи печатают приветствие, дату и завершение. Расписание ежедневное, `catchup=False`. | +| `sql_basic_dag` (`sql_basic_dag.py`) | Connection `postgres_training`, создаваемый при запуске стенда | Создает `public.students_sample`, очищает перед вставкой, добавляет трех студентов и выполняет SELECT. После каждого полного запуска в таблице три строки. | +| `file_operations_dag` (`file_operations_dag.py`) | Каталоги `data/input`, `data/output` из репозитория | Генерирует 100 строк, проверяет данные, сохраняет `output/processed_data.csv` и `output/summary.txt`. Категория High начинается с 70000. | +| `csv_to_postgres` (`csv_to_postgres.py`) | Connection `postgres_training` | Создает `public.orders`, генерирует CSV на 1000 строк, передает путь через XCom и загружает файл. Уже существующие `order_id` пропускаются; новый полный запуск генерирует новые заказы. | +| `csv_to_postgres_dq` (`csv_to_postgres_dq.py`) | Успешная загрузка `csv_to_postgres`; запускать вручную после нее | Проверяет таблицу, точную схему из четырех колонок, наличие строк и отсутствие дубликатов. Сводка перечисляет состояния проверок. По умолчанию после ошибки последующие задачи не выполняются (`all_success`). | +| `data_processing_dag` (`data_processing_dag.py`) | Не требуется, свои входные CSV создаются заново | Извлекает 5 клиентов и 6 заказов в `output/extracted_*.csv`, читает их при преобразовании. `simulate_load` печатает число строк и DDL без записи в БД. Отчет: 6 заказов, выручка 2105. | +| `branching_dag` (`branching_dag.py`) | Variable `branch_format`: `csv` или `json`; по умолчанию `csv` | Выбранная ветка имитирует обработку, другая получает `skipped`. Слияние читает XCom текущего запуска с `none_failed_min_one_success`. Неизвестный формат приводит к ошибке. | +| `error_handling_dag` (`error_handling_dag.py`) | Variable `error_mode`, по умолчанию `retry_once` | Две рабочие задачи, по одному повтору с задержкой 10 секунд. Оба обработчика зависят от обеих задач. Окончательный отказ сохраняет `failed` у DAG Run, даже если обработчик ошибки успешен. | +| `resource_management_dag` (`resource_management_dag.py`) | Создать `training_pool` с 2 слотами | Две задачи по 20 секунд имитируют чтение и занимают 2 и 1 слот, поэтому работают последовательно. Через XCom передают 20 и 30; следующая задача возвращает словарь, последняя печатает его. | -#### 1.1 hello_world_dag.py -**Learning Objectives:** -- Understand basic DAG structure -- Learn about PythonOperator -- Understand task dependencies +Все DAG, кроме `hello_world_dag`, запускаются вручную. Связи между загрузчиком CSV и DQ-DAG нет. Файлы из `input/orders.csv` в ETL-примере и таблица `public.orders` в загрузчике относятся к разным блокам. -**DAG Structure:** -```python -from airflow import DAG -from airflow.operators.python import PythonOperator -from datetime import datetime +## Режимы примера с ошибками -def print_hello(): - print("Hello World from Airflow!") +| `error_mode` | Рабочие задачи | Обработчики | DAG Run | +|---|---|---|---| +| `success` | Обе успешны с первой попытки | Успех: `success`, ошибка: `skipped` | `success` | +| `retry_once` | `retry_task` падает один раз, затем успешна | Успех: `success`, ошибка: `skipped` | `success` | +| `fail_first` | `unreliable_task` окончательно падает | Успех: `upstream_failed`, ошибка: `success` | `failed` | +| `fail_second` | `retry_task` окончательно падает | Успех: `upstream_failed`, ошибка: `success` | `failed` | +| `fail_both` | Обе окончательно падают | Успех: `upstream_failed`, ошибка: `success` | `failed` | -def print_date(): - print(f"Current date: {datetime.now()}") - -def print_goodbye(): - print("Goodbye from Airflow!") - -# DAG definition with simple tasks -``` - -**Tasks:** -- `start_task`: Print welcome message -- `date_task`: Print current date/time -- `end_task`: Print goodbye message - -**Dependencies:** -start_task → date_task → end_task - -#### 1.2 sql_basic_dag.py -**Learning Objectives:** -- Connect to PostgreSQL database -- Execute SQL queries -- Use PostgresOperator - -**Connection Hint:** `docker-compose run --rm airflow-init` automatically provisions the `postgres_training` connection via `airflow connections add`, so no manual setup is required. You can verify it with `docker-compose exec airflow-webserver airflow connections get postgres_training`. - -**Tasks:** -- `create_table`: Create simple table (users, products) -- `insert_data`: Insert sample records -- `query_data`: Select and display data -- `drop_table`: Clean up (optional) - -**SQL Operations:** -```sql --- Create table -CREATE TABLE IF NOT EXISTS students ( - id SERIAL PRIMARY KEY, - name VARCHAR(100), - age INTEGER, - created_at TIMESTAMP DEFAULT NOW() -); - --- Insert data -INSERT INTO students (name, age) VALUES -('Alice', 22), -('Bob', 24), -('Charlie', 21); -``` - -### Level 2: Intermediate Concepts (Week 2) - -#### 2.1 file_operations_dag.py -**Learning Objectives:** -- File system operations -- CSV data processing -- Data transformation - -**Tasks:** -- `generate_sample_data`: Create CSV file with random data -- `read_csv_file`: Read and validate data -- `transform_data`: Simple data transformations -- `write_output`: Save processed data - -**Sample Data Structure:** -```csv -id,name,department,salary -1,Alice,Engineering,50000 -2,Bob,Marketing,45000 -3,Charlie,Sales,48000 -``` - -#### 2.2 csv_to_postgres.py -**Learning Objectives:** -- Load CSV data into PostgreSQL database -- Implement idempotent loading via temporary table -- Use XCom for passing file paths between tasks -- Work with PostgreSQL connections in Airflow - -**Scenario:** -Generate sample orders data as CSV, preview it, and load it into PostgreSQL. - -**Tasks:** -- `create_orders_table`: Create public.orders table in PostgreSQL -- `generate_csv`: Generate sample orders CSV file -- `preview_csv`: Display first few rows of CSV -- `load_csv_to_postgres`: Load CSV data into PostgreSQL using temporary table - -**Database Connection:** Uses `postgres_training` connection (auto-provisioned by init script). -**Generated Files:** CSV files are written to `/opt/airflow/data/output/`. - -**Sample Data Structure:** -```csv -order_id,order_ts,customer_id,amount -1,2023-10-01 10:30:00,101,1250.50 -2,2023-10-01 11:45:00,102,890.00 -3,2023-10-02 09:15:00,103,2100.75 -``` - -**Data Quality Checks:** Run `csv_to_postgres_dq.py` after loading to validate the target table. - -#### 2.3 csv_to_postgres_dq.py -**Learning Objectives:** -- Implement data quality validation in Airflow -- Use Python functions for data checks -- Handle data quality failures -- Separate validation from main ETL pipeline - -**Scenario:** -Run automated data quality checks on the public.orders table after CSV loading. - -**Tasks:** -- `check_table_exists`: Verify public.orders table exists -- `check_schema`: Validate table schema matches expected structure -- `check_row_count`: Ensure table has data -- `check_duplicates`: Verify no duplicate order_id values - -**Quality Checks:** -- Table existence in public schema -- Column names and data types (order_id, order_ts, customer_id, amount) -- Minimum row count (> 0) -- Unique order_id values (no duplicates) - -**Helper Functions:** All DQ check functions are defined directly in `csv_to_postgres_dq.py`. - -#### 2.4 data_processing_dag.py -**Learning Objectives:** -- ETL pipeline concepts -- Multiple data sources -- Error handling basics - -**Tasks:** -- `extract_customers`: Read customer data -- `extract_orders`: Read order data -- `transform_data`: Join and process data -- `load_to_database`: Save results -- `generate_report`: Create summary - -### Level 3: Advanced Concepts (Week 3) - -#### 3.1 branching_dag.py -**Learning Objectives:** -- Conditional task execution -- BranchPythonOperator -- Decision making in workflows - -**Scenario:** -Process data based on file type or data quality - -**Tasks:** -- `check_file_type`: Determine processing path -- `process_csv_branch`: For CSV files -- `process_json_branch`: For JSON files -- `merge_results`: Combine outputs - -#### 3.2 error_handling_dag.py -**Learning Objectives:** -- Task retries -- Error notifications -- Failure handling - -**Tasks:** -- `unreliable_task`: Simulate failures -- `retry_task`: Demonstrate retry mechanism -- `success_handler`: On success callback -- `failure_handler`: On failure callback - -### Level 4: Orchestration & Collaboration (Week 4) - -#### 4.1 advanced_features_dag.py -**Learning Objectives:** -- Control concurrency with pools and `pool_slots` -- Exchange data between tasks using XCom -- Group related tasks using `TaskGroup` -- Configure email-based alerting on failures - -**Scenario:** -Enhanced daily analytics pipeline that reads data from the training database, performs transformations, and writes summaries, while limiting heavy backup tasks via a dedicated pool and sending notifications about pipeline status. - -**Tasks:** -- `extract_group`: Use `TaskGroup` to wrap extract tasks (e.g., customers and orders) -- `transform_group`: Aggregate metrics and prepare summary tables -- `load_group`: Simulate loading results back into the training database or files -- `backup_task`: Heavy backup task running in a dedicated pool (e.g., `backup_pool`) with custom `pool_slots` -- `calculate_metrics`: Python task that returns aggregated metrics (pushed to XCom) -- `log_metrics`: Task that reads metrics via `xcom_pull` and logs them or uses them in a template -- `send_notification`: Final notification task (email or log) triggered with `ALL_DONE` semantics - -## Sample Data Files - -### customers.csv -```csv -customer_id,name,email,join_date -1,Alice Johnson,alice@example.com,2023-01-15 -2,Bob Smith,bob@example.com,2023-02-20 -3,Charlie Brown,charlie@example.com,2023-03-10 -``` - -### orders.csv -```csv -order_id,customer_id,product,amount,order_date -101,1,Laptop,1200,2023-10-01 -102,2,Monitor,300,2023-10-02 -103,1,Keyboard,80,2023-10-03 -104,3,Mouse,25,2023-10-04 -``` - -### products.csv -```csv -product_id,name,category,price -1,Laptop,Electronics,1200 -2,Monitor,Electronics,300 -3,Keyboard,Electronics,80 -4,Mouse,Electronics,25 -``` - -## Database Schema for Training - -### Students Table -```sql -CREATE TABLE students ( - student_id SERIAL PRIMARY KEY, - first_name VARCHAR(50), - last_name VARCHAR(50), - email VARCHAR(100), - enrollment_date DATE, - grade INTEGER -); -``` - -### Courses Table -```sql -CREATE TABLE courses ( - course_id SERIAL PRIMARY KEY, - course_name VARCHAR(100), - instructor VARCHAR(100), - credits INTEGER -); -``` - -### Enrollments Table -```sql -CREATE TABLE enrollments ( - enrollment_id SERIAL PRIMARY KEY, - student_id INTEGER REFERENCES students(student_id), - course_id INTEGER REFERENCES courses(course_id), - enrollment_date DATE, - grade CHAR(1) -); -``` - -## Learning Outcomes by Week - -### Week 1: Foundation -- ✅ Understand DAG structure and components -- ✅ Create basic Python tasks -- ✅ Set up task dependencies -- ✅ Run first successful workflow - -### Week 2: Integration -- ✅ Connect to databases -- ✅ Execute SQL operations -- ✅ Process file data -- ✅ Build simple ETL pipelines - -### Week 3: Advanced Features -- ✅ Implement conditional logic -- ✅ Handle errors and retries -- ✅ Use parameters and templates -- ✅ Monitor and debug workflows - -### Week 4: Orchestration & Operations -- ✅ Use pools to control resource usage -- ✅ Share data between tasks via XCom -- ✅ Group tasks using `TaskGroup` -- ✅ Configure alerting and notifications for failures - -## Common Pitfalls and Solutions - -### Problem: DAG not appearing in UI -**Solution:** Check DAG file location and syntax - -### Problem: Database connection errors -**Solution:** Verify connection strings and database availability - -### Problem: Task failures -**Solution:** Check logs, implement proper error handling - -### Problem: Scheduling issues -**Solution:** Understand cron expressions and execution dates - -## Assessment Criteria - -### Basic Competency -- Can create simple DAG with 3+ tasks -- Understands task dependencies -- Can run and monitor workflows - -### Intermediate Competency -- Can integrate with databases -- Can process file data -- Implements basic error handling - -### Advanced Competency -- Uses conditional branching -- Implements proper error handling -- Creates reusable components -- Optimizes workflow performance +Меняйте Variable после завершения предыдущего запуска. `retries=1` означает две попытки всего. `failure_handler` использует `one_failed`: достаточно одного окончательного отказа, ждать завершения другой рабочей задачи ему не обязательно. diff --git a/airflow-docker/educational-tasks.md b/airflow-docker/educational-tasks.md index a40dbad..8599f62 100644 --- a/airflow-docker/educational-tasks.md +++ b/airflow-docker/educational-tasks.md @@ -1,478 +1,435 @@ -# Учебные задания по Apache Airflow +# Практика по Apache Airflow -Этот документ содержит практические задания для закрепления знаний по Apache Airflow. Каждое задание соответствует одному из учебных DAG'ов и направлено на лучшее понимание конкретных концепций. +Здесь мы знакомимся с Airflow на готовых примерах: запускаем DAG, меняем задачи и смотрим, что произошло. Свой цельный процесс обработки данных вы соберете в курсовых работах дальше по программе. -## Общие инструкции +## Как проходить практику -Перед выполнением заданий: -1. Убедитесь, что стенд Airflow запущен: `docker-compose up -d` -2. Проверьте доступность интерфейса: http://localhost:8080 -3. Ознакомьтесь с соответствующим DAG'ом в интерфейсе Airflow -4. Откройте исходный код DAG'а и прочитайте описание в начале файла +В каждом блоке сначала выполните основную практику. Задания "По желанию" находятся в конце блока: их можно пропустить и перейти дальше. Сложность указана отдельно. Если дополнительному заданию нужны результаты другого упражнения, это написано в его начале. ---- +Основные задания одного блока выполняйте последовательно, сохраняя изменения. Перед новым блоком прочитайте условия начала. Загрузка CSV и проверки DQ используют одну таблицу, поэтому идут вместе. -## Задания для hello_world_dag.py +| Блок | Примеры | Что наблюдаем | Перед практикой | +|---|---|---|---| +| [1. Первый DAG](#first-dag) | `hello_world_dag.py` | Задачи, зависимости, операторы, расписание | Главы 4, 5 и 8 | +| [2. SQL и подключения](#sql) | `sql_basic_dag.py` | Connection, SQL, ошибка подключения | Глава 7 | +| [3. Работа с файлами](#files) | `file_operations_dag.py` | Результаты отдельных задач | Главы 5 и 6 | +| [4. Загрузка и DQ](#load-dq) | `csv_to_postgres.py`, `csv_to_postgres_dq.py` | XCom, данные в БД, ошибка проверки | Главы 6, 7 и 9 | +| [5. Разбор ETL](#etl) | `data_processing_dag.py` | Параллельные шаги и путь данных | Глава 5 | +| [6. Ветвление](#branching) | `branching_dag.py` | Variables, выбранная ветка, `skipped` | Главы 6 и 7 | +| [7. Ошибки и повторы](#errors) | `error_handling_dag.py` | Попытки, обработчики, итог запуска | Главы 6 и 9 | +| [8. Пулы и группировка](#resources) | `resource_management_dag.py` | Слоты, XCom, TaskGroup | Глава 9 | -**Цель:** Освоить базовые концепции Airflow — создание задач, зависимости, операторы. +Главы доступны в [оглавлении учебника](../README.md). Инструкция запуска и параметры учебной БД находятся в [README стенда](README.md). -### Задание 1: Модификация существующих задач -**Сложность:** Начальная +Откройте http://localhost:8080, войдите как `admin` / `admin`. Для выбранного DAG включите переключатель и нажмите Trigger DAG. Дождитесь завершения запуска, затем откройте его граф и логи задач. После правки Python-файла дождитесь обновления вкладки Code; новый запуск должен использовать новый код. Если DAG исчез или перестал обновляться, проверьте Import Errors и логи scheduler. -**Задача:** -- Откройте файл `hello_world_dag.py` -- Измените текст в функциях `print_hello()`, `print_date()`, `print_goodbye()` на русский язык -- Добавьте новую задачу, которая выводит текущую погоду (можно использовать фиктивные данные) +Каталог `/opt/airflow/data/` в контейнере соответствует `airflow-docker/data/` на компьютере. Команды ниже выполняются из `airflow-docker`. Подойдет терминал Linux или PowerShell в Windows. -**Подсказка:** -- Новая задача создаётся по аналогии с `date_task` — нужна Python-функция и `PythonOperator` для неё -- Не забудьте встроить задачу в цепочку зависимостей (`>>`) -- После сохранения файла DAG подхватится автоматически — запустите его вручную через UI и проверьте логи +Для SQL можно открыть консоль учебной БД: -**Цель задания:** Понять структуру DAG, научиться добавлять и модифицировать задачи. +```bash +docker compose exec postgres-training psql -U student -d training +``` -### Задание 2: Изменение расписания -**Сложность:** Начальная +В ней выполняются SQL-запросы из заданий; выход - `\q`. Сохраняйте исходные DAG перед опытами вне каталога `dags`: две Python-копии с одинаковым `dag_id` мешают Airflow выбрать нужную. -**Задача:** -- Измените `schedule_interval` с ежедневного на еженедельное выполнение -- Добавьте параметр `max_active_runs` для ограничения одновременных запусков -- Проверьте изменения в интерфейсе Airflow + -**Подсказка:** -- Расписание задаётся в параметрах `DAG(...)` — ищите `schedule_interval` -- Можно использовать `timedelta(weeks=1)` или cron-выражение (например, `'0 0 * * 1'`) -- `max_active_runs` — тоже параметр `DAG()` +## 1. Первый DAG -**Цель задания:** Освоить настройку расписания и параметров выполнения DAG. +Начните с исходного `hello_world_dag.py`. В нем три задачи: `start_task`, `date_task`, `end_task`. Включение DAG с расписанием может создать автоматический запуск; для опытов выбирайте свой ручной запуск. -### Задание 3: Добавление BashOperator -**Сложность:** Средняя +### 1.1. Добавить задачу -**Задача:** -- Добавьте задачу с `BashOperator`, которая создает текстовый файл в папке `/opt/airflow/data/output/` -- Настройте зависимости так, чтобы эта задача выполнялась между `date_task` и `end_task` -- Убедитесь, что файл создается при каждом запуске DAG +Основная практика. Сложность: начальная. -**Подсказка:** -- Импорт `BashOperator` уже есть в файле — посмотрите шапку -- `BashOperator` принимает параметр `bash_command` со строкой shell-команды -- Для записи в файл подойдёт обычный `echo "текст" > /path/to/file.txt` -- Проверить результат можно: `docker-compose exec airflow-webserver cat /opt/airflow/data/output/<ваш_файл>` +Запустите исходный DAG и найдите сообщения всех трех задач. Переведите приветствие на русский, затем добавьте функцию с фиктивной погодой и `PythonOperator` с `task_id="weather_task"`. Включите ее между `start_task` и `date_task`, изменив существующую цепочку зависимостей. -**Цель задания:** Научиться работать с разными типами операторов. +**Что должно получиться:** в новом запуске четыре задачи со статусом `success`, а в логе `weather_task` есть выбранный вами текст. В графе видно, какая задача должна закончиться перед ней. ---- +### 1.2. Создать файл через BashOperator -## Задания для sql_basic_dag.py +Основная практика. Сложность: начальная. -**Цель:** Освоить работу с базами данных через Airflow. +Добавьте между `date_task` и `end_task` задачу `write_note`. Импорт `BashOperator` уже есть. В `bash_command` запишите команду `echo 'Airflow works' > /opt/airflow/data/output/note.txt`. -### Задание 1: Расширение структуры таблицы -**Сложность:** Средняя +После запуска проверьте файл: -**Задача:** -- Добавьте в таблицу `students_sample` новые поля: `email`, `phone`, `registration_date` -- Модифицируйте SQL-запросы для работы с новой структурой -- Добавьте задачу для обновления существующих записей +```bash +docker compose exec airflow-webserver cat /opt/airflow/data/output/note.txt +``` -**Подсказка:** -- Новые колонки добавляются в переменную `create_table_sql` — подберите подходящие типы (VARCHAR, DATE и т.д.) -- Не забудьте обновить `insert_data_sql` — количество значений в INSERT должно совпадать с количеством колонок -- Для задачи UPDATE создайте новую SQL-переменную и новый `PostgresOperator` по аналогии с существующими -- **Подводный камень:** `TRUNCATE TABLE` не изменяет структуру. Чтобы новые колонки появились, замените его на `DROP TABLE IF EXISTS` +**Что должно получиться:** пять успешных задач и файл с текстом `Airflow works`. При повторном запуске этот файл перезаписывается. -**Цель задания:** Научиться работать с миграциями схемы базы данных. +### 1.3. Посмотреть расписание -### Задание 2: Создание отчетов -**Сложность:** Средняя +Основная практика. Сложность: начальная. -**Задача:** -- Добавьте задачу, которая создает сводный отчет по данным студентов -- Отчет должен содержать: количество студентов, средний возраст, распределение по возрасту -- Сохраните отчет в файл в папке `/opt/airflow/data/output/` +Замените `schedule_interval` на `'0 0 * * 1'`. Найдите новое расписание и следующий автоматический запуск в UI. Сопоставьте дату запуска с часовым поясом DAG. `catchup=False` оставьте как есть. -**Подсказка:** -- Здесь понадобится `PythonOperator`, а не `PostgresOperator` — потому что нужно получить результат SQL и записать в файл -- Для подключения к БД из Python-функции используйте `PostgresHook` (импорт: `from airflow.providers.postgres.hooks.postgres import PostgresHook`) -- У хука есть метод `get_conn()`, который возвращает обычное psycopg2-соединение — дальше работайте через `cursor.execute()` и `fetchone()` -- Задачу поставьте перед `drop_table` в цепочке зависимостей +**Что должно получиться:** UI показывает запуск по понедельникам в 00:00 в часовом поясе DAG. Ручной запуск по кнопке по-прежнему выполняется сразу, когда доступны ресурсы. Для проверки расписания ждать понедельника не нужно. После опыта выключите переключатель DAG. -**Цель задания:** Освоить создание аналитических отчетов в DAG'ах. +### 1.A. Ограничить одновременные запуски -### Задание 3: Работа с соединениями -**Сложность:** Средняя +По желанию. Сложность: средняя. -**Задача:** -- Создайте новое соединение к базе данных через Airflow UI -- Модифицируйте DAG для использования нового соединения -- Добавьте обработку ошибок подключения к БД +В `DAG(...)` задайте `max_active_runs=1`. В одну Python-функцию временно добавьте `time.sleep(30)` с импортом `time`. Включите DAG и запустите его вручную дважды с разными Run Id. -**Подсказка:** -- Соединения создаются в UI: Admin → Connections → "+" -- Параметры для нашей учебной БД: Host = `postgres-training`, Schema = `training`, Login = `student`, Password = `student`, Port = `5432` -- В DAG-файле соединение указывается через `postgres_conn_id` — замените значение на ваш новый Conn Id -- Для проверки ошибок попробуйте указать несуществующее соединение и посмотрите, что покажут логи +**Что должно получиться:** пока первый запуск выполняет задержку, второй не начинает свои задачи. После первого он продолжает работу. Удалите задержку и выключите DAG; ограничение можно оставить. -**Цель задания:** Научиться управлять соединениями с внешними системами. + ---- +## 2. SQL и подключения -## Задания для file_operations_dag.py +Используйте исходный `sql_basic_dag.py`. Он создает `public.students_sample`, очищает ее перед вставкой и добавляет трех студентов. После выполнения данные остаются для проверки. Повторный полный запуск снова оставит три строки. Остальные таблицы учебной БД этот DAG не меняет. -**Цель:** Освоить работу с файлами и данными в Airflow. +### 2.1. Подключиться к учебной БД -### Задание 1: Модификация генерации данных -**Сложность:** Средняя +Основная практика. Сложность: начальная. -**Задача:** -- Добавьте новые поля в генерируемые данные: `department`, `experience_years`, `education_level` -- Модифицируйте валидацию для проверки новых полей -- Добавьте фильтрацию данных по определенным критериям (например, опыт > 3 года) +Запустите DAG с готовым соединением `postgres_training`. В Admin > Connections создайте `student_postgres`: Connection Type = Postgres, Host = `postgres-training`, Database/Schema = `training`, Login = `student`, Password = `student`, Port = `5432`. Замените `postgres_conn_id` во всех SQL-задачах на свой Conn Id. -**Подсказка:** -- Генерация данных происходит в функции `generate_sample_data()` — новые поля добавляются в словарь `data` -- Для `department` подойдёт `random.choice()` со списком отделов, для `experience_years` — привяжите к возрасту (не может быть больше `age - 18`) -- Валидация — в функции `read_and_validate_data()`: добавьте `assert` по аналогии с существующими -- Фильтрацию (pandas `df[df['experience_years'] > 3]`) можно добавить в `transform_data()` и сохранить отдельным файлом +Проверьте таблицу: -**Цель задания:** Научиться работать с различными типами данных и валидацией. +```sql +SELECT name, age FROM public.students_sample ORDER BY name; +``` -### Задание 2: Создание дополнительных отчетов -**Сложность:** Средняя +**Что должно получиться:** Alice - 22, Bob - 24, Charlie - 21. Новый запуск с вашим Connection также успешен. -**Задача:** -- Создайте отчет в формате JSON с детальной статистикой по отделам -- Добавьте визуализацию данных с помощью библиотеки matplotlib (сохранение графика в файл) -- Создайте сводку по зарплатам в разных возрастных категориях +Чтобы увидеть ошибку подключения, временно укажите несуществующий Conn Id и поставьте `retries=0` в `default_args`. Запустите DAG и найдите причину в логе первой задачи. Исправьте Conn Id, верните исходное число повторов и создайте новый запуск. Ручная отметка задачи как успешной не проверяет подключение. -**Подсказка:** -- Создайте новую функцию и новый `PythonOperator`, добавьте в цепочку после `summary_task` -- Для JSON-отчёта: загрузите processed_data.csv через pandas, сгруппируйте (`groupby`) и сохраните через `json.dump()` -- matplotlib может быть не установлен в контейнере — это нормально, сделайте эту часть необязательной (обработайте `ImportError`) -- Не забудьте `import pandas as pd` внутри функции (не в шапке файла!) +### 2.2. Добавить SQL-задачу -**Цель задания:** Освоить создание комплексных отчетов и визуализацию. +Основная практика. Сложность: средняя. -### Задание 3: Оптимизация обработки -**Сложность:** Продвинутая +Добавьте `PostgresOperator` с `task_id="update_student"` между `insert_data` и `query_data`. Его SQL должен установить Alice возраст 25. Используйте то же соединение, что и остальные задачи. -**Задача:** -- Разделите обработку данных на параллельные задачи для разных отделов -- Добавьте контроль качества данных (проверка на дубликаты, аномалии) -- Реализуйте механизм повторной обработки при ошибках +**Что должно получиться:** после запуска у Alice возраст 25, остальные строки не изменились. Повторный полный запуск дает те же три строки: сначала DAG очищает таблицу, потом вставляет данные и выполняет UPDATE. -**Подсказка:** -- Параллельные задачи задаются списком: `read_task >> [task_a, task_b, task_c] >> summary_task` -- Каждая задача фильтрует DataFrame по своему отделу и сохраняет результат в отдельный файл -- Проверку дубликатов можно сделать через `df['id'].is_unique` -- Параметры `retries` и `retry_delay` можно задать как у отдельных задач, так и в `default_args` +### 2.A. Добавить колонку -**Цель задания:** Научиться оптимизировать и делать обработку данных отказоустойчивой. +По желанию. Сложность: средняя. Начните после 2.2. ---- +Добавьте `email VARCHAR(100)` в `create_table_sql` и заполните его в `insert_data_sql`. Для уже созданной таблицы сначала выполните: -## Задания для csv_to_postgres.py +```sql +ALTER TABLE public.students_sample ADD COLUMN email VARCHAR(100); +``` -**Цель:** Освоить паттерны загрузки данных (ETL) из файлов в базу данных и применение XCom. +Команда нужна один раз. `CREATE TABLE IF NOT EXISTS` не меняет существующую схему, а `TRUNCATE` очищает только строки. -### Задание 1: Расширение структуры данных -**Сложность:** Начальная +**Что должно получиться:** после запуска в таблице три строки с непустым `email`. Задание 2.B можно выполнять и без этой колонки. Удаление таблицы или всей базы для опыта не требуется. -**Задача:** -- Добавьте в генератор CSV новую колонку `status` (например, со случайными значениями 'NEW', 'PROCESSING', 'COMPLETED'). -- Обновите функцию `_create_table`, чтобы учесть новую колонку. -- Запустите DAG и проверьте, что данные успешно загрузились с новой колонкой. -- Убедитесь, что сгенерированный файл появился в каталоге `/opt/airflow/data/output/`. +### 2.B. Сохранить отчет -**Подсказка:** -- Новая колонка добавляется в трёх местах: генерация (`_generate_csv`), DDL (`_create_table`), загрузка (`_load_csv` — список колонок в COPY) -- В `_generate_csv()` для случайных значений подойдёт `random.choices(['NEW', 'PROCESSING', 'COMPLETED'], k=rows)` -- **Подводный камень:** если таблица уже существует со старой схемой, новая колонка не появится. Удалите таблицу вручную или добавьте `DROP TABLE` перед `CREATE TABLE` +По желанию. Сложность: средняя. Требуется 2.2; задание 2.A можно пропустить. -**Цель задания:** Понять процесс изменения схемы данных на всех этапах пайплайна. +После `query_data` добавьте Python-задачу. Через `PostgresHook` получите количество студентов, средний возраст и количество студентов каждого возраста. Запишите результат в `/opt/airflow/data/output/students_report.txt`. У хука есть методы `get_first()` и `get_records()`; они позволяют обойтись без ручной работы с курсором. -### Задание 2: Использование PostgresOperator -**Сложность:** Средняя +**Что должно получиться:** в файле 3 студента, средний возраст около 23.33 и по одному студенту возрастов 21, 24 и 25. Эти числа должны совпасть с SQL по `students_sample`. -**Задача:** -- Перепишите задачу `create_orders_table`. Сейчас она использует `PythonOperator` и `PostgresHook` внутри Python-функции. -- Замените её на использование стандартного `PostgresOperator`, используя соединение `postgres_training`. -- Убедитесь, что пайплайн продолжает работать корректно. + -**Подсказка:** -- Посмотрите, как устроен `sql_basic_dag.py` — там `PostgresOperator` уже используется, можно взять за образец -- `PostgresOperator` принимает `postgres_conn_id` и `sql` — SQL можно передать прямо строкой -- После замены проверьте, не осталось ли ссылок на удалённую функцию. `_get_conn()` всё ещё нужна в `_load_csv()` +## 3. Работа с файлами -**Цель задания:** Научиться использовать специализированные операторы для работы с БД вместо кастомного Python-кода. +Начните с исходного `file_operations_dag.py`. Он сам создает 100 строк в `input/sample_data.csv`; данные каждого полного запуска могут отличаться. Чужие входные файлы для этого блока не нужны. ---- +### 3.1. Изменить преобразование -## Задания для csv_to_postgres_dq.py +Основная практика. Сложность: начальная. -**Цель:** Освоить подходы к обеспечению качества данных (Data Quality) в Airflow. +Запустите DAG, откройте `data/output/processed_data.csv` и `summary.txt`. Найдите в `transform_data()` правило вычисления `salary_category`. Измените порог категории High с 70000 на 80000, оставив границу Medium равной 50000. Запустите DAG еще раз. -### Задание 1: Новая проверка качества -**Сложность:** Средняя +**Что должно получиться:** в обработанном CSV 100 строк, у каждой строки с зарплатой от 80000 категория High. Сумма количеств High, Medium и Low в отчете равна 100. Сверяйте отчет с CSV того же запуска: случайные данные не позволяют сравнивать только числа из двух отчетов. -**Задача:** -- Добавьте новую функцию проверки прямо в `csv_to_postgres_dq.py`, которая будет убеждаться, что все значения в колонке `amount` строго больше нуля. -- Добавьте вызов этой функции как новую задачу в DAG `csv_to_postgres_dq`. -- Встройте новую задачу в общую цепочку выполнения (например, перед `data_quality_summary`). +### 3.A. Добавить отдел и опыт -**Подсказка:** -- Возьмите за образец любую из существующих функций проверки (например, `_check_has_rows`) — паттерн одинаковый: подключиться, выполнить SQL, проверить результат, бросить `ValueError` если не ок -- SQL для проверки: `SELECT COUNT(*) FROM public.orders WHERE amount <= 0` -- Не забудьте создать `PythonOperator` и добавить задачу в цепочку +По желанию. Сложность: средняя. -**Цель задания:** Научиться расширять набор проверок качества данных. +В `generate_sample_data()` добавьте `department`, `experience_years`, `education_level`. Отдел выбирайте из `sales`, `support`, `engineering`; опыт должен быть от 0 до `age - 18`. Дополните `read_and_validate_data()` проверками новых полей. В `transform_data()` сохраните сотрудников с опытом больше трех лет в отдельный `output/experienced.csv`, сохранив полный `processed_data.csv`. -### Задание 2: Управление статусом при ошибках (Trigger Rules) -**Сложность:** Продвинутая +**Что должно получиться:** основной файл по-прежнему содержит 100 строк, опыт у всех допустимый, а в `experienced.csv` нет опыта 3 года и меньше. -**Задача:** -- Смоделируйте ошибку (например, временно измените данные так, чтобы проверки не прошли). -- По умолчанию, если падает одна проверка, следующие не выполняются (поведение `all_success`). -- Измените параметры задач так (с помощью `trigger_rule`), чтобы выполнялись *все* проверки, даже если некоторые из них упали. -- Сделайте так, чтобы задача `data_quality_summary` могла анализировать статусы предыдущих задач и отражать общий итог. +### 3.B. Отчет по отделам -**Подсказка:** -- Для моделирования ошибки: измените `EXPECTED_ORDERS_SCHEMA` так, чтобы схема не совпала. Запустите — увидите, что все задачи после упавшей будут skipped -- Ключевой параметр — `trigger_rule` у `PythonOperator`. Значение `"all_done"` означает: «запустись в любом случае, когда все upstream завершились (успешно или нет)» -- Для анализа статусов в `dq_summary`: функция может принимать `**context` и через `context['ti']` получить информацию о статусах предыдущих задач -- После экспериментов не забудьте вернуть `EXPECTED_ORDERS_SCHEMA` к правильным значениям +По желанию. Сложность: средняя. Требуется 3.A. -**Цель задания:** Освоить продвинутую маршрутизацию статусов задач с помощью `trigger_rule`. +Добавьте задачу после `write_summary`. Она читает полный `processed_data.csv` и пишет `output/departments.json`: для каждого отдела количество сотрудников и среднюю зарплату. Для расчета подойдет pandas `groupby`, для записи - `to_json`. ---- +**Что должно получиться:** сумма численности отделов равна 100, средние зарплаты совпадают с группировкой исходного CSV. График для этого задания не требуется. -## Задания для data_processing_dag.py +### 3.C. Разделить обработку по отделам -**Цель:** Освоить ETL процессы и работу с бизнес-логикой. +По желанию. Сложность: высокая. Требуется 3.A, отчет 3.B необязателен. -### Задание 1: Расширение ETL пайплайна -**Сложность:** Средняя +После `transform_data` поставьте три параллельные задачи. Каждая читает полный `processed_data.csv`, отбирает свой отдел, проверяет уникальность `id` и сохраняет отдельный файл. После них добавьте сборку этих трех файлов и проверьте количество строк и уникальность `id` уже в общем результате. Не перезаписывайте исходный файл из параллельных задач. -**Задача:** -- Добавьте новый источник данных — файл с информацией о продуктах -- Создайте задачу для объединения данных о заказах с информацией о продуктах -- Добавьте расчет общей выручки по продуктам +Для опыта с повтором одна из задач должна намеренно падать только на первой попытке; используйте `ti.try_number` и `retries=1`. Этот прием разобран в блоке 7. -**Подсказка:** -- В `create_sample_data()` добавьте генерацию ещё одного CSV (products.csv) с колонками `product`, `category`, `weight_kg` — значения product должны совпадать с теми, что уже есть в orders -- Создайте функцию `extract_products()` по аналогии с `extract_customers()` -- Для объединения в `transform_data()` используйте `pd.merge()` по ключу `product` -- Новую extract-задачу поставьте параллельно с существующими: `create_data_task >> [extract_customers_task, extract_orders_task, extract_products_task]` +**Что должно получиться:** объединенный результат содержит те же 100 уникальных `id`. В графе три задачи имеют общего предшественника, но не зависят друг от друга. Повтор не добавляет дубликаты: файл отдела перезаписывается. -**Цель задания:** Научиться работать с множественными источниками данных. + -### Задание 2: Расширение текстового отчёта -**Сложность:** Средняя +## 4. Загрузка CSV и проверка качества -**Задача:** -- Расширьте функцию `generate_report()` так, чтобы отчёт содержал больше полезной статистики -- Добавьте в отчёт: топ-3 самых дорогих заказа, распределение заказов по месяцам, среднюю сумму заказа по каждому клиенту -- Сохраните расширенный отчёт в `/opt/airflow/data/output/detailed_report.txt` +Здесь работают два DAG и одна таблица `public.orders`. Начните с исходных `csv_to_postgres.py` и `csv_to_postgres_dq.py`. SQL-блок использовал другую таблицу. Сначала выполняйте основные задания, затем расширяйте схему по желанию. -**Подсказка:** -- Всё делается в существующей функции `generate_report()` — дополните её -- Полезные методы pandas: `df.nlargest()`, `df.groupby(...).mean()`, `df.groupby(...).sum()` -- Можно записать результат в тот же файл или создать отдельный +### 4.1. Проследить загрузку и XCom -**Цель задания:** Освоить создание аналитических отчётов с помощью pandas. +Основная практика. Сложность: начальная. -### Задание 3: Мониторинг качества данных -**Сложность:** Продвинутая +Запустите `csv_to_postgres`. В задаче `generate_csv` откройте вкладку XCom с ключом `return_value`: там путь к CSV. Найдите этот файл в `data/output`. Затем выберите `load_csv_to_postgres`, откройте Details и раздел Rendered Templates. В `opKwargs` должен быть тот же путь, подставленный вместо шаблона параметра `csv_path`. -**Задача:** -- Добавьте проверки качества данных на каждом этапе ETL -- Создайте механизм оповещения о проблемах с данными -- Реализуйте архивирование обработанных данных +Проверьте результат в БД: -**Подсказка:** -- Создайте отдельную функцию валидации (проверка: строки > 0, нет NULL в ключевых полях, суммы положительные) и поставьте её между extract и transform -- Для оповещения используйте `on_failure_callback` в `default_args` — это функция, которая вызывается при падении любой задачи. Она получает `context` с информацией об ошибке -- Для архивирования: скопируйте результат в файл с датой в имени (например, `shutil.copy()` + `datetime.now().strftime(...)`) +```sql +SELECT COUNT(*) FROM public.orders; +SELECT order_id, amount FROM public.orders ORDER BY order_id LIMIT 5; +``` -**Цель задания:** Научиться обеспечивать качество данных в ETL процессах. +**Что должно получиться:** первый запуск на пустой таблице загружает 1000 строк. В XCom передается путь к файлу, сами строки хранятся в CSV и PostgreSQL. Каждый новый полный запуск генерирует новые заказы; после двух последовательных запусков на изначально пустой таблице будет 2000 строк. Это отличается от повторного выполнения только задачи загрузки с тем же CSV. ---- +### 4.2. Добавить проверку и увидеть ошибку -## Задания для branching_dag.py +Основная практика. Сложность: средняя. -**Цель:** Освоить условную логику и ветвление в Airflow. +После успешной загрузки отдельно запустите `csv_to_postgres_dq`. Автоматической связи между этими двумя DAG нет. -### Задание 1: Модификация условий ветвления -**Сложность:** Средняя +По образцу `_check_has_rows()` добавьте функцию и Python-задачу `check_positive_amount` перед `data_quality_summary`. Проверьте число строк, где `amount <= 0`, и бросьте `ValueError`, если такие строки есть. Новую задачу включите в существующую цепочку. -**Задача:** -- Измените условие в функции `check_data_quality()` на основе реальных критериев (например, размер файла) -- Добавьте третью ветку обработки для данных "требующих ручной проверки" -- Настройте разные триггерные правила для слияния веток +**Что должно получиться на корректных данных:** все задачи и DAG Run успешны, сводка перечисляет результаты проверок. -**Подсказка:** -- Сейчас функция выбирает ветку случайно — замените `random.random()` на проверку чего-то реального (например, `os.path.getsize()` для размера файла) -- Функция `BranchPythonOperator` возвращает `task_id` ветки для выполнения. Для третьей ветки — верните третий `task_id` -- У `merge_task` проверьте `trigger_rule` — при ветвлении непройденные ветки получают статус `skipped` +Для отрицательного сценария поменяйте знак суммы одного заказа: -**Цель задания:** Научиться создавать сложные условия ветвления. +```sql +UPDATE public.orders SET amount = -amount +WHERE order_id = (SELECT MIN(order_id) FROM public.orders); +``` -### Задание 2: Реализация реального сценария -**Сложность:** Продвинутая +Запустите только DQ-DAG. Новая проверка должна получить `failed`, сводка - `upstream_failed`, весь DAG Run - `failed`. В ее логе должно быть понятно, какое условие нарушено. У DQ-задач нет повторов: повторный запуск с теми же неверными данными не исправляет ошибку. -**Задача:** -- Создайте реальные задачи обработки для CSV и JSON форматов -- Добавьте валидацию данных в каждой ветке -- Реализуйте механизм сравнения результатов из разных веток +Восстановите сумму тем же UPDATE еще один раз и повторите DQ. Дождитесь успешного результата перед продолжением. Во время опыта не запускайте загрузчик и не удаляйте строки: восстановление относится к тому же минимальному `order_id`. -**Подсказка:** -- Вместо `print("Обработка CSV...")` загрузите реальный файл (например, `sample_data.csv`) и посчитайте статистику -- Результат каждой ветки сохраните в отдельный файл — в `merge_results()` загрузите оба (если существуют) и сравните -- Учтите, что при ветвлении выполняется только одна ветка — функция слияния должна обрабатывать случай, когда один из файлов отсутствует +### 4.A. Провести новое поле через загрузку -**Цель задания:** Применить ветвление в реальном сценарии обработки данных. +По желанию. Сложность: высокая. Требуется завершенный основной блок 4. -### Задание 3: Динамическое ветвление -**Сложность:** Продвинутая +Добавьте `status` со значениями `NEW`, `PROCESSING`, `COMPLETED`. Поле нужно провести через четыре участка: генератор CSV, DDL целевой таблицы, список колонок COPY и обе части итогового `INSERT ... SELECT` в `_load_csv()`. -**Задача:** -- Реализуйте ветвление на основе внешних параметров (например, переданных через Variables) -- Добавьте обработку случая, когда ни одна ветка не подходит -- Создайте механизм логирования выбранного пути выполнения +В DDL используйте `status VARCHAR(20) NOT NULL DEFAULT 'NEW'`. Существующую таблицу подготовьте один раз: -**Подсказка:** -- Variables создаются в UI: Admin → Variables. Для чтения в коде: `Variable.get('key', default_var='значение')` -- Для случая «ни одна ветка не подходит» добавьте `DummyOperator` как ветку-заглушку -- `BranchPythonOperator` должен всегда возвращать существующий `task_id` — иначе DAG упадёт с ошибкой +```sql +ALTER TABLE public.orders +ADD COLUMN status VARCHAR(20) NOT NULL DEFAULT 'NEW'; +``` -**Цель задания:** Освоить динамическое принятие решений в DAG'ах. +В `EXPECTED_ORDERS_SCHEMA` DQ-DAG добавьте в конец `("status", "character varying")`. После загрузки проверьте значения `status` в БД и выполните DQ. ---- +**Что должно получиться:** новые заказы содержат статусы из CSV, старые получили `NEW`, проверка схемы проходит. Проверяйте значения, а не только успешность задачи: забытый `status` в INSERT может оставить всем строкам значение по умолчанию. -## Задания для error_handling_dag.py +Завершив опыт, верните исходные четыре списка/описания колонок в загрузчике и список схемы в DQ. Затем выполните `ALTER TABLE public.orders DROP COLUMN status;` и запустите DQ. Проверку положительной суммы из 4.2 сохраните. -**Цель:** Освоить обработку ошибок и создание отказоустойчивых пайплайнов. +### 4.B. Заменить PythonOperator на PostgresOperator -### Задание 1: Настройка стратегий повторения -**Сложность:** Средняя +По желанию. Сложность: средняя. Используйте базовую схему после 4.2 или после восстановления в 4.A. -**Задача:** -- Измените параметры `retries` и `retry_delay` для разных задач -- Добавьте экспоненциальную задержку между повторными попытками -- Реализуйте кастомный обработчик ошибок для конкретных исключений +Замените задачу `create_orders_table` на `PostgresOperator` с тем же `task_id`, SQL создания таблицы и соединением `postgres_training`. Образец оператора есть в SQL-блоке. `_get_conn()` оставьте: загрузчик продолжает использовать эту функцию. -**Подсказка:** -- Параметры повторения можно задать на уровне задачи (перекроют `default_args`): `retries`, `retry_delay`, `retry_exponential_backoff`, `max_retry_delay` -- Для кастомного обработчика: параметр `on_retry_callback` принимает функцию с аргументом `context`, из которого можно получить номер попытки через `context['ti'].try_number` -- Запустите DAG несколько раз и посмотрите в логах, как меняется задержка между попытками +**Что должно получиться:** после замены DAG загружает новую порцию, затем DQ проходит. Имя задачи и цепочка зависимостей сохраняются. -**Цель задания:** Научиться настраивать стратегии обработки ошибок. +### 4.C. Выполнить все проверки после ошибки -### Задание 2: Создание комплексной обработки ошибок -**Сложность:** Продвинутая +По желанию. Сложность: высокая. Требуется 4.2. -**Задача:** -- Добавьте задачи для разных типов ошибок (сетевая ошибка, ошибка данных, системная ошибка) -- Создайте механизм эскалации ошибок (после N неудачных попыток) -- Реализуйте отправку уведомлений о критических ошибках +Временно замените ожидаемый тип `amount` в `EXPECTED_ORDERS_SCHEMA` на `integer`. Сначала запустите исходную цепочку: `check_orders_schema` упадет, последующие задачи получат `upstream_failed`. -**Подсказка:** -- Создайте функции по аналогии с `unreliable_task()`, но бросающие разные типы исключений: `ConnectionError`, `ValueError`, `OSError` -- Для эскалации используйте `on_failure_callback` — внутри проверьте `context['ti'].try_number` и при достижении порога выведите сообщение уровня CRITICAL -- Пример структуры callback: - ```python - def escalation_callback(context): - if context['ti'].try_number >= 3: - # сюда — логику эскалации - ``` +У задач после проверки схемы, включая сводку, задайте `trigger_rule="all_done"`. Повторите запуск. Прочитайте `_log_dq_summary()`: она собирает состояния всех проверок и сама падает, если хотя бы одна неуспешна. -**Цель задания:** Освоить создание комплексной системы обработки ошибок. +**Что должно получиться:** проверки строк, дубликатов и положительной суммы выполняются даже после ошибки схемы. Сводка и DAG Run остаются `failed`. Успешная завершающая задача с `all_done` могла бы скрыть отказ внутри DAG; именно поэтому сводка проверяет результат. -### Задание 3: Добавление логирования через модуль logging -**Сложность:** Средняя +Верните тип `numeric` и исходные `trigger_rule`, затем выполните успешный DQ-запуск. -**Задача:** -- Замените все `print()` в функциях DAG'а на вызовы стандартного модуля `logging` -- Добавьте в каждую функцию логирование начала и окончания выполнения -- Посмотрите, как логи отображаются в интерфейсе Airflow (вкладка Log у каждой задачи) + -**Подсказка:** -- Стандартный паттерн: `import logging` в начале файла, затем `log = logging.getLogger(__name__)` в функции -- Уровни: `log.info()` для штатных событий, `log.warning()` для предупреждений, `log.error()` для ошибок -- В UI Airflow откройте выполненную задачу → вкладка **Log** — сообщения `logging` отображаются с метками уровня и временем, в отличие от `print()` +## 5. Разбор ETL-примера -**Цель задания:** Научиться использовать стандартное логирование Python в задачах Airflow и читать логи через UI. +Начните с исходного `data_processing_dag.py`. Он сам создает свои `customers.csv` и `orders.csv`; таблица `public.orders` из блока 4 ему не нужна. Шаг `simulate_load` показывает подготовку к загрузке и печатает пример DDL. Записи в БД в этом DAG нет. ---- +### 5.1. Проследить путь данных -## Дополнительные задания: пулы, XCom, TaskGroup и алертинг +Основная практика. Сложность: начальная. -**Цель:** Освоить продвинутые возможности оркестрации — управление ресурсами, обмен данными между задачами, группировку и оповещения. +Запустите DAG. Найдите две задачи извлечения и их файлы `output/extracted_customers.csv`, `output/extracted_orders.csv`. Проверьте, что `transform_data()` читает именно их. В графе обе задачи извлечения могут выполняться параллельно, а преобразование ждет обе. -### Задание 1: Пулы и управление ресурсами -**Сложность:** Средняя +В `extract_orders()` временно оставьте только заказы с `amount >= 100` перед сохранением файла. Запустите DAG снова и сравните отчет с результатом до изменения. -**Задача:** -- В интерфейсе Airflow создайте пул `backup_pool` с 2 слотами -- Создайте новый DAG `advanced_features_dag.py` **или** расширьте `data_processing_dag.py` задачами резервного копирования (например, `backup_to_csv`, `backup_to_db`) -- Назначьте этим задачам параметр `pool="backup_pool"` и настройте `pool_slots` так, чтобы одна из задач занимала 2 слота, а другая — 1 -- Наблюдайте в UI, что одновременно запускается не более 2 задач из этого пула +**Что должно получиться:** исходный отчет содержит 6 заказов с выручкой 2105; после фильтра - 4 заказа с выручкой 2000. Лог `simulate_load` явно сообщает об имитации. Верните фильтр к исходному состоянию и запустите DAG перед дополнительными заданиями. -**Подсказка:** -- Пул создаётся в UI: Admin → Pools → "+" -- У `PythonOperator` есть параметры `pool` (имя пула) и `pool_slots` (сколько слотов занимает задача) -- Если задача занимает 2 слота из 2 доступных — вторая задача будет ждать, даже если они не связаны зависимостями +### 5.A. Добавить справочник продуктов -**Цель задания:** Научиться управлять параллелизмом задач через пулы и `pool_slots`. +По желанию. Сложность: средняя. -### Задание 2: Обмен данными через XCom -**Сложность:** Средняя +В `create_sample_data()` добавьте `input/products.csv` с полями `product`, `category`, `weight_kg`. Для каждого из шести продуктов в заказах должна быть одна строка справочника. Добавьте параллельную задачу `extract_products`, сохраняющую `output/extracted_products.csv`, и подключите ее к `transform_data`. Объедините извлеченные заказы со справочником по `product`. -**Задача:** -- В том же DAG добавьте задачу `calculate_metrics` (PythonOperator), которая возвращает словарь с агрегированными показателями, например: `{"total_orders": ..., "avg_amount": ...}` -- Добавьте задачу `log_metrics`, которая с помощью `xcom_pull` читает результат `calculate_metrics` и выводит значения в лог -- Для одной из задач продемонстрируйте использование XCom в Jinja-шаблоне (например, в `bash_command` или SQL-запросе) +**Что должно получиться:** в результате 6 заказов, у всех заполнена категория, общая выручка осталась 2105. Если JOIN размножил строки или потерял заказы, проверьте уникальность и полноту справочника. -**Подсказка:** -- Любое значение, которое функция возвращает через `return`, автоматически сохраняется в XCom -- Для чтения XCom в другой задаче: функция принимает `**context`, затем `context['ti'].xcom_pull(task_ids='имя_задачи')` -- В Jinja-шаблонах (параметры `bash_command`, `sql` и др.) XCom доступен через `{{ ti.xcom_pull(task_ids='...') }}` -- Посмотреть сохранённые XCom-значения можно в UI: откройте задачу → вкладка **XCom** +### 5.B. Расширить отчет -**Цель задания:** Освоить передачу результатов между задачами через XCom и их использование в шаблонах. +По желанию. Сложность: средняя. Справочник из 5.A не нужен. -### Задание 3: TaskGroup и алертинг -**Сложность:** Продвинутая +В `generate_report()` добавьте топ-3 заказа по сумме, выручку по месяцам и среднюю сумму заказа каждого клиента. Сохраните `output/detailed_report.txt`. -**Задача:** -- Объедините логически связанные задачи (например, `extract` / `transform` / `load`) в `TaskGroup`'ы -- Добавьте завершающую задачу `send_notification` на основе примера из раздела про алертинг (PythonOperator с `send_email_smtp` или другим механизмом уведомлений) -- Настройте для задачи уведомления `trigger_rule=TriggerRule.ALL_DONE`, чтобы уведомление отправлялось даже при частичных ошибках -- При желании вынесите функцию отправки письма в отдельный модуль `utils.py` и импортируйте её в DAG +**Что должно получиться:** топ-3 суммы - 1200, 400 и 300; все заказы относятся к октябрю 2023 года; средняя сумма заказа Alice Johnson равна 640. У исходного набора всего шесть строк, поэтому результат можно сверить вручную. -**Подсказка:** -- TaskGroup — контекстный менеджер: `with TaskGroup("имя") as group:` — внутри определяете задачи как обычно -- Зависимости работают на уровне групп: `group_a >> group_b` -- `TriggerRule.ALL_DONE` (из `airflow.utils.trigger_rule`) означает: «запустись, когда все upstream завершились — неважно, с успехом или ошибкой» -- В Graph View группы отображаются как складные блоки — удобно для больших DAG'ов +### 5.C. Валидация и архив -**Цель задания:** Научиться группировать задачи с помощью TaskGroup и строить схему оповещений о статусе пайплайна. +По желанию. Сложность: высокая. ---- +Между преобразованием и `simulate_load` добавьте проверку: результат непустой, `customer_id` и `name` заполнены, суммы положительны. В `create_sample_data()` временно укажите для одного заказа несуществующий `customer_id`. Настройте `on_failure_callback`, который пишет в лог идентификатор задачи и причину ошибки; образец есть в задании 7.B. -## Рекомендации по выполнению +После `generate_report` добавьте копирование `transformed_data.csv` в архивный файл с датой и временем в имени. Для этого подойдет `shutil.copy`. -1. **Начинайте с простых заданий** и постепенно переходите к сложным -2. **Тестируйте каждое изменение** через интерфейс Airflow -3. **Изучайте логи выполнения** для понимания поведения задач (вкладка Log у каждой задачи в UI) -4. **Экспериментируйте** с разными настройками и параметрами -5. **Не бойтесь ломать** — DAG'и можно откатить через `git checkout`, а таблицы пересоздать +**Что должно получиться:** после исчерпания повторов проверка падает, последующие шаги получают `upstream_failed`, новый архив не появляется. После возврата клиента к исходному значению весь DAG успешен, а содержимое нового архива совпадает с результатом запуска. Логи callback ищите в логах scheduler, а не только в логе задачи. -## Оценка прогресса + -- **Начальный уровень:** Выполнены задания для hello_world_dag и sql_basic_dag -- **Средний уровень:** Выполнены задания для file_operations_dag, csv_to_postgres.py, csv_to_postgres_dq.py и data_processing_dag -- **Продвинутый уровень:** Выполнены все задания, включая branching_dag, error_handling_dag и дополнительные задания по пулам, XCom, TaskGroup и алертингу +## 6. Ветвление -Удачи в изучении Apache Airflow! +Используйте исходный `branching_dag.py`. Ветки пока имитируют обработку, входные файлы не нужны. Дождитесь завершения каждого запуска перед изменением Variable. + +### 6.1. Выбрать ветку через Variable + +Основная практика. Сложность: средняя. + +В Admin > Variables создайте ключ `branch_format` со значением `csv`. Запустите DAG, затем смените значение на `json` и сделайте новый запуск. Посмотрите граф и лог `merge_results`. + +**Что должно получиться:** при `csv` CSV-ветка получает `success`, JSON-ветка - `skipped`; при `json` наоборот. `merge_results` выполняется в обоих случаях и читает XCom только текущего запуска. Правило `none_failed_min_one_success` допускает пропущенную ветку, если другая успешна. + +Для третьего запуска задайте `xml`. `choose_format` должен упасть с понятным сообщением, DAG Run - завершиться ошибкой. Верните `branch_format=csv` и проверьте успешный запуск. + +### 6.A. Добавить ветку ручной проверки + +По желанию. Сложность: высокая. + +Добавьте задачу `review_branch` и значение `review` в выбор формата. Для неизвестного значения направляйте выполнение в эту же ветку. Возвращайте из нее строку `review`, включите ее в зависимости перед слиянием и в список `task_ids` при чтении XCom в `merge_results`. + +**Что должно получиться:** при `review` или неизвестном формате выполняется только новая ветка; две остальные пропущены, слияние и весь запуск успешны. Проверьте, что `csv` и `json` продолжают работать. Верните значение `csv`. + +### 6.B. Прочитать CSV и JSON + +По желанию. Сложность: высокая. Третья ветка из 6.A не требуется. + +Создайте два входных файла в `data/input`: `branch_records.csv` и `branch_records.json`. Оба должны содержать две записи с `id` 1 и 2 и суммами `amount` 10 и 20. В функциях веток прочитайте соответствующий файл, проверьте положительность сумм и верните словарь с количеством записей и общей суммой. Слияние уже читает XCom выбранной ветки. + +**Что должно получиться:** отдельные запуски для CSV и JSON дают по 2 записи и сумму 30. Сравните их логи вручную. В каждом запуске работает одна ветка; файл результата другой ветки из старого запуска для сравнения не нужен. + + + +## 7. Ошибки и повторы + +Используйте исходный `error_handling_dag.py`. Режим задается Variable `error_mode`; по умолчанию это `retry_once`. Обе рабочие задачи имеют одну повторную попытку с задержкой 10 секунд. Каждый сценарий начинайте новым запуском и ждите его завершения перед сменой режима. + +### 7.1. Увидеть повторную попытку + +Основная практика. Сложность: начальная. + +Установите `error_mode=retry_once` и запустите DAG. Откройте `retry_task` и логи ее попыток. Первая намеренно падает, вторая завершается успешно. Состояние `up_for_retry` видно во время ожидания; если оно уже сменилось, причину и номера попыток можно найти в логах. + +**Что должно получиться:** `unreliable_task`, `retry_task` и `success_handler` успешны, `failure_handler` пропущен, весь DAG Run успешен. В логе первой попытки есть временная учебная ошибка, во второй - сообщение о выполнении. `retries=1` означает две попытки всего. + +### 7.2. Проверить окончательную ошибку + +Основная практика. Сложность: средняя. + +Добавьте `retries=0` непосредственно в оператор `retry_task`, дождитесь обновления кода и создайте новый запуск в режиме `retry_once`. + +**Что должно получиться:** `retry_task` получает `failed`, `success_handler` - `upstream_failed`, а `failure_handler` выполняется. Весь DAG Run остается `failed`, хотя обработчик ошибки выполнился успешно. + +Уберите локальное переопределение `retries`. Теперь по очереди запустите режимы `fail_first` и `fail_second`. Обработчик должен заметить отказ любой из двух задач. В графе проверьте, что каждый обработчик зависит от обеих рабочих задач. Верните `error_mode=retry_once` и получите успешный запуск. + +### 7.3. Добавить свое сообщение в лог + +Основная практика. Сложность: начальная. + +В `handle_success()` добавьте `logging.info()` с собственным сообщением о завершении. У `logging` есть уровни INFO, WARNING и ERROR; `print` тоже попадает в лог, но не позволяет явно выбрать уровень сообщения. + +**Что должно получиться:** после успешного запуска сообщение есть в логе `success_handler`. При окончательном отказе эта функция не выполняется, поэтому сообщения о всеобщем успехе нет. + +### 7.A. Сравнить задержки + +По желанию. Сложность: высокая. + +В режиме `fail_second` задайте для `retry_task` три повтора, `retry_delay=timedelta(seconds=5)` и `retry_exponential_backoff=True`. Сопоставьте время окончания неудачной попытки с началом следующей. Задержка увеличивается; планировщик может добавить время ожидания, поэтому точного удвоения по часам требовать не нужно. + +**Что должно получиться:** четыре неуспешные попытки, после них `failure_handler` и итог `failed`. Верните параметры повторов к исходным и режим к `retry_once`. + +### 7.B. Добавить callback + +По желанию. Сложность: высокая. + +Добавьте функцию `notify_failure(context)` и укажите ее в `on_failure_callback` рабочих задач. В ней используйте `logging.error` и выведите `context['ti'].task_id` и `context['exception']`. В разных опытах бросьте `ConnectionError`, `ValueError` и `OSError` с различимыми сообщениями. Настройка настоящей почты для этого задания не нужна. + +**Что должно получиться:** после исчерпания повторов callback сообщает, какая задача и почему упала. При временной ошибке с последующим успехом он не вызывается. Сообщения callback проверяйте в логах scheduler: + +```bash +docker compose logs airflow-scheduler +``` + +Если нужно наблюдать каждый повтор, для этого есть отдельный `on_retry_callback`. Верните исходные исключения и `error_mode=retry_once` перед переходом дальше. + + + +## 8. Пулы, XCom и TaskGroup + +Используйте исходный `resource_management_dag.py`. Он имитирует чтение двух источников по 20 секунд и передает небольшие числа через XCom. Ни таблицы, ни файлы из предыдущих заданий ему не нужны. + +### 8.1. Ограничить доступные слоты + +Основная практика. Сложность: средняя. + +Перед запуском в Admin > Pools создайте `training_pool` с двумя слотами. В DAG задача `read_customers` занимает два слота, `read_orders` - один. Запустите DAG и наблюдайте за задачами и пулом. + +**Что должно получиться:** эти две задачи выполняются последовательно, хотя между ними нет зависимости: вместе им нужны три слота. Во время выполнения одной другая ожидает. Если пул не создан, задачи тоже будут ждать, поэтому сначала проверьте его имя и размер. + +Поменяйте `pool_slots` у `read_customers` на 1 и повторите запуск. Теперь обе задачи могут работать одновременно, если свободны ресурсы executor. Сравните интервалы выполнения, затем верните значение 2. + +### 8.2. Добавить метрику в XCom + +Основная практика. Сложность: средняя. + +В UI откройте XCom задачи `calculate_metrics`: исходный словарь содержит `customers=20` и `orders=30`. Добавьте в возвращаемый словарь `total_rows`, равный сумме этих двух значений. Запустите DAG и проверьте XCom и лог `log_metrics`. + +**Что должно получиться:** в обоих местах `total_rows=50`. Возвращаемое Python-функцией значение сохранено под ключом `return_value` и доступно следующей задаче через `xcom_pull`. + +### 8.3. Сгруппировать задачи + +Основная практика. Сложность: средняя. + +Импортируйте `TaskGroup` из `airflow.utils.task_group`. Внутри блока `with DAG(...)` поместите определения двух задач чтения в `with TaskGroup("extract"):`. Метрики и лог оставьте снаружи, зависимости между задачами сохраните. + +После группировки полные идентификаторы станут `extract.read_customers` и `extract.read_orders`. Обновите обе ссылки в `calculate_metrics()`. + +**Что должно получиться:** в графе появилась сворачиваемая группа, внутри видны две задачи, а метрика по-прежнему равна 50. TaskGroup организует граф; ограничения пула продолжают действовать. + +### 8.A. Подставить XCom в шаблон + +По желанию. Сложность: средняя. Требуется 8.2; группировку 8.3 можно сохранить. + +Добавьте после `calculate_metrics` задачу с `BashOperator`. В `bash_command` подставьте `{{ ti.xcom_pull(task_ids='calculate_metrics')['total_rows'] }}` в команду `echo`. Назовите задачу `print_total`. + +**Что должно получиться:** в Details задачи `print_total`, в разделе Rendered Templates, уже подставлено число 50. В логе команды тоже 50. + +### 8.B. Отправить уведомление + +По желанию. Сложность: высокая. Требуется 7.B: вернитесь к `error_handling_dag.py` с callback отказа. Нужен отдельно настроенный SMTP и адрес, на который вы можете отправить тестовое письмо. В стандартном стенде SMTP не настроен; отсутствие письма здесь не означает ошибку основного маршрута. + +Используйте пример из [раздела об алертинге](../09%20-%20Пулы,%20XCom,%20TaskGroup%20и%20алертинг%20в%20Airflow.md#система-оповещений-алертинг). Добавьте отправку письма в callback отказа. Укажите DAG, задачу, идентификатор запуска и причину ошибки. Для успешного DAG Run задайте отдельный `on_success_callback` у `DAG(...)`. + +**Что должно получиться:** после постоянной ошибки приходит сообщение об отказе, после успешного запуска - об успехе. Уведомление не меняет результат обработки данных. Запуск с отказом сохраняет `failed`; ошибку доставки ищите в логах callback. Возвращение к основной практике не требует настройки SMTP: отключите почтовый callback и верните исходный режим ошибок. + +## Когда можно двигаться дальше + +Основная практика закончена, когда вы можете найти свой запуск и его логи, добавить задачу по образцу, объяснить выбранную ветку и повторную попытку, проверить файл или строки в таблице и прочитать XCom. По последнему блоку достаточно показать ожидание слотов и работающую TaskGroup. + +Задания по желанию расширяют эти опыты. Их выполнение не требуется для перехода к следующему блоку программы. -- 2.54.0 From 1277bff6908fe49692f50cf260147c8f8cfa9b02 Mon Sep 17 00:00:00 2001 From: Dmitry Dementev Date: Mon, 28 Sep 2026 09:10:09 -0400 Subject: [PATCH 4/6] =?UTF-8?q?docs(practice):=20=D1=83=D1=82=D0=BE=D1=87?= =?UTF-8?q?=D0=BD=D0=B8=D1=82=D1=8C=20=D0=B8=D0=BD=D1=81=D1=82=D1=80=D1=83?= =?UTF-8?q?=D0=BA=D1=86=D0=B8=D0=B8=20=D0=BF=D0=BE=20=D1=80=D0=B5=D0=B7?= =?UTF-8?q?=D1=83=D0=BB=D1=8C=D1=82=D0=B0=D1=82=D0=B0=D0=BC=20=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D1=85=D0=BE=D0=B6=D0=B4=D0=B5=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Уточнены порядок CSV/COPY, расположение логов callbacks, шаблонов и управления TaskGroup. Сохранены условия проверки, результаты 69 контрольных запусков и одного диагностического, SHA-256 DAG и снимки UI. Все этапы плана закрыты. Основной и дополнительный маршруты проверены на Airflow 2.9.2. SMTP проверен локальным приемником, включая отказ доставки; внешняя почта и Windows отдельно не проверялись. Тестовые контейнеры и тома удалены. --- ...ы, XCom, TaskGroup и алертинг в Airflow.md | 8 +- _attachments/practice-failed-run.png | Bin 0 -> 111611 bytes _attachments/practice-rendered-template.png | Bin 0 -> 135356 bytes _attachments/practice-task-group.png | Bin 0 -> 117728 bytes airflow-docker/dags/branching_dag.py | 4 +- airflow-docker/dags/csv_to_postgres.py | 2 + airflow-docker/educational-tasks.md | 20 ++- .../2026-09-28-practice-improvement-plan.md | 117 ++++++++++++------ .../validation/2026-09-28-practice-runs.jsonl | 71 +++++++++++ docs/validation/2026-09-28-practice.md | 69 +++++++++++ 10 files changed, 240 insertions(+), 51 deletions(-) create mode 100644 _attachments/practice-failed-run.png create mode 100644 _attachments/practice-rendered-template.png create mode 100644 _attachments/practice-task-group.png create mode 100644 docs/validation/2026-09-28-practice-runs.jsonl create mode 100644 docs/validation/2026-09-28-practice.md diff --git a/09 - Пулы, XCom, TaskGroup и алертинг в Airflow.md b/09 - Пулы, XCom, TaskGroup и алертинг в Airflow.md index f197074..45f6356 100644 --- a/09 - Пулы, XCom, TaskGroup и алертинг в Airflow.md +++ b/09 - Пулы, XCom, TaskGroup и алертинг в Airflow.md @@ -236,7 +236,13 @@ def notify_failure(context): Укажите `on_failure_callback=notify_failure` в рабочем операторе. Функция вызывается после окончательного отказа, когда повторы исчерпаны. Для сообщения о предстоящем повторе существует `on_retry_callback`. Например, при `retries=2` задача может выполниться три раза: исходная попытка и два повтора. -Callback запускается при реальном выполнении задачи. Ручная смена статуса в UI его не проверяет. Смотрите лог самой задачи и логи scheduler; ошибки callback ищите в логах scheduler. [Документация Airflow 2.9.2 о callbacks](https://airflow.apache.org/docs/apache-airflow/2.9.2/administration-and-deployment/logging-monitoring/callbacks.html). +Callback запускается при реальном выполнении задачи. Ручная смена статуса в UI его не проверяет. В этом стенде сообщение callback оператора видно в Logs последней попытки задачи. Callback, назначенный самому DAG, пишет в файл логов планировщика. Например, для `error_handling_dag.py`: + +```bash +docker compose exec airflow-scheduler cat /opt/airflow/logs/scheduler/latest/error_handling_dag.py.log +``` + +Этот файл может содержать несколько запусков: сверяйте `run_id` и время. Обычный `docker compose logs airflow-scheduler` не показывает все записи из него. Виды событий описаны в [документации Airflow 2.9.2 о callbacks](https://airflow.apache.org/docs/apache-airflow/2.9.2/administration-and-deployment/logging-monitoring/callbacks.html). ### Отправка почты по желанию diff --git a/_attachments/practice-failed-run.png b/_attachments/practice-failed-run.png new file mode 100644 index 0000000000000000000000000000000000000000..19359ba33ceb3931a620a68a1d1017892c2f53fc GIT binary patch literal 111611 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

3m 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

HhNvJ88tm|vw{Rejl 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

f%%teYo_O)Sl*Ktdw2#e{mQNSsr$~j3evkPO`K1 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 AssI20 literal 0 HcmV?d00001 diff --git a/airflow-docker/dags/branching_dag.py b/airflow-docker/dags/branching_dag.py index 4be49bf..b30587e 100644 --- a/airflow-docker/dags/branching_dag.py +++ b/airflow-docker/dags/branching_dag.py @@ -16,7 +16,9 @@ def choose_format(): file_format = Variable.get("branch_format", default_var="csv") branches = {"csv": "process_csv_branch", "json": "process_json_branch"} if file_format not in branches: - raise ValueError(f"branch_format должен быть csv или json, получено: {file_format}") + raise ValueError( + f"branch_format должен быть csv или json, получено: {file_format}" + ) logging.info("Выбран формат: %s", file_format) return branches[file_format] diff --git a/airflow-docker/dags/csv_to_postgres.py b/airflow-docker/dags/csv_to_postgres.py index 9aa14c2..76e8fe0 100644 --- a/airflow-docker/dags/csv_to_postgres.py +++ b/airflow-docker/dags/csv_to_postgres.py @@ -121,6 +121,8 @@ def _load_csv(csv_path: str) -> None: cur.execute( "CREATE TEMP TABLE tmp_orders (LIKE public.orders INCLUDING DEFAULTS) ON COMMIT DROP;" ) + # Порядок полей CSV должен совпадать со списком COPY. + # HEADER пропускает заголовок, а не сопоставляет имена колонок. cur.copy_expert( "COPY tmp_orders (order_id, order_ts, customer_id, amount) FROM STDIN WITH CSV HEADER", f, diff --git a/airflow-docker/educational-tasks.md b/airflow-docker/educational-tasks.md index 8599f62..78f0c69 100644 --- a/airflow-docker/educational-tasks.md +++ b/airflow-docker/educational-tasks.md @@ -165,7 +165,7 @@ ALTER TABLE public.students_sample ADD COLUMN email VARCHAR(100); После `transform_data` поставьте три параллельные задачи. Каждая читает полный `processed_data.csv`, отбирает свой отдел, проверяет уникальность `id` и сохраняет отдельный файл. После них добавьте сборку этих трех файлов и проверьте количество строк и уникальность `id` уже в общем результате. Не перезаписывайте исходный файл из параллельных задач. -Для опыта с повтором одна из задач должна намеренно падать только на первой попытке; используйте `ti.try_number` и `retries=1`. Этот прием разобран в блоке 7. +Для опыта с повтором одна из задач должна намеренно падать только на первой попытке; используйте `ti.try_number`, `retries=1` и `retry_delay=timedelta(seconds=2)`. Этот прием разобран в блоке 7. **Что должно получиться:** объединенный результат содержит те же 100 уникальных `id`. В графе три задачи имеют общего предшественника, но не зависят друг от друга. Повтор не добавляет дубликаты: файл отдела перезаписывается. @@ -179,7 +179,7 @@ ALTER TABLE public.students_sample ADD COLUMN email VARCHAR(100); Основная практика. Сложность: начальная. -Запустите `csv_to_postgres`. В задаче `generate_csv` откройте вкладку XCom с ключом `return_value`: там путь к CSV. Найдите этот файл в `data/output`. Затем выберите `load_csv_to_postgres`, откройте Details и раздел Rendered Templates. В `opKwargs` должен быть тот же путь, подставленный вместо шаблона параметра `csv_path`. +Запустите `csv_to_postgres`. В задаче `generate_csv` откройте вкладку XCom с ключом `return_value`: там путь к CSV. Найдите этот файл в `data/output`. Затем выберите `load_csv_to_postgres`, откройте Details и прокрутите до Rendered Templates. В `opKwargs` вместо шаблона параметра `csv_path` появился путь к файлу. Часть пути в UI может выглядеть как `***`; сравните имя CSV с XCom. Проверьте результат в БД: @@ -215,7 +215,7 @@ WHERE order_id = (SELECT MIN(order_id) FROM public.orders); По желанию. Сложность: высокая. Требуется завершенный основной блок 4. -Добавьте `status` со значениями `NEW`, `PROCESSING`, `COMPLETED`. Поле нужно провести через четыре участка: генератор CSV, DDL целевой таблицы, список колонок COPY и обе части итогового `INSERT ... SELECT` в `_load_csv()`. +Добавьте `status` со значениями `NEW`, `PROCESSING`, `COMPLETED`. Поле нужно провести через четыре участка: генератор CSV, DDL целевой таблицы, список колонок COPY и обе части итогового `INSERT ... SELECT` в `_load_csv()`. Во всех списках добавляйте его в конец. Порядок колонок CSV должен совпадать с COPY: `CSV HEADER` пропускает первую строку, но не сопоставляет поля по ее именам. В DDL используйте `status VARCHAR(20) NOT NULL DEFAULT 'NEW'`. Существующую таблицу подготовьте один раз: @@ -228,7 +228,7 @@ ADD COLUMN status VARCHAR(20) NOT NULL DEFAULT 'NEW'; **Что должно получиться:** новые заказы содержат статусы из CSV, старые получили `NEW`, проверка схемы проходит. Проверяйте значения, а не только успешность задачи: забытый `status` в INSERT может оставить всем строкам значение по умолчанию. -Завершив опыт, верните исходные четыре списка/описания колонок в загрузчике и список схемы в DQ. Затем выполните `ALTER TABLE public.orders DROP COLUMN status;` и запустите DQ. Проверку положительной суммы из 4.2 сохраните. +Завершив опыт, удалите `status` из генератора, DDL, COPY и обеих частей INSERT ... SELECT. Уберите его и из `EXPECTED_ORDERS_SCHEMA` в DQ. Затем выполните `ALTER TABLE public.orders DROP COLUMN status;` и запустите DQ. Проверку положительной суммы из 4.2 сохраните. ### 4.B. Заменить PythonOperator на PostgresOperator @@ -286,11 +286,11 @@ ADD COLUMN status VARCHAR(20) NOT NULL DEFAULT 'NEW'; По желанию. Сложность: высокая. -Между преобразованием и `simulate_load` добавьте проверку: результат непустой, `customer_id` и `name` заполнены, суммы положительны. В `create_sample_data()` временно укажите для одного заказа несуществующий `customer_id`. Настройте `on_failure_callback`, который пишет в лог идентификатор задачи и причину ошибки; образец есть в задании 7.B. +Между преобразованием и `simulate_load` добавьте проверку: результат непустой, `customer_id` и `name` заполнены, суммы положительны. Для этой задачи задайте `retry_delay=timedelta(seconds=2)`, сохранив один повтор. В `create_sample_data()` временно укажите для одного заказа несуществующий `customer_id`. Настройте `on_failure_callback`, который пишет в лог идентификатор задачи и причину ошибки; образец есть в задании 7.B. После `generate_report` добавьте копирование `transformed_data.csv` в архивный файл с датой и временем в имени. Для этого подойдет `shutil.copy`. -**Что должно получиться:** после исчерпания повторов проверка падает, последующие шаги получают `upstream_failed`, новый архив не появляется. После возврата клиента к исходному значению весь DAG успешен, а содержимое нового архива совпадает с результатом запуска. Логи callback ищите в логах scheduler, а не только в логе задачи. +**Что должно получиться:** после исчерпания повторов проверка падает, последующие шаги получают `upstream_failed`, новый архив не появляется. После возврата клиента к исходному значению весь DAG успешен, а содержимое нового архива совпадает с результатом запуска. Сообщение callback этой проверки находится в логе ее последней попытки. @@ -370,11 +370,7 @@ ADD COLUMN status VARCHAR(20) NOT NULL DEFAULT 'NEW'; Добавьте функцию `notify_failure(context)` и укажите ее в `on_failure_callback` рабочих задач. В ней используйте `logging.error` и выведите `context['ti'].task_id` и `context['exception']`. В разных опытах бросьте `ConnectionError`, `ValueError` и `OSError` с различимыми сообщениями. Настройка настоящей почты для этого задания не нужна. -**Что должно получиться:** после исчерпания повторов callback сообщает, какая задача и почему упала. При временной ошибке с последующим успехом он не вызывается. Сообщения callback проверяйте в логах scheduler: - -```bash -docker compose logs airflow-scheduler -``` +**Что должно получиться:** после исчерпания повторов callback сообщает, какая задача и почему упала. При временной ошибке с последующим успехом он не вызывается. В этом примере callback назначен оператору: откройте Logs упавшей задачи и выберите последнюю попытку. После сообщения об отказе найдите свой текст из callback. Если нужно наблюдать каждый повтор, для этого есть отдельный `on_retry_callback`. Верните исходные исключения и `error_mode=retry_once` перед переходом дальше. @@ -410,7 +406,7 @@ docker compose logs airflow-scheduler После группировки полные идентификаторы станут `extract.read_customers` и `extract.read_orders`. Обновите обе ссылки в `calculate_metrics()`. -**Что должно получиться:** в графе появилась сворачиваемая группа, внутри видны две задачи, а метрика по-прежнему равна 50. TaskGroup организует граф; ограничения пула продолжают действовать. +**Что должно получиться:** в графе появилась сворачиваемая группа. Нажмите на ее название со стрелкой, чтобы увидеть две задачи внутри. Метрика по-прежнему равна 50; ограничения пула продолжают действовать. ### 8.A. Подставить XCom в шаблон diff --git a/docs/specs/2026-09-28-practice-improvement-plan.md b/docs/specs/2026-09-28-practice-improvement-plan.md index 7bdbd41..f249d24 100644 --- a/docs/specs/2026-09-28-practice-improvement-plan.md +++ b/docs/specs/2026-09-28-practice-improvement-plan.md @@ -1,30 +1,38 @@ # План обновления практики по Airflow -Дата: 2026-09-28. Подход согласован с автором курса. Реализация не начата; продолжение — первый незакрытый пункт в разделе «Порядок работы». +Дата: 2026-09-28. Подход согласован с автором курса. Реализация завершена. Результаты и границы проверки сохранены в [протоколе](../validation/2026-09-28-practice.md). -Этот документ сохраняет решения и порядок дальнейшей работы. Текущие задания находятся в [educational-tasks.md](../../airflow-docker/educational-tasks.md), запуск стенда описан в [README](../../airflow-docker/README.md). +Этот документ сохраняет решения, порядок доработки и результаты проверки. Текущие задания находятся в [educational-tasks.md](../../airflow-docker/educational-tasks.md), запуск стенда описан в [README](../../airflow-docker/README.md). ## Цель и границы Стенд знакомит студентов с Apache Airflow и входит в [программу Data Engineering](https://de.dementev.space/). Перед ним студенты проходят Python, pandas, SQL и Docker. Самостоятельное построение цельных процессов обработки данных относится к последующим курсовым работам. -Результат этого блока: студент умеет запустить готовый DAG, найти его граф и логи, внести небольшое изменение по образцу и объяснить наблюдаемое поведение. Практика остаётся набором небольших экспериментов с инструментом. +Результат этого блока: студент умеет запустить готовый DAG, найти его граф и логи, внести небольшое изменение по образцу и объяснить наблюдаемое поведение. Практика остается набором небольших экспериментов с инструментом. -Обновление сохраняет ознакомительный формат и версию Airflow 2.9.x. Основной маршрут выполняется на существующем стенде; внешние интеграции относятся к заданиям по желанию. Учебная имитация отдельных операций допустима, если описание и логи честно называют её имитацией. +Обновление сохраняет ознакомительный формат и версию Airflow 2.9.x. Основной маршрут выполняется на существующем стенде; внешние интеграции относятся к заданиям по желанию. Учебная имитация отдельных операций допустима, если описание и логи честно называют ее имитацией. ## Порядок работы -Перед продолжением сверить чек-лист с текущими файлами и историей Git. Прочитать согласованные решения ниже, затем выполнить первый незавершённый этап. Локальные решения в этих границах принимать и записывать в рабочей таблице; противоречия с согласованной целью выносить на обсуждение. +Перед продолжением сверить чек-лист с текущими файлами и историей Git. Прочитать согласованные решения ниже, затем выполнить первый незавершенный этап. Локальные решения в этих границах принимать и записывать в рабочей таблице; противоречия с согласованной целью выносить на обсуждение. - [x] Зафиксировать подход и находки в этом документе; добавить ссылки из README стенда и AGENTS.md. -- [ ] **1. Разобрать все исходные задания.** Создать здесь рабочую таблицу с колонками: исходное задание (DAG/раздел и номер), изучаемая возможность, исходное состояние и зависимости, решение с обоснованием, место в будущем маршруте, наблюдаемый результат. Решения: оставить основным, разделить, перенести в дополнительные или переформулировать. Исходный набор — 25 заданий в `educational-tasks.md` на коммите `13e3102`; после переработки число упражнений может измениться. **Готово:** учтены 25 из 25 исходных заданий, у каждой части разделённого задания определена обязательность, а все зависимости основного маршрута обеспечены без выполнения дополнительных упражнений. Сопоставление с главами учебника объясняет выбор тем; новые темы добавляются только при выявленной необходимости для цели блока. -- [ ] **2. Проверить и исправить несогласованности.** Сверить каждую строку раздела «Найденные несогласованности и препятствия» с актуальным кодом. Для каждой записать итог: исправлено, не подтвердилось с объяснением или оставлено как явно описанный учебный сценарий. **Готово:** все находки разобраны; подтверждённые ошибки устранены, изменённое поведение проверено на версии стенда. Невыполненная проверка остаётся открытой. -- [ ] **3. Переработать показательный блок обработки ошибок.** Подготовить основной управляемый эксперимент с повтором, наблюдаемые результаты и отдельное усложнение по желанию. **Готово:** исходное состояние и возврат к нему описаны; воспроизведены успех после повтора и окончательная ошибка; логи, состояния задач и итог DAG Run совпадают с описанием. Этот блок служит образцом объёма и оформления остальных упражнений. -- [ ] **4. Переработать остальные блоки.** Применить выбранное распределение и образец к остальным заданиям. **Готово:** каждая строка рабочей таблицы ссылается на итоговое упражнение или содержит причину объединения; у каждого упражнения есть обязательность и проверяемый результат; подготовка и восстановление общих данных описаны там, где нужны. -- [ ] **5. Согласовать навигацию.** Обновить маршрут и оценку прогресса в `educational-tasks.md`, корневом README и README стенда. Сверить `dag-specifications.md` с итоговым содержанием. **Готово:** ссылки ведут к актуальным упражнениям; документы согласованно описывают порядок и условия завершения; устаревшие прототипы и необоснованные оценки времени удалены или исправлены. -- [ ] **6. Пройти маршрут двумя способами.** Выполнить только основную практику, затем дополнительные упражнения с переходами обратно к основной. **Готово:** обе последовательности пройдены в изолированном окружении; указанные результаты подтверждены в данных и UI; сохранены условия запусков, результаты и ограничения по правилам раздела «Как проверять результат доработки». +- [x] **1. Разобрать все исходные задания.** Создать здесь рабочую таблицу с колонками: исходное задание (DAG/раздел и номер), изучаемая возможность, исходное состояние и зависимости, решение с обоснованием, место в будущем маршруте, наблюдаемый результат. Решения: оставить основным, разделить, перенести в дополнительные или переформулировать. Исходный набор - 25 заданий в `educational-tasks.md` на коммите `13e3102`; после переработки число упражнений может измениться. **Готово:** учтены 25 из 25 исходных заданий, у каждой части разделенного задания определена обязательность, а все зависимости основного маршрута обеспечены без выполнения дополнительных упражнений. Сопоставление с главами учебника объясняет выбор тем; новые темы добавляются только при выявленной необходимости для цели блока. +- [x] **2. Проверить и исправить несогласованности.** Сверить каждую строку раздела "Найденные несогласованности и препятствия" с актуальным кодом. Для каждой записать итог: исправлено, не подтвердилось с объяснением или оставлено как явно описанный учебный сценарий. **Готово:** все находки разобраны; подтвержденные ошибки устранены, измененное поведение проверено на версии стенда. Невыполненная проверка остается открытой. +- [x] **3. Переработать показательный блок обработки ошибок.** Подготовить основной управляемый эксперимент с повтором, наблюдаемые результаты и отдельное усложнение по желанию. **Готово:** исходное состояние и возврат к нему описаны; воспроизведены успех после повтора и окончательная ошибка; логи, состояния задач и итог DAG Run совпадают с описанием. Этот блок служит образцом объема и оформления остальных упражнений. +- [x] **4. Переработать остальные блоки.** Применить выбранное распределение и образец к остальным заданиям. **Готово:** каждая строка рабочей таблицы ссылается на итоговое упражнение или содержит причину объединения; у каждого упражнения есть обязательность и проверяемый результат; подготовка и восстановление общих данных описаны там, где нужны. +- [x] **5. Согласовать навигацию.** Обновить маршрут и оценку прогресса в `educational-tasks.md`, корневом README и README стенда. Сверить `dag-specifications.md` с итоговым содержанием. **Готово:** ссылки ведут к актуальным упражнениям; документы согласованно описывают порядок и условия завершения; устаревшие прототипы и необоснованные оценки времени удалены или исправлены. +- [x] **6. Пройти маршрут двумя способами.** Выполнить только основную практику, затем дополнительные упражнения с переходами обратно к основной. **Готово:** обе последовательности пройдены в изолированном окружении; указанные результаты подтверждены в данных и UI; сохранены условия запусков, результаты и ограничения по правилам раздела "Как проверять результат доработки". -По мере выполнения обновлять рабочую таблицу и чек-лист. У закрытого этапа указывать ссылку на коммит или запись о проверке. Если часть проверок недоступна, записать причину и продолжать независимую работу; соответствующий этап остаётся незавершённым. +По мере выполнения обновлять рабочую таблицу и чек-лист. У закрытого этапа указывать ссылку на коммит или запись о проверке. Если часть проверок недоступна, записать причину и продолжать независимую работу; соответствующий этап остается незавершенным. + +## Текущее состояние реализации + +Исправления DAG сохранены в `cf1b770`, задания и навигация в `d8dfe83`. Последующие уточнения и этот протокол входят в завершающий коммит ветки `work/airflow-practice-refresh`. + +Все 25 исходных заданий сопоставлены с 34 упражнениями: 16 основных и 18 по желанию. Основной маршрут пройден без дополнительных упражнений. Затем проверены дополнительные расширения и возвраты к базовой схеме, режимам и настройкам. Сохранены [условия и результаты проверки](../validation/2026-09-28-practice.md), [состояния 70 контрольных и диагностических запусков](../validation/2026-09-28-practice-runs.jsonl) и снимки UI. + +Открытых этапов реализации нет. При следующей правке сверять затронутые задания с кодом и протоколом. Проверка не измеряет трудность на студентах; SMTP проверен локальным приемником, без внешнего почтового провайдера. ## Согласованные решения @@ -32,61 +40,96 @@ 2. **Ясное начало каждого блока.** Указаны исходный DAG, необходимые файлы, таблицы и настройки, а также изменения, которые остаются от предыдущей работы. Блок определяется учебным смыслом: загрузка CSV и последующая DQ-проверка могут объединять два DAG. 3. **Основная практика и задания по желанию.** Обязательность отделена от сложности. Дополнительные задания расположены в конце своего блока; их результаты не нужны для продолжения основной практики. Пропуск дополнительного задания не препятствует завершению блока. 4. **Разделение смешанных заданий.** Если одно упражнение объединяет знакомство с возможностью Airflow и значительное усложнение, оно разделяется. Например, TaskGroup и настройка настоящей отправки писем не должны быть одним обязательным заданием. -5. **Наблюдаемый результат.** У каждого задания есть раздел «Что должно получиться»: конкретное поведение в UI, сообщение в логах или результат в файле/таблице. Преднамеренная ошибка может быть правильным результатом упражнения. +5. **Наблюдаемый результат.** У каждого задания есть раздел "Что должно получиться": конкретное поведение в UI, сообщение в логах или результат в файле/таблице. Преднамеренная ошибка может быть правильным результатом упражнения. 6. **Управляемые учебные сценарии.** Основные примеры ветвления и повторных попыток позволяют гарантированно увидеть нужный случай. Случайность допустима в данных, если она не мешает наблюдать изучаемое поведение и проверять результат. -7. **Понятное завершение.** Основная практика даёт достаточное знакомство для перехода дальше по программе. Выполнение всех дополнительных упражнений не является скрытым условием прохождения курса. +7. **Понятное завершение.** Основная практика дает достаточное знакомство для перехода дальше по программе. Выполнение всех дополнительных упражнений не является скрытым условием прохождения курса. Дополнительное задание, меняющее общую таблицу или настройку, должно описывать возврат к состоянию, необходимому следующему блоку. Перенос задания в конец раздела сам по себе эту проблему не решает. Восстановление касается только объектов упражнения, без общего сброса стенда или чужих изменений. ## Как оформлять задания -Сохраняется один основной документ для студентов — `educational-tasks.md`. Не нужно дублировать инструкции в отдельном сборнике дополнительных упражнений. +Сохраняется один основной документ для студентов - `educational-tasks.md`. Не нужно дублировать инструкции в отдельном сборнике дополнительных упражнений. В начале блока: связь с главами учебника, необходимые исходные условия и короткий запуск готового примера. У задания: -- пометка «Основная практика» или «По желанию»; сложность указывается отдельно; +- пометка "Основная практика" или "По желанию"; сложность указывается отдельно; - конкретное изменение, позволяющее познакомиться с одной возможностью Airflow; - подсказка по необходимости; -- «Что должно получиться» с указанием места проверки; -- для экспериментов с ошибками — способ вызвать ошибку и вернуть нормальное поведение; +- "Что должно получиться" с указанием места проверки; +- для экспериментов с ошибками - способ вызвать ошибку и вернуть нормальное поведение; - при необходимости один короткий вопрос о причине наблюдаемого результата. -Для ветвления и обработки ошибок проверяются статусы отдельных задач и всего DAG Run. Формулировка «всё зелёное» не подходит для сценария с ожидаемыми пропусками или ошибками. Проверка результата не должна требовать ожидания недельного расписания или неограниченного числа случайных запусков. +Для ветвления и обработки ошибок проверяются статусы отдельных задач и всего DAG Run. Формулировка "всё зеленое" не подходит для сценария с ожидаемыми пропусками или ошибками. Проверка результата не должна требовать ожидания недельного расписания или неограниченного числа случайных запусков. -## Предварительное распределение содержания +## Распределение содержания -Это начальная гипотеза для этапа 1. Окончательное распределение определяется рабочей таблицей после проверки зависимостей; правила прохождения находятся в разделе «Согласованные решения». +Распределение уточнено после разбора 25 исходных заданий. Номера итоговых упражнений указаны в рабочей таблице ниже; правила прохождения находятся в разделе "Согласованные решения". | Блок | Основная практика | Возможное усложнение по желанию | |---|---|---| | Первый DAG | Запуск, граф, логи, добавление задачи по образцу, зависимости, знакомство с расписанием | Дополнительные эксперименты с ограничениями запусков | -| SQL и Connections | Выполнение SQL, смена подключения, наблюдение результата и ошибки подключения | Дополнительные отчёты и расширение модели данных | -| Файлы и преобразования | Проследить путь файла между задачами, изменить простое преобразование или проверку | Новые поля, графики, отчёты, обработка по отделам | +| SQL и Connections | Выполнение SQL, смена подключения, наблюдение результата и ошибки подключения | Дополнительные отчеты и расширение модели данных | +| Файлы и преобразования | Проследить путь файла между задачами, изменить простое преобразование или проверку | Новые поля, отчеты, обработка по отделам | | CSV → PostgreSQL → DQ | Проследить загрузку, найти путь к файлу в XCom, добавить проверку, увидеть корректные и ошибочные данные | Расширение схемы с согласованным изменением загрузчика и DQ, общая сводка проверок | | Пример ETL | Прочитать граф с параллельными задачами, найти промежуточные и итоговые результаты | Новый источник, расширенная аналитика, архивирование | | Ветвление и Variables | Управляемо выбрать ветку, увидеть `skipped`, понять слияние веток | Третья ветка и полноценная обработка разных форматов | | Ошибки и повторы | Вызвать ошибку, увидеть повтор и успешное завершение, найти причину в логах | Экспоненциальная задержка, разные исключения, эскалация | -| Пулы, TaskGroup и оповещения | Короткие отдельные эксперименты: ожидание слотов, группировка существующих задач, сообщение обработчика ошибки | Настоящая отправка писем и более сложные сценарии | +| Пулы, XCom и TaskGroup | Ожидание слотов, новая метрика XCom, группировка существующих задач | XCom в Bash-шаблоне; настоящая отправка писем на примере с ошибками | Чтение и изменение XCom не должно зависеть от выполнения дополнительного задания по резервному копированию. Для знакомства с пулами достаточно готовых задач с наблюдаемой длительностью; написание механизма резервного копирования не является целью такого эксперимента. +## Разбор 25 исходных заданий + +Номера слева относятся к версии `13e3102`, номера справа - к новой редакции `educational-tasks.md`. Состав основной практики покрывает темы глав 4-9; главы 1-3 дают контекст перед запуском. Обработка по интервалам и самостоятельный проект остаются следующей ступенью программы. + +| Исходное задание | Возможность | Вход и зависимости | Решение и причина | Новое место | Наблюдаемый результат | +|---|---|---|---|---|---| +| hello_world / 1 | PythonOperator, зависимости | Исходный DAG | Основное: небольшое изменение графа | [1.1](../../airflow-docker/educational-tasks.md#first-dag) | Новая задача и ее сообщение видны в UI | +| hello_world / 2 | Расписание, ограничения запусков | После 1.1 | Разделить: расписание основное, конкуренция по желанию | [1.3, 1.A](../../airflow-docker/educational-tasks.md#first-dag) | Видно расписание; второй запуск ожидает при лимите 1 | +| hello_world / 3 | BashOperator | После 1.1 | Основное: другой оператор и файл | [1.2](../../airflow-docker/educational-tasks.md#first-dag) | Файл содержит ожидаемый текст | +| sql_basic / 1 | SQL в задаче, схема таблицы | Исходный SQL-DAG и training | Разделить: UPDATE основное, схема по желанию | [2.2, 2.A](../../airflow-docker/educational-tasks.md#sql) | Возраст изменен; дополнительная колонка заполнена | +| sql_basic / 2 | PostgresHook и отчет | После 2.2, таблица с тремя строками | По желанию: дополнительная обработка | [2.B](../../airflow-docker/educational-tasks.md#sql) | Файл: 3 студента, средний возраст 23.33 после UPDATE | +| sql_basic / 3 | Connections и диагностика | Учебная БД, собственный Conn Id | Основное; вместо неопределенного обработчика - найти и исправить ошибку подключения | [2.1](../../airflow-docker/educational-tasks.md#sql) | Ошибка видна в логах, после исправления запуск успешен | +| file_operations / 1 | Генерация, валидация | Исходный файловый DAG | Разделить: простая трансформация основная, новые поля по желанию | [3.1, 3.A](../../airflow-docker/educational-tasks.md#files) | 100 обработанных строк; поля и фильтр проверены отдельно | +| file_operations / 2 | Отчет по отделам | Требует 3.A | По желанию; график исключен, поскольку требует отдельной зависимости и не знакомит с новой возможностью Airflow | [3.B](../../airflow-docker/educational-tasks.md#files) | Сумма численности отделов равна числу строк входа | +| file_operations / 3 | Параллельность, DQ, retries | Требует 3.A | По желанию; параллельность также видна в основном ETL-примере | [3.C](../../airflow-docker/educational-tasks.md#files) | Раздельные файлы покрывают все отделы, сборка читает их | +| csv_to_postgres / 1 | Проведение поля через загрузку | После основной загрузки и DQ | По желанию: согласовать все четыре SQL/CSV-участка и проверку схемы | [4.A](../../airflow-docker/educational-tasks.md#load-dq) | status заполнен; DQ проходит; исходная схема восстановлена | +| csv_to_postgres / 2 | Специализированный оператор | Базовая схема orders | По желанию: сравнение двух реализаций уже знакомого действия | [4.B](../../airflow-docker/educational-tasks.md#load-dq) | После замены оператора загрузка и DQ успешны | +| csv_to_postgres_dq / 1 | Новая проверка, ошибка данных | Основная загрузка orders | Основное: положительный и отрицательный сценарии | [4.2](../../airflow-docker/educational-tasks.md#load-dq) | Отрицательная сумма вызывает failed; восстановление возвращает success | +| csv_to_postgres_dq / 2 | Trigger Rules и итог DAG Run | После 4.2 | По желанию: запуск всех проверок и общая сводка | [4.C](../../airflow-docker/educational-tasks.md#load-dq) | Все проверки завершаются, итог сохраняет ошибку | +| data_processing / 1 | Несколько источников | Исходный ETL, свои CSV | По желанию; основной разбор графа добавлен перед расширениями | [5.1, 5.A](../../airflow-docker/educational-tasks.md#etl) | После JOIN остаются 6 заказов и выручка 2105 | +| data_processing / 2 | Отчет pandas | После основного запуска, products не нужен | По желанию | [5.B](../../airflow-docker/educational-tasks.md#etl) | Топ заказов и средние сверяются с шестью исходными строками | +| data_processing / 3 | Валидация, callback, архив | После 5.1; поля продуктов необязательны | По желанию с конкретной ошибкой и восстановлением | [5.C](../../airflow-docker/educational-tasks.md#etl) | При ошибке архив не создается; после исправления совпадает с результатом | +| branching / 1 | Выбор ветки и слияние | Исходный DAG, Variable branch_format | Разделить: две управляемые ветки основные, третья по желанию | [6.1, 6.A](../../airflow-docker/educational-tasks.md#branching) | Выбранная ветка success, другая skipped, слияние success | +| branching / 2 | Работа выбранной ветки с данными | Два явно заданных входных файла | По желанию; использовать XCom текущего запуска вместо старых файлов другой ветки | [6.B](../../airflow-docker/educational-tasks.md#branching) | Оба формата дают одинаковые агрегаты в отдельных запусках | +| branching / 3 | Variables, неизвестный параметр | После 6.1 | Variable включена в основное; fallback объединен с третьей веткой | [6.1, 6.A](../../airflow-docker/educational-tasks.md#branching) | Неизвестное значение дает понятную ошибку либо review-ветку после расширения | +| error_handling / 1 | Повторы и задержка | Исходный DAG, Variable error_mode | Разделить: один управляемый повтор основной, backoff по желанию | [7.1, 7.2, 7.A](../../airflow-docker/educational-tasks.md#errors) | Две попытки; с нулем повторов - failed | +| error_handling / 2 | Типы ошибок и оповещение | После 7.2 | По желанию; локальный callback без внешней почты | [7.B](../../airflow-docker/educational-tasks.md#errors) | После исчерпания повторов есть сообщение с task_id и причиной | +| error_handling / 3 | logging | После 7.1 | Основное небольшое дополнение вместо механической замены всех print | [7.3](../../airflow-docker/educational-tasks.md#errors) | Собственные сообщения видны в логах | +| Дополнительные / 1 | Пулы и слоты | Готовый resource_management_dag, пул из 2 слотов | Основной короткий эксперимент; резервное копирование заменено имитацией чтения | [8.1](../../airflow-docker/educational-tasks.md#resources) | Задачи с весами 2 и 1 выполняются последовательно | +| Дополнительные / 2 | XCom и Jinja | После 8.1 | Основная метрика; шаблон Bash по желанию | [8.2, 8.A](../../airflow-docker/educational-tasks.md#resources) | total_rows = 50, шаблон выводит то же значение | +| Дополнительные / 3 | TaskGroup и алертинг | После 8.2; для почты нужен SMTP | Разделить: группировка основная, внешняя доставка по желанию | [8.3, 8.B](../../airflow-docker/educational-tasks.md#resources) | Группа раскрывается; XCom работает с полными task_id | + +Основной маршрут сохраняет изменения внутри блоков. Общие данные есть только в паре загрузка/DQ; изменение схемы вынесено после основных опытов, с обратным ALTER и возвратом списков колонок. Задания 3.B и 3.C явно зависят от дополнительного 3.A, но дальнейшая основная практика от этой ветки не зависит. Variables возвращаются к исходным значениям перед переходом дальше. + ## Найденные несогласованности и препятствия -Ниже зафиксированы результаты чтения кода и заданий. Это не отчёт о полном прохождении стенда: Docker, DAG и UI в рамках обсуждения не запускались. Семантика статусов и зависимостей сверялась с документацией Airflow 2.9.2. +Первичные находки подтвердились при чтении кода и выполнении. Ниже их итоговое состояние; подробные результаты приведены в [протоколе](../validation/2026-09-28-practice.md). -| Место | Наблюдение | Направление исправления | +| Место | Исходная проблема | Итог | |---|---|---| -| [SQL-задание](../../airflow-docker/educational-tasks.md), [sql_basic_dag.py](../../airflow-docker/dags/sql_basic_dag.py) | Совет заменить завершающий `TRUNCATE` на `DROP` не готовит уже существующую таблицу к вставке в новые колонки: выполнение может упасть раньше завершающей задачи. | Описать подготовку учебной таблицы до запуска и момент проверки данных до очистки. Согласовать имя задачи очистки с её действием. | -| [csv_to_postgres.py](../../airflow-docker/dags/csv_to_postgres.py) | Подсказка про добавление `status` упоминает генератор, DDL и COPY, но не итоговый `INSERT … SELECT`. | Провести поле через все этапы и проверять его значения в целевой таблице. | -| [csv_to_postgres_dq.py](../../airflow-docker/dags/csv_to_postgres_dq.py) | Строгая проверка схемы ожидает исходные четыре колонки и конфликтует с предыдущим расширением таблицы. | Согласовать порядок упражнений и исходное состояние DQ. Расширение схемы можно выполнять после знакомства с базовой загрузкой и проверками. | -| DQ-задание про Trigger Rules | После ошибки при стандартном `all_success` обещан `skipped`, хотя в описанном сценарии ожидается `upstream_failed`. | Исправить ожидаемые состояния и проверить их на стенде. | -| [error_handling_dag.py](../../airflow-docker/dags/error_handling_dag.py) | `chain` связывает два списка попарно: обработчик успеха зависит от одной рабочей задачи, обработчик ошибки — от другой. Комментарии описывают общий результат. | Согласовать граф с учебным сценарием; проверить варианты успеха и отказа каждой рабочей задачи, а также итог DAG Run. | -| DQ-сводка и [пример алертинга](../../09%20-%20Пулы,%20XCom,%20TaskGroup%20и%20алертинг%20в%20Airflow.md) | В исходном DQ используется `all_success`; риск возникает при предлагаемом в упражнении переходе на `all_done`. Успешная единственная завершающая задача с этим правилом может сделать DAG Run успешным при ошибке внутри. Пример письма также безусловно сообщает об успехе. | Явно определить желаемый итог и проверить согласованность статусов с сообщением. Отправку уведомления и успешность обработки рассматривать отдельно. | -| [data_processing_dag.py](../../airflow-docker/dags/data_processing_dag.py) | Загрузка в БД имитируется, но лог сообщает о загрузке. Transform читает исходные файлы, а не результаты Extract. | Честно назвать имитацию и согласовать показанный путь данных с фактическими входами задач. Полноценную загрузку ради этого примера добавлять необязательно. | -| Задание про пулы | Для пула из двух слотов и двух задач с потреблением два и один предлагается наблюдать «не более двух задач». Это не показывает существенное ограничение: эти две задачи одновременно не помещаются. | Описать ожидаемое ожидание одной задачи и дать достаточную длительность для наблюдения в UI. | -| Ветвление, повторы и дополнительные упражнения | Случайный выбор мешает гарантированно наблюдать нужный случай. Сравнение файлов двух взаимоисключающих веток не определяет, от каких запусков брать результаты. Некоторые задания требуют полей, добавленных раньше. | Задать управляемые сценарии, понятные входы и зависимости; исключить случайное использование результатов старого запуска. | +| SQL | DROP после вставки не готовил существующую таблицу к новой колонке; результат очищался до проверки. | Исправлено: TRUNCATE перед вставкой, task_id `truncate_table`, три строки остаются в БД. В 2.A ALTER выполняется до запуска. Проверены UPDATE, email и отчет. | +| Загрузчик CSV | Подсказка про status пропускала INSERT ... SELECT. | Исправлено: перечислены четыре участка загрузчика и порядок полей CSV/COPY. Проверены значения статусов в БД, а не только наличие колонки. | +| Схема DQ | Дополнительная колонка конфликтовала с ожидаемыми четырьмя. | Исправлено: расширение идет после основного DQ; описаны обновление ожидания и возврат схемы. Оба состояния проверены. | +| Trigger Rules DQ | После отказа обещан skipped. | Исправлено на upstream_failed; подтверждены отказ проверки суммы и отказ схемы. | +| Обработчики ошибок | chain связывал списки попарно. | Исправлено: оба обработчика имеют обе рабочие задачи в непосредственных предшественниках. Проверены успех, повтор, отказ первой, второй и обеих задач, общий итог DAG Run. | +| Сводка DQ и алертинг | Единственная успешная завершающая задача с all_done могла скрыть ошибку; письмо безусловно сообщало об успехе. | Исправлено: сводка сверяет состояния и падает при неуспешной проверке. Уведомления назначены callbacks задачи и DAG. Проверены all_done, письма об отказе/успехе и сохранение результата при ошибке SMTP. | +| ETL | Transform обходил результаты Extract, имитация называлась загрузкой в БД. | Исправлен путь данных. Имитация сохранена и явно обозначена именем simulate_load и логом. Фильтрация Extract изменила отчет с 6/2105 на 4/2000. | +| Пулы | Формулировка "не более двух задач" не показывала ограничение по весам. | Добавлен готовый пример: в пуле из двух слотов веса 2+1 выполняются последовательно, 1+1 пересекаются по времени. Проверены оба варианта. | +| Ветвление, повторы и зависимости | Случайность мешала наблюдать нужный случай; использовались неопределенные входы и результаты старых запусков. | Исправлено: Variables управляют режимами; входы CSV/JSON заданы; слияние читает XCom текущего запуска; зависимости дополнительных упражнений указаны явно. | +| Логи callbacks | Первоначальная инструкция отправляла все сообщения в docker compose logs scheduler. | Уточнено после выполнения: callback оператора виден в последней попытке задачи, callback DAG в файле scheduler. Оба места проверены. | -Источники для технической сверки: [chain](https://airflow.apache.org/docs/apache-airflow/2.9.2/_api/airflow/models/baseoperator/index.html#airflow.models.baseoperator.chain), [статусы задач](https://airflow.apache.org/docs/apache-airflow/2.9.2/core-concepts/tasks.html#task-instances), [итог DAG Run](https://airflow.apache.org/docs/apache-airflow/2.9.2/core-concepts/dag-run.html#dag-run-status), [пулы](https://airflow.apache.org/docs/apache-airflow/2.9.2/administration-and-deployment/pools.html). +Технические источники: [chain](https://airflow.apache.org/docs/apache-airflow/2.9.2/_api/airflow/models/baseoperator/index.html#airflow.models.baseoperator.chain), [статусы задач](https://airflow.apache.org/docs/apache-airflow/2.9.2/core-concepts/tasks.html#task-instances), [итог DAG Run](https://airflow.apache.org/docs/apache-airflow/2.9.2/core-concepts/dag-run.html#dag-run-status), [пулы](https://airflow.apache.org/docs/apache-airflow/2.9.2/administration-and-deployment/pools.html), [COPY в PostgreSQL 16](https://www.postgresql.org/docs/16/sql-copy.html). Условия поиска логов уточнены по фактическому стенду 2.9.2. ## Как проверять результат доработки @@ -97,7 +140,7 @@ - **Документы и исходники:** все 25 исходных заданий учтены, ссылки работают, имена файлов/задач/таблиц совпадают, обязательность и зависимости согласованы. - **Стенд:** подтверждены версия Airflow и загрузка DAG без ошибок импорта; выполнены затронутые сценарии и проверены файлы/таблицы. Проверка синтаксиса Python не заменяет выполнение задач. - **UI:** студент действительно может найти указанные состояния, логи, XCom, граф и настройки; показанные результаты совпадают с текстом. -- **Прохождение:** отдельно проверены основной маршрут и переходы после дополнительных заданий. Не выполненные проверки так и отмечаются; предполагаемое время не выдаётся за измеренное на студентах. +- **Прохождение:** отдельно проверены основной маршрут и переходы после дополнительных заданий. Не выполненные проверки так и отмечаются; предполагаемое время не выдается за измеренное на студентах. Эксперименты с изменением данных выполняются в изолированном учебном окружении. Проверка не должна затрагивать другие работающие стенды или несвязанные изменения пользователя. diff --git a/docs/validation/2026-09-28-practice-runs.jsonl b/docs/validation/2026-09-28-practice-runs.jsonl new file mode 100644 index 0000000..dc4cfbb --- /dev/null +++ b/docs/validation/2026-09-28-practice-runs.jsonl @@ -0,0 +1,71 @@ +{"type": "environment", "date": "2026-09-28", "airflow": "2.9.2", "python": "3.12.4", "executor": "LocalExecutor", "required_scenarios": 69, "recorded_runs": 70, "final_source_sha256": {"branching_dag.py": "873f00e26ede268582245aac0ec3b82f858a3654fb7a4e1e08eb6e68ec5d031a", "csv_to_postgres.py": "43452b5f639365be07174e417fa981116b478e90acaeffb184e530d9ae67cb3b", "csv_to_postgres_dq.py": "05e43bc4bdab64813d7a853fee9285c11cb0e82a077c33a641219ca47b4604af", "data_processing_dag.py": "df39a91f89ff2e572d0a101b52171b9af97fb4abc7a66fc90c136425acb64b89", "error_handling_dag.py": "914e00b5845afa82c08aa540501a9994825b9b48a375bcc65ec59ae87b2cc23f", "file_operations_dag.py": "c4b5969a296af94c61cd63a9511a5572f9b858cbd75be913e8dffd015441b48f", "hello_world_dag.py": "09036daee495267a9dc23fd9c04fbd99c50b76194be1f3004822cee87f7797e7", "resource_management_dag.py": "03774dbfe5cadc6d6f036814f4fb2f8db0991c511b7ea3e877f577dca644b13e", "sql_basic_dag.py": "d028ff3d281ad322e8443447f43000497db67053cfeb757dc23b21b78f8b536f"}} +{"label": "base_branching_dag", "dag": "branching_dag", "run_id": "base_branching_dag__1790598303491558368", "state": "success", "expected_state": "success", "tasks": {"start_task": {"state": "success", "attempts": 0, "start": "2026-09-28T12:25:04.466969+00:00", "end": "2026-09-28T12:25:04.466989+00:00"}, "choose_format": {"state": "success", "attempts": 1, "start": "2026-09-28T12:25:06.300063+00:00", "end": "2026-09-28T12:25:07.740076+00:00"}, "process_csv_branch": {"state": "success", "attempts": 1, "start": "2026-09-28T12:25:09.192486+00:00", "end": "2026-09-28T12:25:09.736934+00:00"}, "end_task": {"state": "success", "attempts": 0, "start": "2026-09-28T12:25:11.926795+00:00", "end": "2026-09-28T12:25:11.926806+00:00"}, "process_json_branch": {"state": "skipped", "attempts": 0, "start": "2026-09-28T12:25:07.614511+00:00", "end": "2026-09-28T12:25:07.614511+00:00"}, "merge_results": {"state": "success", "attempts": 1, "start": "2026-09-28T12:25:10.695251+00:00", "end": "2026-09-28T12:25:11.211111+00:00"}}} +{"label": "base_csv_to_postgres", "dag": "csv_to_postgres", "run_id": "base_csv_to_postgres__1790598316856917563", "state": "success", "expected_state": "success", "tasks": {"create_orders_table": {"state": "success", "attempts": 1, "start": "2026-09-28T12:25:17.703222+00:00", "end": "2026-09-28T12:25:18.385307+00:00"}, "generate_csv": {"state": "success", "attempts": 1, "start": "2026-09-28T12:25:19.297092+00:00", "end": "2026-09-28T12:25:20.954830+00:00"}, "preview_csv": {"state": "success", "attempts": 1, "start": "2026-09-28T12:25:21.691404+00:00", "end": "2026-09-28T12:25:23.672198+00:00"}, "load_csv_to_postgres": {"state": "success", "attempts": 1, "start": "2026-09-28T12:25:24.349364+00:00", "end": "2026-09-28T12:25:25.132534+00:00"}}} +{"label": "base_data_processing_dag", "dag": "data_processing_dag", "run_id": "base_data_processing_dag__1790598297048939564", "state": "success", "expected_state": "success", "tasks": {"create_sample_data": {"state": "success", "attempts": 1, "start": "2026-09-28T12:24:58.018194+00:00", "end": "2026-09-28T12:24:59.516887+00:00"}, "extract_orders": {"state": "success", "attempts": 1, "start": "2026-09-28T12:25:00.524479+00:00", "end": "2026-09-28T12:25:04.438975+00:00"}, "extract_customers": {"state": "success", "attempts": 1, "start": "2026-09-28T12:25:00.319521+00:00", "end": "2026-09-28T12:25:04.581621+00:00"}, "transform_data": {"state": "success", "attempts": 1, "start": "2026-09-28T12:25:06.641381+00:00", "end": "2026-09-28T12:25:08.648080+00:00"}, "simulate_load": {"state": "success", "attempts": 1, "start": "2026-09-28T12:25:10.564728+00:00", "end": "2026-09-28T12:25:11.970738+00:00"}, "generate_report": {"state": "success", "attempts": 1, "start": "2026-09-28T12:25:13.716924+00:00", "end": "2026-09-28T12:25:15.365893+00:00"}}} +{"label": "base_dq", "dag": "csv_to_postgres_dq", "run_id": "base_dq__1790598363500865461", "state": "success", "expected_state": "success", "tasks": {"data_quality_summary": {"state": "success", "attempts": 1, "start": "2026-09-28T12:26:09.667195+00:00", "end": "2026-09-28T12:26:10.149648+00:00"}, "check_orders_schema": {"state": "success", "attempts": 1, "start": "2026-09-28T12:26:05.724275+00:00", "end": "2026-09-28T12:26:06.206894+00:00"}, "check_orders_has_rows": {"state": "success", "attempts": 1, "start": "2026-09-28T12:26:06.888967+00:00", "end": "2026-09-28T12:26:07.337686+00:00"}, "check_order_duplicates": {"state": "success", "attempts": 1, "start": "2026-09-28T12:26:08.530504+00:00", "end": "2026-09-28T12:26:09.010861+00:00"}, "check_orders_table_exists": {"state": "success", "attempts": 1, "start": "2026-09-28T12:26:04.557178+00:00", "end": "2026-09-28T12:26:05.087473+00:00"}}} +{"label": "base_error_handling_dag", "dag": "error_handling_dag", "run_id": "base_error_handling_dag__1790598307068800147", "state": "success", "expected_state": "success", "tasks": {"start_task": {"state": "success", "attempts": 0, "start": "2026-09-28T12:25:08.688153+00:00", "end": "2026-09-28T12:25:08.688162+00:00"}, "unreliable_task": {"state": "success", "attempts": 1, "start": "2026-09-28T12:25:09.327897+00:00", "end": "2026-09-28T12:25:09.842635+00:00"}, "success_handler": {"state": "success", "attempts": 1, "start": "2026-09-28T12:25:21.859653+00:00", "end": "2026-09-28T12:25:22.425907+00:00"}, "retry_task": {"state": "success", "attempts": 2, "start": "2026-09-28T12:25:20.608453+00:00", "end": "2026-09-28T12:25:21.211328+00:00"}, "failure_handler": {"state": "skipped", "attempts": 0, "start": "2026-09-28T12:25:21.289459+00:00", "end": "2026-09-28T12:25:21.289459+00:00"}}} +{"label": "base_file_operations_dag", "dag": "file_operations_dag", "run_id": "base_file_operations_dag__1790598285829374474", "state": "success", "expected_state": "success", "tasks": {"generate_sample_data": {"state": "success", "attempts": 1, "start": "2026-09-28T12:24:47.788802+00:00", "end": "2026-09-28T12:24:52.901884+00:00"}, "read_and_validate_data": {"state": "success", "attempts": 1, "start": "2026-09-28T12:24:54.105194+00:00", "end": "2026-09-28T12:24:56.336898+00:00"}, "transform_data": {"state": "success", "attempts": 1, "start": "2026-09-28T12:24:57.980925+00:00", "end": "2026-09-28T12:24:59.453129+00:00"}, "write_summary": {"state": "success", "attempts": 1, "start": "2026-09-28T12:25:00.578118+00:00", "end": "2026-09-28T12:25:05.022890+00:00"}}} +{"label": "base_hello_world_dag", "dag": "hello_world_dag", "run_id": "base_hello_world_dag__1790598285331279953", "state": "success", "expected_state": "success", "tasks": {"start_task": {"state": "success", "attempts": 1, "start": "2026-09-28T12:24:47.450226+00:00", "end": "2026-09-28T12:24:48.038258+00:00"}, "date_task": {"state": "success", "attempts": 1, "start": "2026-09-28T12:24:50.252850+00:00", "end": "2026-09-28T12:24:50.964908+00:00"}, "end_task": {"state": "success", "attempts": 1, "start": "2026-09-28T12:24:54.481818+00:00", "end": "2026-09-28T12:24:55.586281+00:00"}}} +{"label": "base_resource_management_dag", "dag": "resource_management_dag", "run_id": "base_resource_management_dag__1790598315287681546", "state": "success", "expected_state": "success", "tasks": {"read_customers": {"state": "success", "attempts": 1, "start": "2026-09-28T12:25:16.416179+00:00", "end": "2026-09-28T12:25:36.854962+00:00"}, "read_orders": {"state": "success", "attempts": 1, "start": "2026-09-28T12:25:38.182441+00:00", "end": "2026-09-28T12:25:58.742686+00:00"}, "calculate_metrics": {"state": "success", "attempts": 1, "start": "2026-09-28T12:25:59.666739+00:00", "end": "2026-09-28T12:26:00.315747+00:00"}, "log_metrics": {"state": "success", "attempts": 1, "start": "2026-09-28T12:26:00.826752+00:00", "end": "2026-09-28T12:26:01.327392+00:00"}}} +{"label": "base_sql_basic_dag", "dag": "sql_basic_dag", "run_id": "base_sql_basic_dag__1790598285733066584", "state": "success", "expected_state": "success", "tasks": {"create_table": {"state": "success", "attempts": 1, "start": "2026-09-28T12:24:47.558668+00:00", "end": "2026-09-28T12:24:48.171774+00:00"}, "truncate_table": {"state": "success", "attempts": 1, "start": "2026-09-28T12:24:50.270342+00:00", "end": "2026-09-28T12:24:50.925384+00:00"}, "insert_data": {"state": "success", "attempts": 1, "start": "2026-09-28T12:24:54.165693+00:00", "end": "2026-09-28T12:24:55.422059+00:00"}, "query_data": {"state": "success", "attempts": 1, "start": "2026-09-28T12:24:58.018381+00:00", "end": "2026-09-28T12:24:58.580976+00:00"}}} +{"label": "main_1_1", "dag": "hello_world_dag", "run_id": "main_1_1__1790598669894851732", "state": "success", "expected_state": "success", "tasks": {"start_task": {"state": "success", "attempts": 1, "start": "2026-09-28T12:31:11.829375+00:00", "end": "2026-09-28T12:31:12.443251+00:00"}, "weather_task": {"state": "success", "attempts": 1, "start": "2026-09-28T12:31:12.949206+00:00", "end": "2026-09-28T12:31:13.415511+00:00"}, "date_task": {"state": "success", "attempts": 1, "start": "2026-09-28T12:31:14.385576+00:00", "end": "2026-09-28T12:31:14.885931+00:00"}, "end_task": {"state": "success", "attempts": 1, "start": "2026-09-28T12:31:16.391743+00:00", "end": "2026-09-28T12:31:16.910520+00:00"}}} +{"label": "main_1_2_3", "dag": "hello_world_dag", "run_id": "main_1_2_3__1790598718302127877", "state": "success", "expected_state": "success", "tasks": {"start_task": {"state": "success", "attempts": 1, "start": "2026-09-28T12:31:59.745450+00:00", "end": "2026-09-28T12:32:00.316366+00:00"}, "write_note": {"state": "success", "attempts": 1, "start": "2026-09-28T12:32:05.072071+00:00", "end": "2026-09-28T12:32:06.021474+00:00"}, "weather_task": {"state": "success", "attempts": 1, "start": "2026-09-28T12:32:01.159334+00:00", "end": "2026-09-28T12:32:01.723974+00:00"}, "date_task": {"state": "success", "attempts": 1, "start": "2026-09-28T12:32:02.837180+00:00", "end": "2026-09-28T12:32:03.474962+00:00"}, "end_task": {"state": "success", "attempts": 1, "start": "2026-09-28T12:32:07.286250+00:00", "end": "2026-09-28T12:32:07.807426+00:00"}}} +{"label": "main_2_1_bad_connection", "dag": "sql_basic_dag", "run_id": "main_2_1_bad_connection__1790598823284321738", "state": "failed", "expected_state": "failed", "tasks": {"truncate_table": {"state": "upstream_failed", "attempts": 0, "start": "2026-09-28T12:33:44.966461+00:00", "end": "2026-09-28T12:33:44.966461+00:00"}, "insert_data": {"state": "upstream_failed", "attempts": 0, "start": "2026-09-28T12:33:45.092205+00:00", "end": "2026-09-28T12:33:45.092205+00:00"}, "query_data": {"state": "upstream_failed", "attempts": 0, "start": "2026-09-28T12:33:46.184509+00:00", "end": "2026-09-28T12:33:46.184509+00:00"}, "create_table": {"state": "failed", "attempts": 1, "start": "2026-09-28T12:33:44.394182+00:00", "end": "2026-09-28T12:33:44.864384+00:00"}}} +{"label": "main_2_1_connection", "dag": "sql_basic_dag", "run_id": "main_2_1_connection__1790598770237278526", "state": "success", "expected_state": "success", "tasks": {"truncate_table": {"state": "success", "attempts": 1, "start": "2026-09-28T12:32:53.727892+00:00", "end": "2026-09-28T12:32:54.590903+00:00"}, "query_data": {"state": "success", "attempts": 1, "start": "2026-09-28T12:32:57.779131+00:00", "end": "2026-09-28T12:32:58.430291+00:00"}, "insert_data": {"state": "success", "attempts": 1, "start": "2026-09-28T12:32:55.515062+00:00", "end": "2026-09-28T12:32:56.385369+00:00"}, "create_table": {"state": "success", "attempts": 1, "start": "2026-09-28T12:32:51.379822+00:00", "end": "2026-09-28T12:32:52.074251+00:00"}}} +{"label": "main_2_2_repeat", "dag": "sql_basic_dag", "run_id": "main_2_2_repeat__1790598880752727153", "state": "success", "expected_state": "success", "tasks": {"create_table": {"state": "success", "attempts": 1, "start": "2026-09-28T12:34:42.273136+00:00", "end": "2026-09-28T12:34:42.874104+00:00"}, "truncate_table": {"state": "success", "attempts": 1, "start": "2026-09-28T12:34:43.459390+00:00", "end": "2026-09-28T12:34:44.136238+00:00"}, "insert_data": {"state": "success", "attempts": 1, "start": "2026-09-28T12:34:45.510497+00:00", "end": "2026-09-28T12:34:46.049469+00:00"}, "query_data": {"state": "success", "attempts": 1, "start": "2026-09-28T12:34:49.918420+00:00", "end": "2026-09-28T12:34:50.754474+00:00"}, "update_student": {"state": "success", "attempts": 1, "start": "2026-09-28T12:34:47.643123+00:00", "end": "2026-09-28T12:34:48.453621+00:00"}}} +{"label": "main_2_2_update", "dag": "sql_basic_dag", "run_id": "main_2_2_update__1790598869163909877", "state": "success", "expected_state": "success", "tasks": {"create_table": {"state": "success", "attempts": 1, "start": "2026-09-28T12:34:30.191396+00:00", "end": "2026-09-28T12:34:30.709128+00:00"}, "truncate_table": {"state": "success", "attempts": 1, "start": "2026-09-28T12:34:31.374968+00:00", "end": "2026-09-28T12:34:31.950460+00:00"}, "insert_data": {"state": "success", "attempts": 1, "start": "2026-09-28T12:34:33.266504+00:00", "end": "2026-09-28T12:34:33.826087+00:00"}, "update_student": {"state": "success", "attempts": 1, "start": "2026-09-28T12:34:34.392167+00:00", "end": "2026-09-28T12:34:34.997350+00:00"}, "query_data": {"state": "success", "attempts": 1, "start": "2026-09-28T12:34:35.712919+00:00", "end": "2026-09-28T12:34:36.267889+00:00"}}} +{"label": "main_3_1", "dag": "file_operations_dag", "run_id": "main_3_1__1790598934667802974", "state": "success", "expected_state": "success", "tasks": {"transform_data": {"state": "success", "attempts": 1, "start": "2026-09-28T12:35:41.591826+00:00", "end": "2026-09-28T12:35:43.072378+00:00"}, "write_summary": {"state": "success", "attempts": 1, "start": "2026-09-28T12:35:43.657051+00:00", "end": "2026-09-28T12:35:45.282296+00:00"}, "read_and_validate_data": {"state": "success", "attempts": 1, "start": "2026-09-28T12:35:38.756892+00:00", "end": "2026-09-28T12:35:40.407604+00:00"}, "generate_sample_data": {"state": "success", "attempts": 1, "start": "2026-09-28T12:35:35.850235+00:00", "end": "2026-09-28T12:35:37.611113+00:00"}}} +{"label": "main_4_1", "dag": "csv_to_postgres", "run_id": "main_4_1__1790598948261221421", "state": "success", "expected_state": "success", "tasks": {"preview_csv": {"state": "success", "attempts": 1, "start": "2026-09-28T12:35:54.292219+00:00", "end": "2026-09-28T12:35:56.183442+00:00"}, "load_csv_to_postgres": {"state": "success", "attempts": 1, "start": "2026-09-28T12:35:57.028083+00:00", "end": "2026-09-28T12:35:57.557853+00:00"}, "create_orders_table": {"state": "success", "attempts": 1, "start": "2026-09-28T12:35:49.970156+00:00", "end": "2026-09-28T12:35:50.510862+00:00"}, "generate_csv": {"state": "success", "attempts": 1, "start": "2026-09-28T12:35:51.118676+00:00", "end": "2026-09-28T12:35:52.724944+00:00"}}} +{"label": "main_4_2_negative", "dag": "csv_to_postgres_dq", "run_id": "main_4_2_negative__1790599011734924894", "state": "failed", "expected_state": "failed", "tasks": {"check_orders_table_exists": {"state": "success", "attempts": 1, "start": "2026-09-28T12:36:52.890470+00:00", "end": "2026-09-28T12:36:53.470813+00:00"}, "check_orders_schema": {"state": "success", "attempts": 1, "start": "2026-09-28T12:36:54.443034+00:00", "end": "2026-09-28T12:36:54.969375+00:00"}, "check_positive_amount": {"state": "failed", "attempts": 1, "start": "2026-09-28T12:36:59.434123+00:00", "end": "2026-09-28T12:37:00.000016+00:00"}, "check_orders_has_rows": {"state": "success", "attempts": 1, "start": "2026-09-28T12:36:56.309978+00:00", "end": "2026-09-28T12:36:56.841855+00:00"}, "data_quality_summary": {"state": "upstream_failed", "attempts": 0, "start": "2026-09-28T12:37:00.114864+00:00", "end": "2026-09-28T12:37:00.114864+00:00"}, "check_order_duplicates": {"state": "success", "attempts": 1, "start": "2026-09-28T12:36:57.649020+00:00", "end": "2026-09-28T12:36:58.151617+00:00"}}} +{"label": "main_4_2_positive", "dag": "csv_to_postgres_dq", "run_id": "main_4_2_positive__1790599000150640710", "state": "success", "expected_state": "success", "tasks": {"check_positive_amount": {"state": "success", "attempts": 1, "start": "2026-09-28T12:36:46.773434+00:00", "end": "2026-09-28T12:36:47.237395+00:00"}, "data_quality_summary": {"state": "success", "attempts": 1, "start": "2026-09-28T12:36:47.780290+00:00", "end": "2026-09-28T12:36:48.296235+00:00"}, "check_orders_table_exists": {"state": "success", "attempts": 1, "start": "2026-09-28T12:36:41.328500+00:00", "end": "2026-09-28T12:36:41.826903+00:00"}, "check_orders_schema": {"state": "success", "attempts": 1, "start": "2026-09-28T12:36:42.632046+00:00", "end": "2026-09-28T12:36:43.225919+00:00"}, "check_orders_has_rows": {"state": "success", "attempts": 1, "start": "2026-09-28T12:36:44.252071+00:00", "end": "2026-09-28T12:36:44.799840+00:00"}, "check_order_duplicates": {"state": "success", "attempts": 1, "start": "2026-09-28T12:36:45.510631+00:00", "end": "2026-09-28T12:36:46.128483+00:00"}}} +{"label": "main_4_2_restore", "dag": "csv_to_postgres_dq", "run_id": "main_4_2_restore__1790599023379510146", "state": "success", "expected_state": "success", "tasks": {"check_orders_table_exists": {"state": "success", "attempts": 1, "start": "2026-09-28T12:37:04.600783+00:00", "end": "2026-09-28T12:37:05.244958+00:00"}, "check_orders_schema": {"state": "success", "attempts": 1, "start": "2026-09-28T12:37:06.720511+00:00", "end": "2026-09-28T12:37:07.273312+00:00"}, "check_order_duplicates": {"state": "success", "attempts": 1, "start": "2026-09-28T12:37:09.578662+00:00", "end": "2026-09-28T12:37:10.260325+00:00"}, "check_orders_has_rows": {"state": "success", "attempts": 1, "start": "2026-09-28T12:37:07.872450+00:00", "end": "2026-09-28T12:37:08.452528+00:00"}, "data_quality_summary": {"state": "success", "attempts": 1, "start": "2026-09-28T12:37:12.613421+00:00", "end": "2026-09-28T12:37:13.154513+00:00"}, "check_positive_amount": {"state": "success", "attempts": 1, "start": "2026-09-28T12:37:11.411627+00:00", "end": "2026-09-28T12:37:11.924165+00:00"}}} +{"label": "main_5_1_filter", "dag": "data_processing_dag", "run_id": "main_5_1_filter__1790599074663133799", "state": "success", "expected_state": "success", "tasks": {"create_sample_data": {"state": "success", "attempts": 1, "start": "2026-09-28T12:37:55.824116+00:00", "end": "2026-09-28T12:37:57.680612+00:00"}, "extract_customers": {"state": "success", "attempts": 1, "start": "2026-09-28T12:37:59.047390+00:00", "end": "2026-09-28T12:38:00.783528+00:00"}, "extract_orders": {"state": "success", "attempts": 1, "start": "2026-09-28T12:37:59.039283+00:00", "end": "2026-09-28T12:38:00.812189+00:00"}, "transform_data": {"state": "success", "attempts": 1, "start": "2026-09-28T12:38:01.501036+00:00", "end": "2026-09-28T12:38:03.122143+00:00"}, "simulate_load": {"state": "success", "attempts": 1, "start": "2026-09-28T12:38:04.173110+00:00", "end": "2026-09-28T12:38:05.600297+00:00"}, "generate_report": {"state": "success", "attempts": 1, "start": "2026-09-28T12:38:06.618522+00:00", "end": "2026-09-28T12:38:08.794823+00:00"}}} +{"label": "main_5_1_restore", "dag": "data_processing_dag", "run_id": "main_5_1_restore__1790599131072124593", "state": "success", "expected_state": "success", "tasks": {"create_sample_data": {"state": "success", "attempts": 1, "start": "2026-09-28T12:38:52.059583+00:00", "end": "2026-09-28T12:38:53.524667+00:00"}, "extract_customers": {"state": "success", "attempts": 1, "start": "2026-09-28T12:38:55.185088+00:00", "end": "2026-09-28T12:38:56.827156+00:00"}, "transform_data": {"state": "success", "attempts": 1, "start": "2026-09-28T12:38:58.100743+00:00", "end": "2026-09-28T12:38:59.670338+00:00"}, "simulate_load": {"state": "success", "attempts": 1, "start": "2026-09-28T12:39:01.276000+00:00", "end": "2026-09-28T12:39:02.673380+00:00"}, "extract_orders": {"state": "success", "attempts": 1, "start": "2026-09-28T12:38:55.342213+00:00", "end": "2026-09-28T12:38:56.915006+00:00"}, "generate_report": {"state": "success", "attempts": 1, "start": "2026-09-28T12:39:03.515006+00:00", "end": "2026-09-28T12:39:04.916556+00:00"}}} +{"label": "main_6_1_csv", "dag": "branching_dag", "run_id": "main_6_1_csv__1790599221866037738", "state": "success", "expected_state": "success", "tasks": {"start_task": {"state": "success", "attempts": 0, "start": "2026-09-28T12:40:23.121466+00:00", "end": "2026-09-28T12:40:23.121476+00:00"}, "process_csv_branch": {"state": "success", "attempts": 1, "start": "2026-09-28T12:40:25.928269+00:00", "end": "2026-09-28T12:40:26.732622+00:00"}, "process_json_branch": {"state": "skipped", "attempts": 0, "start": "2026-09-28T12:40:25.140559+00:00", "end": "2026-09-28T12:40:25.140559+00:00"}, "choose_format": {"state": "success", "attempts": 1, "start": "2026-09-28T12:40:24.652209+00:00", "end": "2026-09-28T12:40:25.245620+00:00"}, "merge_results": {"state": "success", "attempts": 1, "start": "2026-09-28T12:40:27.779111+00:00", "end": "2026-09-28T12:40:28.278924+00:00"}, "end_task": {"state": "success", "attempts": 0, "start": "2026-09-28T12:40:28.547843+00:00", "end": "2026-09-28T12:40:28.547856+00:00"}}} +{"label": "main_6_1_csv_restore", "dag": "branching_dag", "run_id": "main_6_1_csv_restore__1790599246736022049", "state": "success", "expected_state": "success", "tasks": {"start_task": {"state": "success", "attempts": 0, "start": "2026-09-28T12:40:47.688841+00:00", "end": "2026-09-28T12:40:47.688856+00:00"}, "process_json_branch": {"state": "skipped", "attempts": 0, "start": "2026-09-28T12:40:49.804302+00:00", "end": "2026-09-28T12:40:49.804302+00:00"}, "choose_format": {"state": "success", "attempts": 1, "start": "2026-09-28T12:40:49.301493+00:00", "end": "2026-09-28T12:40:49.954481+00:00"}, "process_csv_branch": {"state": "success", "attempts": 1, "start": "2026-09-28T12:40:50.449791+00:00", "end": "2026-09-28T12:40:51.052888+00:00"}, "merge_results": {"state": "success", "attempts": 1, "start": "2026-09-28T12:40:51.831887+00:00", "end": "2026-09-28T12:40:52.503932+00:00"}, "end_task": {"state": "success", "attempts": 0, "start": "2026-09-28T12:40:52.882378+00:00", "end": "2026-09-28T12:40:52.882395+00:00"}}} +{"label": "main_6_1_json", "dag": "branching_dag", "run_id": "main_6_1_json__1790599230837355984", "state": "success", "expected_state": "success", "tasks": {"start_task": {"state": "success", "attempts": 0, "start": "2026-09-28T12:40:31.315989+00:00", "end": "2026-09-28T12:40:31.315999+00:00"}, "process_csv_branch": {"state": "skipped", "attempts": 0, "start": "2026-09-28T12:40:33.528600+00:00", "end": "2026-09-28T12:40:33.528600+00:00"}, "choose_format": {"state": "success", "attempts": 1, "start": "2026-09-28T12:40:33.004290+00:00", "end": "2026-09-28T12:40:33.646275+00:00"}, "process_json_branch": {"state": "success", "attempts": 1, "start": "2026-09-28T12:40:34.836572+00:00", "end": "2026-09-28T12:40:35.404802+00:00"}, "merge_results": {"state": "success", "attempts": 1, "start": "2026-09-28T12:40:36.209056+00:00", "end": "2026-09-28T12:40:36.773333+00:00"}, "end_task": {"state": "success", "attempts": 0, "start": "2026-09-28T12:40:37.541270+00:00", "end": "2026-09-28T12:40:37.541279+00:00"}}} +{"label": "main_6_1_xml", "dag": "branching_dag", "run_id": "main_6_1_xml__1790599239960170363", "state": "failed", "expected_state": "failed", "tasks": {"start_task": {"state": "success", "attempts": 0, "start": "2026-09-28T12:40:40.887013+00:00", "end": "2026-09-28T12:40:40.887026+00:00"}, "choose_format": {"state": "failed", "attempts": 1, "start": "2026-09-28T12:40:42.076249+00:00", "end": "2026-09-28T12:40:42.682151+00:00"}, "process_csv_branch": {"state": "upstream_failed", "attempts": 0, "start": "2026-09-28T12:40:42.871931+00:00", "end": "2026-09-28T12:40:42.871931+00:00"}, "process_json_branch": {"state": "upstream_failed", "attempts": 0, "start": "2026-09-28T12:40:42.884065+00:00", "end": "2026-09-28T12:40:42.884065+00:00"}, "merge_results": {"state": "upstream_failed", "attempts": 0, "start": "2026-09-28T12:40:43.830677+00:00", "end": "2026-09-28T12:40:43.830677+00:00"}, "end_task": {"state": "upstream_failed", "attempts": 0, "start": "2026-09-28T12:40:44.428621+00:00", "end": "2026-09-28T12:40:44.428621+00:00"}}} +{"label": "main_7_1", "dag": "error_handling_dag", "run_id": "main_7_1__1790599256282469073", "state": "success", "expected_state": "success", "tasks": {"start_task": {"state": "success", "attempts": 0, "start": "2026-09-28T12:40:56.875385+00:00", "end": "2026-09-28T12:40:56.875397+00:00"}, "unreliable_task": {"state": "success", "attempts": 1, "start": "2026-09-28T12:40:58.566416+00:00", "end": "2026-09-28T12:40:59.270822+00:00"}, "retry_task": {"state": "success", "attempts": 2, "start": "2026-09-28T12:41:09.795211+00:00", "end": "2026-09-28T12:41:10.316195+00:00"}, "failure_handler": {"state": "skipped", "attempts": 0, "start": "2026-09-28T12:41:10.449363+00:00", "end": "2026-09-28T12:41:10.449363+00:00"}, "success_handler": {"state": "success", "attempts": 1, "start": "2026-09-28T12:41:11.113518+00:00", "end": "2026-09-28T12:41:11.598224+00:00"}}} +{"label": "main_7_2_fail_both", "dag": "error_handling_dag", "run_id": "main_7_2_fail_both__1790599401713196879", "state": "failed", "expected_state": "failed", "tasks": {"start_task": {"state": "success", "attempts": 0, "start": "2026-09-28T12:43:22.530430+00:00", "end": "2026-09-28T12:43:22.530442+00:00"}, "retry_task": {"state": "failed", "attempts": 2, "start": "2026-09-28T12:43:36.132875+00:00", "end": "2026-09-28T12:43:36.780473+00:00"}, "unreliable_task": {"state": "failed", "attempts": 2, "start": "2026-09-28T12:43:36.118123+00:00", "end": "2026-09-28T12:43:36.741330+00:00"}, "success_handler": {"state": "upstream_failed", "attempts": 0, "start": "2026-09-28T12:43:36.841371+00:00", "end": "2026-09-28T12:43:36.841371+00:00"}, "failure_handler": {"state": "success", "attempts": 1, "start": "2026-09-28T12:43:37.573043+00:00", "end": "2026-09-28T12:43:38.121760+00:00"}}} +{"label": "main_7_2_fail_first", "dag": "error_handling_dag", "run_id": "main_7_2_fail_first__1790599364116452900", "state": "failed", "expected_state": "failed", "tasks": {"start_task": {"state": "success", "attempts": 0, "start": "2026-09-28T12:42:44.750400+00:00", "end": "2026-09-28T12:42:44.750408+00:00"}, "retry_task": {"state": "success", "attempts": 1, "start": "2026-09-28T12:42:45.926971+00:00", "end": "2026-09-28T12:42:46.477915+00:00"}, "failure_handler": {"state": "success", "attempts": 1, "start": "2026-09-28T12:42:59.251667+00:00", "end": "2026-09-28T12:42:59.716135+00:00"}, "unreliable_task": {"state": "failed", "attempts": 2, "start": "2026-09-28T12:42:57.061887+00:00", "end": "2026-09-28T12:42:57.672300+00:00"}, "success_handler": {"state": "upstream_failed", "attempts": 0, "start": "2026-09-28T12:42:57.864139+00:00", "end": "2026-09-28T12:42:57.864139+00:00"}}} +{"label": "main_7_2_fail_second", "dag": "error_handling_dag", "run_id": "main_7_2_fail_second__1790599383044317774", "state": "failed", "expected_state": "failed", "tasks": {"start_task": {"state": "success", "attempts": 0, "start": "2026-09-28T12:43:03.978190+00:00", "end": "2026-09-28T12:43:03.978208+00:00"}, "retry_task": {"state": "failed", "attempts": 2, "start": "2026-09-28T12:43:17.475284+00:00", "end": "2026-09-28T12:43:17.984522+00:00"}, "unreliable_task": {"state": "success", "attempts": 1, "start": "2026-09-28T12:43:05.869070+00:00", "end": "2026-09-28T12:43:06.372928+00:00"}, "success_handler": {"state": "upstream_failed", "attempts": 0, "start": "2026-09-28T12:43:18.102304+00:00", "end": "2026-09-28T12:43:18.102304+00:00"}, "failure_handler": {"state": "success", "attempts": 1, "start": "2026-09-28T12:43:19.615230+00:00", "end": "2026-09-28T12:43:20.101638+00:00"}}} +{"label": "main_7_2_no_retry", "dag": "error_handling_dag", "run_id": "main_7_2_no_retry__1790599315199596055", "state": "failed", "expected_state": "failed", "tasks": {"failure_handler": {"state": "success", "attempts": 1, "start": "2026-09-28T12:42:00.036017+00:00", "end": "2026-09-28T12:42:00.705269+00:00"}, "start_task": {"state": "success", "attempts": 0, "start": "2026-09-28T12:41:56.214072+00:00", "end": "2026-09-28T12:41:56.214088+00:00"}, "unreliable_task": {"state": "success", "attempts": 1, "start": "2026-09-28T12:41:57.872256+00:00", "end": "2026-09-28T12:41:58.819669+00:00"}, "retry_task": {"state": "failed", "attempts": 1, "start": "2026-09-28T12:41:57.911390+00:00", "end": "2026-09-28T12:41:58.936653+00:00"}, "success_handler": {"state": "upstream_failed", "attempts": 0, "start": "2026-09-28T12:41:59.040015+00:00", "end": "2026-09-28T12:41:59.040015+00:00"}}} +{"label": "main_7_2_success", "dag": "error_handling_dag", "run_id": "main_7_2_success__1790599420340411753", "state": "success", "expected_state": "success", "tasks": {"start_task": {"state": "success", "attempts": 0, "start": "2026-09-28T12:43:40.702654+00:00", "end": "2026-09-28T12:43:40.702666+00:00"}, "unreliable_task": {"state": "success", "attempts": 1, "start": "2026-09-28T12:43:42.414466+00:00", "end": "2026-09-28T12:43:43.206132+00:00"}, "retry_task": {"state": "success", "attempts": 1, "start": "2026-09-28T12:43:42.347891+00:00", "end": "2026-09-28T12:43:43.281951+00:00"}, "failure_handler": {"state": "skipped", "attempts": 0, "start": "2026-09-28T12:43:43.383261+00:00", "end": "2026-09-28T12:43:43.383261+00:00"}, "success_handler": {"state": "success", "attempts": 1, "start": "2026-09-28T12:43:44.696936+00:00", "end": "2026-09-28T12:43:45.165836+00:00"}}} +{"label": "main_7_3", "dag": "error_handling_dag", "run_id": "main_7_3__1790599466582227262", "state": "success", "expected_state": "success", "tasks": {"start_task": {"state": "success", "attempts": 0, "start": "2026-09-28T12:44:27.093897+00:00", "end": "2026-09-28T12:44:27.093911+00:00"}, "unreliable_task": {"state": "success", "attempts": 1, "start": "2026-09-28T12:44:28.737493+00:00", "end": "2026-09-28T12:44:29.422467+00:00"}, "retry_task": {"state": "success", "attempts": 2, "start": "2026-09-28T12:44:40.106129+00:00", "end": "2026-09-28T12:44:40.590496+00:00"}, "failure_handler": {"state": "skipped", "attempts": 0, "start": "2026-09-28T12:44:40.639777+00:00", "end": "2026-09-28T12:44:40.639777+00:00"}, "success_handler": {"state": "success", "attempts": 1, "start": "2026-09-28T12:44:41.109197+00:00", "end": "2026-09-28T12:44:41.576977+00:00"}}} +{"label": "main_8_1_parallel", "dag": "resource_management_dag", "run_id": "main_8_1_parallel__1790599574558882106", "state": "success", "expected_state": "success", "tasks": {"read_orders": {"state": "success", "attempts": 1, "start": "2026-09-28T12:46:16.538315+00:00", "end": "2026-09-28T12:46:37.107764+00:00"}, "read_customers": {"state": "success", "attempts": 1, "start": "2026-09-28T12:46:16.596349+00:00", "end": "2026-09-28T12:46:37.192829+00:00"}, "calculate_metrics": {"state": "success", "attempts": 1, "start": "2026-09-28T12:46:37.808171+00:00", "end": "2026-09-28T12:46:38.458356+00:00"}, "log_metrics": {"state": "success", "attempts": 1, "start": "2026-09-28T12:46:39.331072+00:00", "end": "2026-09-28T12:46:39.899368+00:00"}}} +{"label": "main_8_1_serial", "dag": "resource_management_dag", "run_id": "main_8_1_serial__1790599482620382943", "state": "success", "expected_state": "success", "tasks": {"log_metrics": {"state": "success", "attempts": 1, "start": "2026-09-28T12:45:29.173691+00:00", "end": "2026-09-28T12:45:30.128755+00:00"}, "read_customers": {"state": "success", "attempts": 1, "start": "2026-09-28T12:44:43.856681+00:00", "end": "2026-09-28T12:45:04.694013+00:00"}, "read_orders": {"state": "success", "attempts": 1, "start": "2026-09-28T12:45:05.830821+00:00", "end": "2026-09-28T12:45:26.382507+00:00"}, "calculate_metrics": {"state": "success", "attempts": 1, "start": "2026-09-28T12:45:27.408530+00:00", "end": "2026-09-28T12:45:28.420441+00:00"}}} +{"label": "main_8_2", "dag": "resource_management_dag", "run_id": "main_8_2__1790599640804934985", "state": "success", "expected_state": "success", "tasks": {"read_customers": {"state": "success", "attempts": 1, "start": "2026-09-28T12:47:21.889563+00:00", "end": "2026-09-28T12:47:42.647916+00:00"}, "read_orders": {"state": "success", "attempts": 1, "start": "2026-09-28T12:47:43.632588+00:00", "end": "2026-09-28T12:48:04.391177+00:00"}, "calculate_metrics": {"state": "success", "attempts": 1, "start": "2026-09-28T12:48:05.904759+00:00", "end": "2026-09-28T12:48:06.622084+00:00"}, "log_metrics": {"state": "success", "attempts": 1, "start": "2026-09-28T12:48:08.049355+00:00", "end": "2026-09-28T12:48:08.564654+00:00"}}} +{"label": "main_8_3", "dag": "resource_management_dag", "run_id": "main_8_3__1790599731782559761", "state": "success", "expected_state": "success", "tasks": {"extract.read_customers": {"state": "success", "attempts": 1, "start": "2026-09-28T12:48:53.427367+00:00", "end": "2026-09-28T12:49:13.903909+00:00"}, "extract.read_orders": {"state": "success", "attempts": 1, "start": "2026-09-28T12:49:15.274901+00:00", "end": "2026-09-28T12:49:35.809526+00:00"}, "calculate_metrics": {"state": "success", "attempts": 1, "start": "2026-09-28T12:49:36.965983+00:00", "end": "2026-09-28T12:49:37.918550+00:00"}, "log_metrics": {"state": "success", "attempts": 1, "start": "2026-09-28T12:49:39.405235+00:00", "end": "2026-09-28T12:49:39.974974+00:00"}}} +{"label": "optional_1_A_first", "dag": "hello_world_dag", "run_id": "optional_1_A_first__1790599828501658295", "state": "success", "expected_state": "success", "tasks": {"start_task": {"state": "success", "attempts": 1, "start": "2026-09-28T12:50:29.705066+00:00", "end": "2026-09-28T12:51:00.256126+00:00"}, "date_task": {"state": "success", "attempts": 1, "start": "2026-09-28T12:51:02.651448+00:00", "end": "2026-09-28T12:51:03.175435+00:00"}, "weather_task": {"state": "success", "attempts": 1, "start": "2026-09-28T12:51:01.427549+00:00", "end": "2026-09-28T12:51:02.022120+00:00"}, "write_note": {"state": "success", "attempts": 1, "start": "2026-09-28T12:51:03.805590+00:00", "end": "2026-09-28T12:51:04.377917+00:00"}, "end_task": {"state": "success", "attempts": 1, "start": "2026-09-28T12:51:04.973858+00:00", "end": "2026-09-28T12:51:05.452179+00:00"}}} +{"label": "optional_1_A_second", "dag": "hello_world_dag", "run_id": "optional_1_A_second__1790599831490826780", "state": "success", "expected_state": "success", "tasks": {"start_task": {"state": "success", "attempts": 1, "start": "2026-09-28T12:51:07.523497+00:00", "end": "2026-09-28T12:51:38.200910+00:00"}, "weather_task": {"state": "success", "attempts": 1, "start": "2026-09-28T12:51:40.092576+00:00", "end": "2026-09-28T12:51:40.720014+00:00"}, "date_task": {"state": "success", "attempts": 1, "start": "2026-09-28T12:51:42.001534+00:00", "end": "2026-09-28T12:51:42.531175+00:00"}, "end_task": {"state": "success", "attempts": 1, "start": "2026-09-28T12:51:45.023411+00:00", "end": "2026-09-28T12:51:45.554981+00:00"}, "write_note": {"state": "success", "attempts": 1, "start": "2026-09-28T12:51:43.392660+00:00", "end": "2026-09-28T12:51:44.179028+00:00"}}} +{"label": "optional_2_A_B", "dag": "sql_basic_dag", "run_id": "optional_2_A_B__1790599987276042498", "state": "success", "expected_state": "success", "tasks": {"create_table": {"state": "success", "attempts": 1, "start": "2026-09-28T12:53:09.211357+00:00", "end": "2026-09-28T12:53:09.951512+00:00"}, "truncate_table": {"state": "success", "attempts": 1, "start": "2026-09-28T12:53:11.047425+00:00", "end": "2026-09-28T12:53:11.740876+00:00"}, "insert_data": {"state": "success", "attempts": 1, "start": "2026-09-28T12:53:12.934788+00:00", "end": "2026-09-28T12:53:13.607534+00:00"}, "update_student": {"state": "success", "attempts": 1, "start": "2026-09-28T12:53:15.944973+00:00", "end": "2026-09-28T12:53:16.638095+00:00"}, "query_data": {"state": "success", "attempts": 1, "start": "2026-09-28T12:53:17.435005+00:00", "end": "2026-09-28T12:53:18.141358+00:00"}, "report": {"state": "success", "attempts": 1, "start": "2026-09-28T12:53:19.546507+00:00", "end": "2026-09-28T12:53:20.286011+00:00"}}} +{"label": "optional_3_A_B_C", "dag": "file_operations_dag", "run_id": "optional_3_A_B_C__1790600042332111688", "state": "success", "expected_state": "success", "tasks": {"transform_data": {"state": "success", "attempts": 1, "start": "2026-09-28T12:54:09.980335+00:00", "end": "2026-09-28T12:54:12.248114+00:00"}, "write_summary": {"state": "success", "attempts": 1, "start": "2026-09-28T12:54:13.879091+00:00", "end": "2026-09-28T12:54:17.284622+00:00"}, "read_and_validate_data": {"state": "success", "attempts": 1, "start": "2026-09-28T12:54:07.264422+00:00", "end": "2026-09-28T12:54:08.788050+00:00"}, "generate_sample_data": {"state": "success", "attempts": 1, "start": "2026-09-28T12:54:03.667581+00:00", "end": "2026-09-28T12:54:06.067366+00:00"}, "department_engineering": {"state": "success", "attempts": 1, "start": "2026-09-28T12:54:14.349510+00:00", "end": "2026-09-28T12:54:17.713422+00:00"}, "department_support": {"state": "success", "attempts": 1, "start": "2026-09-28T12:54:14.885206+00:00", "end": "2026-09-28T12:54:17.977159+00:00"}, "department_report": {"state": "success", "attempts": 1, "start": "2026-09-28T12:54:20.187672+00:00", "end": "2026-09-28T12:54:21.852688+00:00"}, "department_sales": {"state": "success", "attempts": 2, "start": "2026-09-28T12:54:20.381886+00:00", "end": "2026-09-28T12:54:22.016177+00:00"}, "gather": {"state": "success", "attempts": 1, "start": "2026-09-28T12:54:22.800577+00:00", "end": "2026-09-28T12:54:24.457104+00:00"}}} +{"label": "optional_4_A_dq", "dag": "csv_to_postgres_dq", "run_id": "optional_4_A_dq__1790600084423979625", "state": "success", "expected_state": "success", "tasks": {"check_orders_table_exists": {"state": "success", "attempts": 1, "start": "2026-09-28T12:54:45.792417+00:00", "end": "2026-09-28T12:54:46.301310+00:00"}, "check_orders_schema": {"state": "success", "attempts": 1, "start": "2026-09-28T12:54:47.351405+00:00", "end": "2026-09-28T12:54:47.874188+00:00"}, "check_orders_has_rows": {"state": "success", "attempts": 1, "start": "2026-09-28T12:54:48.529849+00:00", "end": "2026-09-28T12:54:48.966554+00:00"}, "check_order_duplicates": {"state": "success", "attempts": 1, "start": "2026-09-28T12:54:49.787486+00:00", "end": "2026-09-28T12:54:50.271862+00:00"}, "check_positive_amount": {"state": "success", "attempts": 1, "start": "2026-09-28T12:54:51.064045+00:00", "end": "2026-09-28T12:54:51.705791+00:00"}, "data_quality_summary": {"state": "success", "attempts": 1, "start": "2026-09-28T12:54:52.304665+00:00", "end": "2026-09-28T12:54:52.846778+00:00"}}} +{"label": "optional_4_A_restore", "dag": "csv_to_postgres_dq", "run_id": "optional_4_A_restore__1790600176128130911", "state": "success", "expected_state": "success", "tasks": {"check_orders_schema": {"state": "success", "attempts": 1, "start": "2026-09-28T12:56:18.907596+00:00", "end": "2026-09-28T12:56:19.356101+00:00"}, "check_orders_table_exists": {"state": "success", "attempts": 1, "start": "2026-09-28T12:56:17.659452+00:00", "end": "2026-09-28T12:56:18.088407+00:00"}, "check_positive_amount": {"state": "success", "attempts": 1, "start": "2026-09-28T12:56:22.339147+00:00", "end": "2026-09-28T12:56:22.756030+00:00"}, "check_orders_has_rows": {"state": "success", "attempts": 1, "start": "2026-09-28T12:56:19.993271+00:00", "end": "2026-09-28T12:56:20.456629+00:00"}, "check_order_duplicates": {"state": "success", "attempts": 1, "start": "2026-09-28T12:56:21.187257+00:00", "end": "2026-09-28T12:56:21.591291+00:00"}, "data_quality_summary": {"state": "success", "attempts": 1, "start": "2026-09-28T12:56:23.525671+00:00", "end": "2026-09-28T12:56:23.996173+00:00"}}} +{"label": "optional_4_A_status", "dag": "csv_to_postgres", "run_id": "optional_4_A_status__1790600072998689452", "state": "success", "expected_state": "success", "tasks": {"create_orders_table": {"state": "success", "attempts": 1, "start": "2026-09-28T12:54:34.420941+00:00", "end": "2026-09-28T12:54:34.965465+00:00"}, "generate_csv": {"state": "success", "attempts": 1, "start": "2026-09-28T12:54:35.846349+00:00", "end": "2026-09-28T12:54:37.632934+00:00"}, "load_csv_to_postgres": {"state": "success", "attempts": 1, "start": "2026-09-28T12:54:40.758903+00:00", "end": "2026-09-28T12:54:41.313249+00:00"}, "preview_csv": {"state": "success", "attempts": 1, "start": "2026-09-28T12:54:38.368799+00:00", "end": "2026-09-28T12:54:39.934048+00:00"}}} +{"label": "optional_4_A_status_bad_order", "dag": "csv_to_postgres", "run_id": "optional_4_A_status__1790599866797382800", "state": "failed", "expected_state": "failed", "tasks": {"create_orders_table": {"state": "success", "attempts": 1, "start": "2026-09-28T12:51:07.993200+00:00", "end": "2026-09-28T12:51:08.577937+00:00"}, "load_csv_to_postgres": {"state": "failed", "attempts": 2, "start": "2026-09-28T12:51:47.478501+00:00", "end": "2026-09-28T12:51:48.034511+00:00"}, "generate_csv": {"state": "success", "attempts": 1, "start": "2026-09-28T12:51:09.311281+00:00", "end": "2026-09-28T12:51:10.967148+00:00"}, "preview_csv": {"state": "success", "attempts": 1, "start": "2026-09-28T12:51:12.691761+00:00", "end": "2026-09-28T12:51:14.377415+00:00"}}} +{"label": "optional_4_B_dq", "dag": "csv_to_postgres_dq", "run_id": "optional_4_B_dq__1790600237996456220", "state": "success", "expected_state": "success", "tasks": {"check_orders_table_exists": {"state": "success", "attempts": 1, "start": "2026-09-28T12:57:19.103267+00:00", "end": "2026-09-28T12:57:19.560193+00:00"}, "check_orders_schema": {"state": "success", "attempts": 1, "start": "2026-09-28T12:57:20.324388+00:00", "end": "2026-09-28T12:57:20.846060+00:00"}, "check_orders_has_rows": {"state": "success", "attempts": 1, "start": "2026-09-28T12:57:22.250197+00:00", "end": "2026-09-28T12:57:22.727576+00:00"}, "check_order_duplicates": {"state": "success", "attempts": 1, "start": "2026-09-28T12:57:23.415948+00:00", "end": "2026-09-28T12:57:23.951371+00:00"}, "check_positive_amount": {"state": "success", "attempts": 1, "start": "2026-09-28T12:57:25.207786+00:00", "end": "2026-09-28T12:57:25.689645+00:00"}, "data_quality_summary": {"state": "success", "attempts": 1, "start": "2026-09-28T12:57:27.492209+00:00", "end": "2026-09-28T12:57:28.083003+00:00"}}} +{"label": "optional_4_B_operator", "dag": "csv_to_postgres", "run_id": "optional_4_B_operator__1790600227029157362", "state": "success", "expected_state": "success", "tasks": {"generate_csv": {"state": "success", "attempts": 1, "start": "2026-09-28T12:57:10.252580+00:00", "end": "2026-09-28T12:57:11.814673+00:00"}, "create_orders_table": {"state": "success", "attempts": 1, "start": "2026-09-28T12:57:08.082255+00:00", "end": "2026-09-28T12:57:08.667507+00:00"}, "preview_csv": {"state": "success", "attempts": 1, "start": "2026-09-28T12:57:13.073106+00:00", "end": "2026-09-28T12:57:14.567664+00:00"}, "load_csv_to_postgres": {"state": "success", "attempts": 1, "start": "2026-09-28T12:57:15.203959+00:00", "end": "2026-09-28T12:57:15.691633+00:00"}}} +{"label": "optional_4_C_all_done", "dag": "csv_to_postgres_dq", "run_id": "optional_4_C_all_done__1790600337228090134", "state": "failed", "expected_state": "failed", "tasks": {"check_orders_table_exists": {"state": "success", "attempts": 1, "start": "2026-09-28T12:58:58.725586+00:00", "end": "2026-09-28T12:58:59.201374+00:00"}, "check_orders_schema": {"state": "failed", "attempts": 1, "start": "2026-09-28T12:59:01.015357+00:00", "end": "2026-09-28T12:59:01.541150+00:00"}, "check_orders_has_rows": {"state": "success", "attempts": 1, "start": "2026-09-28T12:59:02.461634+00:00", "end": "2026-09-28T12:59:02.991782+00:00"}, "check_order_duplicates": {"state": "success", "attempts": 1, "start": "2026-09-28T12:59:03.664066+00:00", "end": "2026-09-28T12:59:04.142534+00:00"}, "check_positive_amount": {"state": "success", "attempts": 1, "start": "2026-09-28T12:59:04.892560+00:00", "end": "2026-09-28T12:59:05.430995+00:00"}, "data_quality_summary": {"state": "failed", "attempts": 1, "start": "2026-09-28T12:59:07.120230+00:00", "end": "2026-09-28T12:59:07.593930+00:00"}}} +{"label": "optional_4_C_default", "dag": "csv_to_postgres_dq", "run_id": "optional_4_C_default__1790600291366135652", "state": "failed", "expected_state": "failed", "tasks": {"check_order_duplicates": {"state": "upstream_failed", "attempts": 0, "start": "2026-09-28T12:58:15.164590+00:00", "end": "2026-09-28T12:58:15.164590+00:00"}, "check_orders_table_exists": {"state": "success", "attempts": 1, "start": "2026-09-28T12:58:13.042381+00:00", "end": "2026-09-28T12:58:13.526508+00:00"}, "check_positive_amount": {"state": "upstream_failed", "attempts": 0, "start": "2026-09-28T12:58:16.262284+00:00", "end": "2026-09-28T12:58:16.262284+00:00"}, "data_quality_summary": {"state": "upstream_failed", "attempts": 0, "start": "2026-09-28T12:58:16.585583+00:00", "end": "2026-09-28T12:58:16.585583+00:00"}, "check_orders_schema": {"state": "failed", "attempts": 1, "start": "2026-09-28T12:58:14.330420+00:00", "end": "2026-09-28T12:58:14.851567+00:00"}, "check_orders_has_rows": {"state": "upstream_failed", "attempts": 0, "start": "2026-09-28T12:58:14.960806+00:00", "end": "2026-09-28T12:58:14.960806+00:00"}}} +{"label": "optional_4_C_restore", "dag": "csv_to_postgres_dq", "run_id": "optional_4_C_restore__1790600390711838382", "state": "success", "expected_state": "success", "tasks": {"check_orders_table_exists": {"state": "success", "attempts": 1, "start": "2026-09-28T12:59:51.882799+00:00", "end": "2026-09-28T12:59:52.351329+00:00"}, "check_orders_schema": {"state": "success", "attempts": 1, "start": "2026-09-28T12:59:53.403783+00:00", "end": "2026-09-28T12:59:53.992901+00:00"}, "check_orders_has_rows": {"state": "success", "attempts": 1, "start": "2026-09-28T12:59:54.968165+00:00", "end": "2026-09-28T12:59:55.731129+00:00"}, "check_order_duplicates": {"state": "success", "attempts": 1, "start": "2026-09-28T12:59:58.083402+00:00", "end": "2026-09-28T12:59:59.365498+00:00"}, "check_positive_amount": {"state": "success", "attempts": 1, "start": "2026-09-28T13:00:01.904918+00:00", "end": "2026-09-28T13:00:03.145987+00:00"}, "data_quality_summary": {"state": "success", "attempts": 1, "start": "2026-09-28T13:00:03.953750+00:00", "end": "2026-09-28T13:00:04.515677+00:00"}}} +{"label": "optional_5_A_B_C_success", "dag": "data_processing_dag", "run_id": "optional_5_A_B_C_success__1790599826280464970", "state": "success", "expected_state": "success", "tasks": {"create_sample_data": {"state": "success", "attempts": 1, "start": "2026-09-28T12:50:28.065679+00:00", "end": "2026-09-28T12:50:29.967444+00:00"}, "validate": {"state": "success", "attempts": 1, "start": "2026-09-28T12:50:38.601847+00:00", "end": "2026-09-28T12:50:40.510529+00:00"}, "transform_data": {"state": "success", "attempts": 1, "start": "2026-09-28T12:50:35.381953+00:00", "end": "2026-09-28T12:50:37.142909+00:00"}, "extract_customers": {"state": "success", "attempts": 1, "start": "2026-09-28T12:50:30.926518+00:00", "end": "2026-09-28T12:50:32.669276+00:00"}, "extract_products": {"state": "success", "attempts": 1, "start": "2026-09-28T12:50:31.062084+00:00", "end": "2026-09-28T12:50:32.916827+00:00"}, "extract_orders": {"state": "success", "attempts": 1, "start": "2026-09-28T12:50:30.983002+00:00", "end": "2026-09-28T12:50:33.137312+00:00"}, "simulate_load": {"state": "success", "attempts": 1, "start": "2026-09-28T12:50:41.199488+00:00", "end": "2026-09-28T12:50:42.824662+00:00"}, "generate_report": {"state": "success", "attempts": 1, "start": "2026-09-28T12:50:43.422545+00:00", "end": "2026-09-28T12:50:45.474906+00:00"}, "archive": {"state": "success", "attempts": 1, "start": "2026-09-28T12:50:46.874833+00:00", "end": "2026-09-28T12:50:47.500464+00:00"}}} +{"label": "optional_5_C_failure", "dag": "data_processing_dag", "run_id": "optional_5_C_failure__1790599890880680316", "state": "failed", "expected_state": "failed", "tasks": {"create_sample_data": {"state": "success", "attempts": 1, "start": "2026-09-28T12:51:31.769851+00:00", "end": "2026-09-28T12:51:33.331037+00:00"}, "extract_orders": {"state": "success", "attempts": 1, "start": "2026-09-28T12:51:34.907501+00:00", "end": "2026-09-28T12:51:36.969211+00:00"}, "extract_customers": {"state": "success", "attempts": 1, "start": "2026-09-28T12:51:34.860135+00:00", "end": "2026-09-28T12:51:37.189529+00:00"}, "transform_data": {"state": "success", "attempts": 1, "start": "2026-09-28T12:51:38.760107+00:00", "end": "2026-09-28T12:51:40.621671+00:00"}, "simulate_load": {"state": "upstream_failed", "attempts": 0, "start": "2026-09-28T12:51:49.189667+00:00", "end": "2026-09-28T12:51:49.189667+00:00"}, "generate_report": {"state": "upstream_failed", "attempts": 0, "start": "2026-09-28T12:51:49.662176+00:00", "end": "2026-09-28T12:51:49.662176+00:00"}, "archive": {"state": "upstream_failed", "attempts": 0, "start": "2026-09-28T12:51:50.496425+00:00", "end": "2026-09-28T12:51:50.496425+00:00"}, "extract_products": {"state": "success", "attempts": 1, "start": "2026-09-28T12:51:35.050525+00:00", "end": "2026-09-28T12:51:37.258136+00:00"}, "validate": {"state": "failed", "attempts": 2, "start": "2026-09-28T12:51:47.454480+00:00", "end": "2026-09-28T12:51:49.033534+00:00"}}} +{"label": "optional_5_C_restore", "dag": "data_processing_dag", "run_id": "optional_5_C_restore__1790599954560978695", "state": "success", "expected_state": "success", "tasks": {"extract_orders": {"state": "success", "attempts": 1, "start": "2026-09-28T12:52:38.792872+00:00", "end": "2026-09-28T12:52:41.056815+00:00"}, "extract_customers": {"state": "success", "attempts": 1, "start": "2026-09-28T12:52:38.712862+00:00", "end": "2026-09-28T12:52:40.983042+00:00"}, "transform_data": {"state": "success", "attempts": 1, "start": "2026-09-28T12:52:44.098377+00:00", "end": "2026-09-28T12:52:46.183169+00:00"}, "generate_report": {"state": "success", "attempts": 1, "start": "2026-09-28T12:52:54.485567+00:00", "end": "2026-09-28T12:52:56.742958+00:00"}, "archive": {"state": "success", "attempts": 1, "start": "2026-09-28T12:52:57.823277+00:00", "end": "2026-09-28T12:52:58.453906+00:00"}, "create_sample_data": {"state": "success", "attempts": 1, "start": "2026-09-28T12:52:35.864954+00:00", "end": "2026-09-28T12:52:37.451156+00:00"}, "extract_products": {"state": "success", "attempts": 1, "start": "2026-09-28T12:52:38.920025+00:00", "end": "2026-09-28T12:52:41.149145+00:00"}, "validate": {"state": "success", "attempts": 1, "start": "2026-09-28T12:52:47.821696+00:00", "end": "2026-09-28T12:52:49.319829+00:00"}, "simulate_load": {"state": "success", "attempts": 1, "start": "2026-09-28T12:52:50.127366+00:00", "end": "2026-09-28T12:52:52.548589+00:00"}}} +{"label": "optional_6_A_csv", "dag": "branching_dag", "run_id": "optional_6_A_csv__1790599969413452767", "state": "success", "expected_state": "success", "tasks": {"process_csv_branch": {"state": "success", "attempts": 1, "start": "2026-09-28T12:52:54.817429+00:00", "end": "2026-09-28T12:52:55.475004+00:00"}, "start_task": {"state": "success", "attempts": 0, "start": "2026-09-28T12:52:49.756524+00:00", "end": "2026-09-28T12:52:49.756537+00:00"}, "process_json_branch": {"state": "skipped", "attempts": 0, "start": "2026-09-28T12:52:53.426676+00:00", "end": "2026-09-28T12:52:53.426676+00:00"}, "choose_format": {"state": "success", "attempts": 1, "start": "2026-09-28T12:52:51.852609+00:00", "end": "2026-09-28T12:52:53.700004+00:00"}, "review_branch": {"state": "skipped", "attempts": 0, "start": "2026-09-28T12:52:53.426676+00:00", "end": "2026-09-28T12:52:53.426676+00:00"}, "merge_results": {"state": "success", "attempts": 1, "start": "2026-09-28T12:52:56.749010+00:00", "end": "2026-09-28T12:52:57.301418+00:00"}, "end_task": {"state": "success", "attempts": 0, "start": "2026-09-28T12:52:57.406094+00:00", "end": "2026-09-28T12:52:57.406105+00:00"}}} +{"label": "optional_6_A_json", "dag": "branching_dag", "run_id": "optional_6_A_json__1790599981318891965", "state": "success", "expected_state": "success", "tasks": {"process_csv_branch": {"state": "skipped", "attempts": 0, "start": "2026-09-28T12:53:04.678219+00:00", "end": "2026-09-28T12:53:04.678219+00:00"}, "review_branch": {"state": "skipped", "attempts": 0, "start": "2026-09-28T12:53:04.678219+00:00", "end": "2026-09-28T12:53:04.678219+00:00"}, "start_task": {"state": "success", "attempts": 0, "start": "2026-09-28T12:53:02.190235+00:00", "end": "2026-09-28T12:53:02.190247+00:00"}, "choose_format": {"state": "success", "attempts": 1, "start": "2026-09-28T12:53:03.854653+00:00", "end": "2026-09-28T12:53:04.883167+00:00"}, "process_json_branch": {"state": "success", "attempts": 1, "start": "2026-09-28T12:53:05.617301+00:00", "end": "2026-09-28T12:53:06.163931+00:00"}, "merge_results": {"state": "success", "attempts": 1, "start": "2026-09-28T12:53:06.846567+00:00", "end": "2026-09-28T12:53:07.431430+00:00"}, "end_task": {"state": "success", "attempts": 0, "start": "2026-09-28T12:53:07.527378+00:00", "end": "2026-09-28T12:53:07.527385+00:00"}}} +{"label": "optional_6_A_review", "dag": "branching_dag", "run_id": "optional_6_A_review__1790599950878181492", "state": "success", "expected_state": "success", "tasks": {"start_task": {"state": "success", "attempts": 0, "start": "2026-09-28T12:52:31.880500+00:00", "end": "2026-09-28T12:52:31.880513+00:00"}, "process_csv_branch": {"state": "skipped", "attempts": 0, "start": "2026-09-28T12:52:34.520952+00:00", "end": "2026-09-28T12:52:34.520952+00:00"}, "process_json_branch": {"state": "skipped", "attempts": 0, "start": "2026-09-28T12:52:34.520952+00:00", "end": "2026-09-28T12:52:34.520952+00:00"}, "choose_format": {"state": "success", "attempts": 1, "start": "2026-09-28T12:52:33.686169+00:00", "end": "2026-09-28T12:52:34.634325+00:00"}, "review_branch": {"state": "success", "attempts": 1, "start": "2026-09-28T12:52:35.883342+00:00", "end": "2026-09-28T12:52:36.335243+00:00"}, "merge_results": {"state": "success", "attempts": 1, "start": "2026-09-28T12:52:37.145269+00:00", "end": "2026-09-28T12:52:37.638569+00:00"}, "end_task": {"state": "success", "attempts": 0, "start": "2026-09-28T12:52:38.080346+00:00", "end": "2026-09-28T12:52:38.080354+00:00"}}} +{"label": "optional_6_A_xml", "dag": "branching_dag", "run_id": "optional_6_A_xml__1790599960064387143", "state": "success", "expected_state": "success", "tasks": {"start_task": {"state": "success", "attempts": 0, "start": "2026-09-28T12:52:40.576461+00:00", "end": "2026-09-28T12:52:40.576475+00:00"}, "process_csv_branch": {"state": "skipped", "attempts": 0, "start": "2026-09-28T12:52:42.254102+00:00", "end": "2026-09-28T12:52:42.254102+00:00"}, "process_json_branch": {"state": "skipped", "attempts": 0, "start": "2026-09-28T12:52:42.254102+00:00", "end": "2026-09-28T12:52:42.254102+00:00"}, "choose_format": {"state": "success", "attempts": 1, "start": "2026-09-28T12:52:41.714877+00:00", "end": "2026-09-28T12:52:42.372355+00:00"}, "review_branch": {"state": "success", "attempts": 1, "start": "2026-09-28T12:52:44.072978+00:00", "end": "2026-09-28T12:52:44.611876+00:00"}, "end_task": {"state": "success", "attempts": 0, "start": "2026-09-28T12:52:47.349562+00:00", "end": "2026-09-28T12:52:47.349570+00:00"}, "merge_results": {"state": "success", "attempts": 1, "start": "2026-09-28T12:52:45.532475+00:00", "end": "2026-09-28T12:52:46.387047+00:00"}}} +{"label": "optional_6_B_csv", "dag": "branching_dag", "run_id": "optional_6_B_csv__1790600030982481228", "state": "success", "expected_state": "success", "tasks": {"start_task": {"state": "success", "attempts": 0, "start": "2026-09-28T12:53:51.382625+00:00", "end": "2026-09-28T12:53:51.382631+00:00"}, "process_json_branch": {"state": "skipped", "attempts": 0, "start": "2026-09-28T12:53:53.983930+00:00", "end": "2026-09-28T12:53:53.983930+00:00"}, "review_branch": {"state": "skipped", "attempts": 0, "start": "2026-09-28T12:53:53.983930+00:00", "end": "2026-09-28T12:53:53.983930+00:00"}, "choose_format": {"state": "success", "attempts": 1, "start": "2026-09-28T12:53:52.593521+00:00", "end": "2026-09-28T12:53:54.169523+00:00"}, "process_csv_branch": {"state": "success", "attempts": 1, "start": "2026-09-28T12:53:55.338055+00:00", "end": "2026-09-28T12:53:57.034412+00:00"}, "end_task": {"state": "success", "attempts": 0, "start": "2026-09-28T12:53:58.733441+00:00", "end": "2026-09-28T12:53:58.733456+00:00"}, "merge_results": {"state": "success", "attempts": 1, "start": "2026-09-28T12:53:58.016770+00:00", "end": "2026-09-28T12:53:58.536435+00:00"}}} +{"label": "optional_6_B_json", "dag": "branching_dag", "run_id": "optional_6_B_json__1790600043123594630", "state": "success", "expected_state": "success", "tasks": {"start_task": {"state": "success", "attempts": 0, "start": "2026-09-28T12:54:04.312564+00:00", "end": "2026-09-28T12:54:04.312573+00:00"}, "process_csv_branch": {"state": "skipped", "attempts": 0, "start": "2026-09-28T12:54:07.267935+00:00", "end": "2026-09-28T12:54:07.267935+00:00"}, "review_branch": {"state": "skipped", "attempts": 0, "start": "2026-09-28T12:54:07.267935+00:00", "end": "2026-09-28T12:54:07.267935+00:00"}, "choose_format": {"state": "success", "attempts": 1, "start": "2026-09-28T12:54:06.715738+00:00", "end": "2026-09-28T12:54:07.404010+00:00"}, "process_json_branch": {"state": "success", "attempts": 1, "start": "2026-09-28T12:54:08.623885+00:00", "end": "2026-09-28T12:54:10.327426+00:00"}, "merge_results": {"state": "success", "attempts": 1, "start": "2026-09-28T12:54:11.590619+00:00", "end": "2026-09-28T12:54:12.518040+00:00"}, "end_task": {"state": "success", "attempts": 0, "start": "2026-09-28T12:54:13.240380+00:00", "end": "2026-09-28T12:54:13.240393+00:00"}}} +{"label": "optional_7_A", "dag": "error_handling_dag", "run_id": "optional_7_A__1790600019449470468", "state": "failed", "expected_state": "failed", "tasks": {"start_task": {"state": "success", "attempts": 0, "start": "2026-09-28T12:53:40.237917+00:00", "end": "2026-09-28T12:53:40.237924+00:00"}, "unreliable_task": {"state": "success", "attempts": 1, "start": "2026-09-28T12:53:41.595590+00:00", "end": "2026-09-28T12:53:42.183338+00:00"}, "success_handler": {"state": "upstream_failed", "attempts": 0, "start": "2026-09-28T12:54:50.391456+00:00", "end": "2026-09-28T12:54:50.391456+00:00"}, "failure_handler": {"state": "success", "attempts": 1, "start": "2026-09-28T12:54:51.055581+00:00", "end": "2026-09-28T12:54:51.678943+00:00"}, "retry_task": {"state": "failed", "attempts": 4, "start": "2026-09-28T12:54:49.753125+00:00", "end": "2026-09-28T12:54:50.277862+00:00"}}} +{"label": "optional_7_B_ConnectionError", "dag": "error_handling_dag", "run_id": "optional_7_B_ConnectionError__1790600133593914390", "state": "failed", "expected_state": "failed", "tasks": {"start_task": {"state": "success", "attempts": 0, "start": "2026-09-28T12:55:34.970055+00:00", "end": "2026-09-28T12:55:34.970064+00:00"}, "unreliable_task": {"state": "success", "attempts": 1, "start": "2026-09-28T12:55:36.562237+00:00", "end": "2026-09-28T12:55:37.011257+00:00"}, "retry_task": {"state": "failed", "attempts": 2, "start": "2026-09-28T12:55:47.492118+00:00", "end": "2026-09-28T12:55:47.940804+00:00"}, "success_handler": {"state": "upstream_failed", "attempts": 0, "start": "2026-09-28T12:55:48.064846+00:00", "end": "2026-09-28T12:55:48.064846+00:00"}, "failure_handler": {"state": "success", "attempts": 1, "start": "2026-09-28T12:55:48.712490+00:00", "end": "2026-09-28T12:55:49.197555+00:00"}}} +{"label": "optional_7_B_OSError", "dag": "error_handling_dag", "run_id": "optional_7_B_OSError__1790600252561937372", "state": "failed", "expected_state": "failed", "tasks": {"start_task": {"state": "success", "attempts": 0, "start": "2026-09-28T12:57:33.632472+00:00", "end": "2026-09-28T12:57:33.632487+00:00"}, "unreliable_task": {"state": "success", "attempts": 1, "start": "2026-09-28T12:57:34.290109+00:00", "end": "2026-09-28T12:57:34.768072+00:00"}, "retry_task": {"state": "failed", "attempts": 2, "start": "2026-09-28T12:57:45.543523+00:00", "end": "2026-09-28T12:57:46.128267+00:00"}, "success_handler": {"state": "upstream_failed", "attempts": 0, "start": "2026-09-28T12:57:46.246909+00:00", "end": "2026-09-28T12:57:46.246909+00:00"}, "failure_handler": {"state": "success", "attempts": 1, "start": "2026-09-28T12:57:47.071181+00:00", "end": "2026-09-28T12:57:47.715989+00:00"}}} +{"label": "optional_7_B_ValueError", "dag": "error_handling_dag", "run_id": "optional_7_B_ValueError__1790600191959984913", "state": "failed", "expected_state": "failed", "tasks": {"success_handler": {"state": "upstream_failed", "attempts": 0, "start": "2026-09-28T12:56:45.784424+00:00", "end": "2026-09-28T12:56:45.784424+00:00"}, "failure_handler": {"state": "success", "attempts": 1, "start": "2026-09-28T12:56:46.689244+00:00", "end": "2026-09-28T12:56:47.474193+00:00"}, "start_task": {"state": "success", "attempts": 0, "start": "2026-09-28T12:56:32.295591+00:00", "end": "2026-09-28T12:56:32.295598+00:00"}, "unreliable_task": {"state": "success", "attempts": 1, "start": "2026-09-28T12:56:33.808507+00:00", "end": "2026-09-28T12:56:34.327861+00:00"}, "retry_task": {"state": "failed", "attempts": 2, "start": "2026-09-28T12:56:44.934551+00:00", "end": "2026-09-28T12:56:45.531229+00:00"}}} +{"label": "optional_7_B_restore", "dag": "error_handling_dag", "run_id": "optional_7_B_restore__1790600308604083181", "state": "success", "expected_state": "success", "tasks": {"start_task": {"state": "success", "attempts": 0, "start": "2026-09-28T12:58:28.905883+00:00", "end": "2026-09-28T12:58:28.905893+00:00"}, "unreliable_task": {"state": "success", "attempts": 1, "start": "2026-09-28T12:58:30.479933+00:00", "end": "2026-09-28T12:58:30.955552+00:00"}, "success_handler": {"state": "success", "attempts": 1, "start": "2026-09-28T12:58:44.841191+00:00", "end": "2026-09-28T12:58:45.414819+00:00"}, "retry_task": {"state": "success", "attempts": 2, "start": "2026-09-28T12:58:42.378837+00:00", "end": "2026-09-28T12:58:43.211009+00:00"}, "failure_handler": {"state": "skipped", "attempts": 0, "start": "2026-09-28T12:58:43.325326+00:00", "end": "2026-09-28T12:58:43.325326+00:00"}}} +{"label": "optional_8_A", "dag": "resource_management_dag", "run_id": "optional_8_A__1790600366934777325", "state": "success", "expected_state": "success", "tasks": {"extract.read_customers": {"state": "success", "attempts": 1, "start": "2026-09-28T12:59:28.045365+00:00", "end": "2026-09-28T12:59:48.565983+00:00"}, "extract.read_orders": {"state": "success", "attempts": 1, "start": "2026-09-28T12:59:49.448034+00:00", "end": "2026-09-28T13:00:10.063529+00:00"}, "calculate_metrics": {"state": "success", "attempts": 1, "start": "2026-09-28T13:00:11.157140+00:00", "end": "2026-09-28T13:00:11.708381+00:00"}, "print_total": {"state": "success", "attempts": 1, "start": "2026-09-28T13:00:12.427443+00:00", "end": "2026-09-28T13:00:13.032322+00:00"}, "log_metrics": {"state": "success", "attempts": 1, "start": "2026-09-28T13:00:12.452891+00:00", "end": "2026-09-28T13:00:12.951344+00:00"}}} +{"label": "optional_8_B_delivery_error_failed", "dag": "error_handling_dag", "run_id": "optional_8_B_delivery_error_failed__1790600442808275037", "state": "failed", "expected_state": "failed", "tasks": {"retry_task": {"state": "failed", "attempts": 2, "start": "2026-09-28T13:00:55.863913+00:00", "end": "2026-09-28T13:00:56.344315+00:00"}, "success_handler": {"state": "upstream_failed", "attempts": 0, "start": "2026-09-28T13:00:56.517274+00:00", "end": "2026-09-28T13:00:56.517274+00:00"}, "start_task": {"state": "success", "attempts": 0, "start": "2026-09-28T13:00:43.241323+00:00", "end": "2026-09-28T13:00:43.241338+00:00"}, "unreliable_task": {"state": "success", "attempts": 1, "start": "2026-09-28T13:00:44.542395+00:00", "end": "2026-09-28T13:00:45.057656+00:00"}, "failure_handler": {"state": "success", "attempts": 1, "start": "2026-09-28T13:00:58.115826+00:00", "end": "2026-09-28T13:00:58.719339+00:00"}}} +{"label": "optional_8_B_delivery_error_success", "dag": "error_handling_dag", "run_id": "optional_8_B_delivery_error_success__1790600461566675460", "state": "success", "expected_state": "success", "tasks": {"start_task": {"state": "success", "attempts": 0, "start": "2026-09-28T13:01:02.553513+00:00", "end": "2026-09-28T13:01:02.553524+00:00"}, "success_handler": {"state": "success", "attempts": 1, "start": "2026-09-28T13:01:04.593955+00:00", "end": "2026-09-28T13:01:05.213490+00:00"}, "retry_task": {"state": "success", "attempts": 1, "start": "2026-09-28T13:01:03.364187+00:00", "end": "2026-09-28T13:01:03.838482+00:00"}, "unreliable_task": {"state": "success", "attempts": 1, "start": "2026-09-28T13:01:03.355462+00:00", "end": "2026-09-28T13:01:03.840328+00:00"}, "failure_handler": {"state": "skipped", "attempts": 0, "start": "2026-09-28T13:01:03.939908+00:00", "end": "2026-09-28T13:01:03.939908+00:00"}}} +{"label": "optional_8_B_failure", "dag": "error_handling_dag", "run_id": "optional_8_B_failure__1790600374167341045", "state": "failed", "expected_state": "failed", "tasks": {"start_task": {"state": "success", "attempts": 0, "start": "2026-09-28T12:59:34.633405+00:00", "end": "2026-09-28T12:59:34.633414+00:00"}, "unreliable_task": {"state": "success", "attempts": 1, "start": "2026-09-28T12:59:36.262871+00:00", "end": "2026-09-28T12:59:36.837096+00:00"}, "retry_task": {"state": "failed", "attempts": 2, "start": "2026-09-28T12:59:47.584630+00:00", "end": "2026-09-28T12:59:48.117663+00:00"}, "success_handler": {"state": "upstream_failed", "attempts": 0, "start": "2026-09-28T12:59:48.256654+00:00", "end": "2026-09-28T12:59:48.256654+00:00"}, "failure_handler": {"state": "success", "attempts": 1, "start": "2026-09-28T12:59:49.504044+00:00", "end": "2026-09-28T12:59:50.105201+00:00"}}} +{"label": "optional_8_B_restore", "dag": "error_handling_dag", "run_id": "optional_8_B_restore__1790600507926543810", "state": "success", "expected_state": "success", "tasks": {"start_task": {"state": "success", "attempts": 0, "start": "2026-09-28T13:01:49.154391+00:00", "end": "2026-09-28T13:01:49.154407+00:00"}, "unreliable_task": {"state": "success", "attempts": 1, "start": "2026-09-28T13:01:50.686480+00:00", "end": "2026-09-28T13:01:51.192154+00:00"}, "retry_task": {"state": "success", "attempts": 2, "start": "2026-09-28T13:02:01.992429+00:00", "end": "2026-09-28T13:02:02.590182+00:00"}, "success_handler": {"state": "success", "attempts": 1, "start": "2026-09-28T13:02:03.292271+00:00", "end": "2026-09-28T13:02:03.992153+00:00"}, "failure_handler": {"state": "skipped", "attempts": 0, "start": "2026-09-28T13:02:02.684938+00:00", "end": "2026-09-28T13:02:02.684938+00:00"}}} +{"label": "optional_8_B_success", "dag": "error_handling_dag", "run_id": "optional_8_B_success__1790600393174553780", "state": "success", "expected_state": "success", "tasks": {"success_handler": {"state": "success", "attempts": 1, "start": "2026-09-28T12:59:58.041525+00:00", "end": "2026-09-28T12:59:59.107268+00:00"}, "start_task": {"state": "success", "attempts": 0, "start": "2026-09-28T12:59:54.153604+00:00", "end": "2026-09-28T12:59:54.153613+00:00"}, "unreliable_task": {"state": "success", "attempts": 1, "start": "2026-09-28T12:59:54.985172+00:00", "end": "2026-09-28T12:59:55.734497+00:00"}, "retry_task": {"state": "success", "attempts": 1, "start": "2026-09-28T12:59:55.307245+00:00", "end": "2026-09-28T12:59:55.926514+00:00"}, "failure_handler": {"state": "skipped", "attempts": 0, "start": "2026-09-28T12:59:56.121310+00:00", "end": "2026-09-28T12:59:56.121310+00:00"}}} diff --git a/docs/validation/2026-09-28-practice.md b/docs/validation/2026-09-28-practice.md new file mode 100644 index 0000000..0a96978 --- /dev/null +++ b/docs/validation/2026-09-28-practice.md @@ -0,0 +1,69 @@ +# Проверка обновленной практики + +Дата: 2026-09-28. Код примеров: коммит `cf1b770`; задания и навигация: `d8dfe83`. Проверка выполнена авторским прогоном по [заданиям](../../airflow-docker/educational-tasks.md). Это проверка воспроизводимости инструкций; время и трудность прохождения на студентах не измерялись. + +## Окружение + +Образ собран из Dockerfile стенда. Фактические версии внутри контейнера: Apache Airflow 2.9.2, Python 3.12.4, pandas 2.1.4, mimesis 15.1.0, PostgreSQL provider 5.11.1. Executor: LocalExecutor, PostgreSQL 16. + +Использован отдельный Compose-проект `airflow-manual-practice-check`: собственные новые тома БД и копии `dags/`, `data/`. Порты перенесены на 18089, 15439, 15440. Для автоматизации включен Basic Auth API, `parallelism=4`, интервалы обнаружения файлов сокращены до 5 секунд. Перед запуском измененного DAG проверялся новый разбор файла и выдерживался интервал обновления сериализованного графа. + +Изменения студента выполнялись только в копиях. Внутри блока они сохранялись. Вначале прошли исходные примеры, затем основной маршрут без дополнительных заданий. Дополнительные расширения проверены после него. Независимые блоки выполнялись одновременно; загрузчик и DQ шли последовательно. + +Повторный `docker compose run --rm airflow-init` на существующих томах также завершился с кодом 0: учетная запись сохранена, подключение пересоздано. + +## Исходные примеры + +Все девять DAG загрузились без Import Errors и завершились успешно. Для `resource_management_dag` заранее создан пул из двух слотов. У `error_handling_dag` режим по умолчанию дал ошибку первой попытки и успех второй. У ветвления выбранная ветка успешна, другая пропущена. + +SQL оставил трех студентов, загрузчик создал 1000 заказов, DQ прошел. Файловый пример записал 100 строк. ETL создал шесть заказов с общей суммой 2105, шаг `simulate_load` сообщил об имитации. + +## Основной маршрут + +| Упражнения | Действия и фактический результат | +|---|---| +| 1.1-1.3 | После добавления погоды четыре задачи, после BashOperator пять; `note.txt` содержит `Airflow works`. Расписание изменилось на `0 0 * * 1`, ручной запуск прошел. | +| 2.1-2.2 | Создано отдельное подключение. Неверный Conn Id при нуле повторов: `create_table=failed`, следующие задачи `upstream_failed`. После восстановления и UPDATE: Alice 25, Bob 24, Charlie 21. Повторный полный запуск оставил три строки. | +| 3.1 | Порог High изменен на 80000. Проверены 100 строк и соответствие категории зарплате в CSV того же запуска. | +| 4.1-4.2 | Второй последовательный запуск загрузчика увеличил число заказов до 2000. Добавлена проверка суммы: корректные данные проходят; один отрицательный заказ дает `check_positive_amount=failed`, сводка `upstream_failed`, DAG Run `failed`. Повторный UPDATE восстановил успешный DQ. | +| 5.1 | Фильтр в Extract дал 4 заказа и сумму 2000 в результате Transform. После удаления фильтра снова 6 и 2105. | +| 6.1 | Режимы `csv`, `json`, `xml`, затем `csv`: выбор нужной ветки, отказ при неизвестном формате и успешное восстановление. XCom слияния относится к текущему запуску. | +| 7.1-7.3 | `retry_once`: две попытки и успех; переопределение `retries=0`: окончательный отказ. `fail_first`, `fail_second`, `fail_both`: обработчик ошибки успешен, обработчик успеха `upstream_failed`, DAG Run `failed`. Режим `success` и возврат к `retry_once` успешны. Добавлена запись в лог обработчика успеха. | +| 8.1 | Интервалы выполнения задач с весами 2 и 1 не пересекаются. После замены веса 2 на 1 интервалы пересекаются. | +| 8.2-8.3 | Добавлена метрика `total_rows=50`. После TaskGroup обновлены оба полных task_id, все четыре задачи успешны; XCom сохранил 20, 30 и 50. | + +Основной маршрут завершен без дополнительных упражнений. + +## Дополнительные упражнения + +| Упражнения | Действия и фактический результат | +|---|---| +| 1.A | Два ручных запуска с задержкой 30 секунд и `max_active_runs=1`: задачи второго начались после завершения первого. Затем задержка удалена, DAG выключен. | +| 2.A-2.B | Колонка email добавлена через ALTER до запуска. Три адреса заполнены, отчет содержит 3 студента и средний возраст 23.33; UPDATE из основной практики сохранен. | +| 3.A-3.C | Новые поля проверены, файл опытных сотрудников отделен от полного. Численность отделов в JSON дает 100; сборка трех файлов содержит те же 100 уникальных id. Отдел sales выполнился со второй попытки без дублей. | +| 4.A | При неверном порядке CSV/COPY получено `invalid input syntax for type numeric: "NEW"`. После согласования порядка новые статусы попали в БД и прошел DQ. После удаления поля из кода и таблицы DQ снова успешен; положительная сумма продолжает проверяться. | +| 4.B-4.C | Создание таблицы заменено PostgresOperator, загрузка и DQ успешны. Неверный ожидаемый тип amount при all_success дал upstream_failed у следующих задач. С all_done они выполнились, но сводка и DAG Run остались failed. Возврат numeric и исходных trigger rules восстановил успешный DQ. | +| 5.A-5.C | Справочник продуктов не изменил 6 заказов и сумму 2105. Топ-3: 1200, 400, 300; средний заказ Alice 640. Неверный клиент вызвал отказ проверки и заблокировал архив. После восстановления появился один архив, побайтово равный результату. | +| 6.A-6.B | Третья ветка выбрана для review и неизвестного формата; csv/json продолжают работать. Чтение двух фиксированных файлов дало по 2 записи с суммой 30 в отдельных запусках. | +| 7.A | Три повтора дали четыре неуспешные попытки, после которых DAG Run остался failed. | +| 7.B | ConnectionError, ValueError и OSError вызвали callback после второй попытки; проверены task_id и текст причины. В обеих попытках восстановленного retry_once callback отказа отсутствует. | +| 8.A | После группировки добавлен BashOperator с чтением XCom; в Details видна команда echo 50, в Logs число 50 и успешный выход. | +| 8.B | Локальный SMTP принял одно письмо об отказе retry_task и одно об успехе DAG. При недоступном SMTP неуспешный запуск остался failed, успешный остался success. После отключения почтовых callbacks и возврата retry_once запуск успешен. | + +[Протокол запусков JSONL](2026-09-28-practice-runs.jsonl) содержит 69 контрольных запусков и один диагностический запуск с неверным порядком CSV/COPY. Для каждого сохранены ожидаемый и фактический итог, состояния и число попыток задач, время начала и окончания. В первой строке указаны версии и SHA-256 итоговых DAG. Все ожидаемые состояния сошлись. + +При выполнении уточнены инструкции: одинаковый порядок CSV/COPY; короткая задержка повтора в 3.C и 5.C; расположение Rendered Templates внутри Details; раскрытие TaskGroup нажатием на название со стрелкой. Сообщение callback оператора подтверждено в логе последней попытки. Команда `docker compose logs airflow-scheduler` его не показала. Отправка из callback DAG и ошибка его доставки найдены в `/opt/airflow/logs/scheduler/latest/error_handling_dag.py.log`. + +## Интерфейс и документы + +Chromium: вход под учебной учетной записью, список девяти DAG, запуск кнопкой Trigger DAG, граф с четырьмя прямыми ребрами от рабочих задач к обработчикам. В Logs доступны две попытки; во второй видно `Starting attempt 2 of 2` и сообщение об успехе. В XCom загрузчика найден путь к CSV; тот же CSV виден в Details, раздел Rendered Templates, у задачи загрузки. Часть пути там маскируется как `***`; инструкция уточнена, имя файла сверено с XCom. + +Снимки проверенных состояний: [окончательный отказ](../../_attachments/practice-failed-run.png), [раскрытая TaskGroup](../../_attachments/practice-task-group.png), [путь в Rendered Templates](../../_attachments/practice-rendered-template.png). Группа раскрыта нажатием на название со стрелкой, внутри видны обе задачи. + +Markdown отрисован отдельно: восемь строк маршрута, 34 заголовка упражнений, рабочий переход к блоку ошибок. Проверены локальные ссылки и якоря в измененных документах, синтаксис девяти DAG и `git diff --check`. + +После сохранения протокола изолированные контейнеры, браузер и два тестовых тома удалены. Остальные работающие сервисы не изменялись. + +## Границы проверки + +Почтовый пример проверен на локальном SMTP-приемнике без отправки сообщений третьим лицам. Внешний провайдер, авторизация и TLS не проверялись: их параметры зависят от выбранного студентом сервера. Поведение Windows не проверялось на отдельной Windows-машине; опубликованные команды не используют синтаксис, специфичный для Bash. -- 2.54.0 From f6b450c922205cabaee45a13ee426d51d7a0c4fb Mon Sep 17 00:00:00 2001 From: Dmitry Dementev Date: Mon, 28 Sep 2026 09:19:48 -0400 Subject: [PATCH 5/6] =?UTF-8?q?docs(practice):=20=D0=BF=D0=B5=D1=80=D0=B5?= =?UTF-8?q?=D0=BD=D0=B5=D1=81=D1=82=D0=B8=20=D0=B7=D0=B0=D0=B4=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=B2=20=D0=BA=D0=BE=D1=80=D0=B5=D0=BD=D1=8C=20?= =?UTF-8?q?=D1=80=D0=B5=D0=BF=D0=BE=D0=B7=D0=B8=D1=82=D0=BE=D1=80=D0=B8?= =?UTF-8?q?=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...ы, XCom, TaskGroup и алертинг в Airflow.md | 2 +- README.md | 2 +- airflow-docker/README.md | 21 ++++---- airflow-docker/dag-specifications.md | 2 +- .../2026-09-28-practice-improvement-plan.md | 54 +++++++++---------- docs/validation/2026-09-28-practice.md | 2 +- ...ucational-tasks.md => educational-tasks.md | 4 +- 7 files changed, 43 insertions(+), 44 deletions(-) rename airflow-docker/educational-tasks.md => educational-tasks.md (98%) diff --git a/09 - Пулы, XCom, TaskGroup и алертинг в Airflow.md b/09 - Пулы, XCom, TaskGroup и алертинг в Airflow.md index 45f6356..5cd97b1 100644 --- a/09 - Пулы, XCom, TaskGroup и алертинг в Airflow.md +++ b/09 - Пулы, XCom, TaskGroup и алертинг в Airflow.md @@ -283,7 +283,7 @@ def notify_success(context): Не добавляйте ради уведомления единственную завершающую задачу с `trigger_rule="all_done"`: если она успешна, Airflow может признать весь запуск успешным, несмотря на отказ внутри графа. Callback не меняет граф и итог обработки. Ошибка отправки означает проблему уведомления; она не превращает успешно обработанные данные в неуспешные и не скрывает исходный отказ. Подробности определения результата - в [описании DAG Run](https://airflow.apache.org/docs/apache-airflow/2.9.2/core-concepts/dag-run.html#dag-run-status). -Проверка: сначала воспроизведите окончательный отказ, затем успешный запуск. Сверьте текст каждого письма с состоянием соответствующего DAG Run. Упражнения находятся в [блоке ошибок](airflow-docker/educational-tasks.md#errors) и [задании на почту](airflow-docker/educational-tasks.md#resources). +Проверка: сначала воспроизведите окончательный отказ, затем успешный запуск. Сверьте текст каждого письма с состоянием соответствующего DAG Run. Упражнения находятся в [блоке ошибок](educational-tasks.md#errors) и [задании на почту](educational-tasks.md#resources). ## Практическое применение: улучшенный DAG diff --git a/README.md b/README.md index 3c7f871..df810e8 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ ## Практический стенд -В [`airflow-docker`](airflow-docker/) находятся Airflow 2.9.2, учебная PostgreSQL и девять готовых DAG. [Инструкция запуска](airflow-docker/README.md) поможет поднять стенд, а [маршрут практики](airflow-docker/educational-tasks.md) связывает восемь блоков заданий с главами учебника. +В [`airflow-docker`](airflow-docker/) находятся Airflow 2.9.2, учебная PostgreSQL и девять готовых DAG. [Инструкция запуска](airflow-docker/README.md) поможет поднять стенд, а [маршрут практики](educational-tasks.md) связывает восемь блоков заданий с главами учебника. Здесь вы запускаете готовый DAG, меняете одну часть и проверяете результат в графе, логах или данных. Самостоятельные процессы обработки данных относятся к курсовым работам дальше по [программе](https://de.dementev.space/). diff --git a/airflow-docker/README.md b/airflow-docker/README.md index e616b9b..ef84db7 100644 --- a/airflow-docker/README.md +++ b/airflow-docker/README.md @@ -32,20 +32,20 @@ docker compose logs airflow-init ## Прохождение практики -Откройте [задания](educational-tasks.md). Их восемь блоков проходят последовательно; перед каждым указаны нужные главы [учебника](../README.md), исходные файлы и настройки. +Откройте [задания](../educational-tasks.md). Их восемь блоков проходят последовательно; перед каждым указаны нужные главы [учебника](../README.md), исходные файлы и настройки. Основные задания внутри блока сохраняют изменения предыдущих шагов. Дополнительные находятся в конце блока и помечены "По желанию"; их можно пропустить. Для завершения практики достаточно основных заданий и проверки их результатов. | Блок | DAG | |---|---| -| [Первый DAG](educational-tasks.md#first-dag) | `hello_world_dag` | -| [SQL и подключения](educational-tasks.md#sql) | `sql_basic_dag` | -| [Работа с файлами](educational-tasks.md#files) | `file_operations_dag` | -| [Загрузка CSV и DQ](educational-tasks.md#load-dq) | `csv_to_postgres`, `csv_to_postgres_dq` | -| [Разбор ETL](educational-tasks.md#etl) | `data_processing_dag` | -| [Ветвление](educational-tasks.md#branching) | `branching_dag` | -| [Ошибки и повторы](educational-tasks.md#errors) | `error_handling_dag` | -| [Пулы, XCom и TaskGroup](educational-tasks.md#resources) | `resource_management_dag` | +| [Первый DAG](../educational-tasks.md#first-dag) | `hello_world_dag` | +| [SQL и подключения](../educational-tasks.md#sql) | `sql_basic_dag` | +| [Работа с файлами](../educational-tasks.md#files) | `file_operations_dag` | +| [Загрузка CSV и DQ](../educational-tasks.md#load-dq) | `csv_to_postgres`, `csv_to_postgres_dq` | +| [Разбор ETL](../educational-tasks.md#etl) | `data_processing_dag` | +| [Ветвление](../educational-tasks.md#branching) | `branching_dag` | +| [Ошибки и повторы](../educational-tasks.md#errors) | `error_handling_dag` | +| [Пулы, XCom и TaskGroup](../educational-tasks.md#resources) | `resource_management_dag` | Описание поведения исходных примеров находится в [справочнике DAG](dag-specifications.md). Почта нужна только для отдельного задания по желанию; SMTP в стенде не настроен. @@ -59,8 +59,7 @@ airflow-docker/ ├── dags/ # Девять учебных DAG ├── data/input/ # Входные CSV и данные упражнений ├── data/output/ # Результаты задач -├── init-postgres.sql # Начальная подготовка учебной БД -└── educational-tasks.md # Маршрут и условия заданий +└── init-postgres.sql # Начальная подготовка учебной БД ``` `dags/` и `data/` смонтированы в контейнеры как `/opt/airflow/dags/` и `/opt/airflow/data/`. Правки DAG подхватываются планировщиком; перед новым запуском дождитесь обновления вкладки Code. Резервные копии Python-файлов храните вне `dags/`, чтобы не создать два определения одного `dag_id`. diff --git a/airflow-docker/dag-specifications.md b/airflow-docker/dag-specifications.md index e6f917c..127a732 100644 --- a/airflow-docker/dag-specifications.md +++ b/airflow-docker/dag-specifications.md @@ -1,6 +1,6 @@ # Что делают исходные DAG -Это справочник примеров для Airflow 2.9.2. Порядок прохождения, изменения кода и условия завершения находятся в [практических заданиях](educational-tasks.md). +Это справочник примеров для Airflow 2.9.2. Порядок прохождения, изменения кода и условия завершения находятся в [практических заданиях](../educational-tasks.md). | DAG и файл в `dags/` | Подготовка | Поведение и результат | |---|---|---| diff --git a/docs/specs/2026-09-28-practice-improvement-plan.md b/docs/specs/2026-09-28-practice-improvement-plan.md index f249d24..aa860f4 100644 --- a/docs/specs/2026-09-28-practice-improvement-plan.md +++ b/docs/specs/2026-09-28-practice-improvement-plan.md @@ -2,7 +2,7 @@ Дата: 2026-09-28. Подход согласован с автором курса. Реализация завершена. Результаты и границы проверки сохранены в [протоколе](../validation/2026-09-28-practice.md). -Этот документ сохраняет решения, порядок доработки и результаты проверки. Текущие задания находятся в [educational-tasks.md](../../airflow-docker/educational-tasks.md), запуск стенда описан в [README](../../airflow-docker/README.md). +Этот документ сохраняет решения, порядок доработки и результаты проверки. Текущие задания находятся в [educational-tasks.md](../../educational-tasks.md), запуск стенда описан в [README](../../airflow-docker/README.md). ## Цель и границы @@ -48,7 +48,7 @@ ## Как оформлять задания -Сохраняется один основной документ для студентов - `educational-tasks.md`. Не нужно дублировать инструкции в отдельном сборнике дополнительных упражнений. +Задания находятся в корне репозитория рядом с главами учебника: `educational-tasks.md`. В `airflow-docker/` остаются инструкция запуска, стенд и примеры DAG. Основные и дополнительные упражнения сохраняются в одном документе. В начале блока: связь с главами учебника, необходимые исходные условия и короткий запуск готового примера. У задания: @@ -84,31 +84,31 @@ | Исходное задание | Возможность | Вход и зависимости | Решение и причина | Новое место | Наблюдаемый результат | |---|---|---|---|---|---| -| hello_world / 1 | PythonOperator, зависимости | Исходный DAG | Основное: небольшое изменение графа | [1.1](../../airflow-docker/educational-tasks.md#first-dag) | Новая задача и ее сообщение видны в UI | -| hello_world / 2 | Расписание, ограничения запусков | После 1.1 | Разделить: расписание основное, конкуренция по желанию | [1.3, 1.A](../../airflow-docker/educational-tasks.md#first-dag) | Видно расписание; второй запуск ожидает при лимите 1 | -| hello_world / 3 | BashOperator | После 1.1 | Основное: другой оператор и файл | [1.2](../../airflow-docker/educational-tasks.md#first-dag) | Файл содержит ожидаемый текст | -| sql_basic / 1 | SQL в задаче, схема таблицы | Исходный SQL-DAG и training | Разделить: UPDATE основное, схема по желанию | [2.2, 2.A](../../airflow-docker/educational-tasks.md#sql) | Возраст изменен; дополнительная колонка заполнена | -| sql_basic / 2 | PostgresHook и отчет | После 2.2, таблица с тремя строками | По желанию: дополнительная обработка | [2.B](../../airflow-docker/educational-tasks.md#sql) | Файл: 3 студента, средний возраст 23.33 после UPDATE | -| sql_basic / 3 | Connections и диагностика | Учебная БД, собственный Conn Id | Основное; вместо неопределенного обработчика - найти и исправить ошибку подключения | [2.1](../../airflow-docker/educational-tasks.md#sql) | Ошибка видна в логах, после исправления запуск успешен | -| file_operations / 1 | Генерация, валидация | Исходный файловый DAG | Разделить: простая трансформация основная, новые поля по желанию | [3.1, 3.A](../../airflow-docker/educational-tasks.md#files) | 100 обработанных строк; поля и фильтр проверены отдельно | -| file_operations / 2 | Отчет по отделам | Требует 3.A | По желанию; график исключен, поскольку требует отдельной зависимости и не знакомит с новой возможностью Airflow | [3.B](../../airflow-docker/educational-tasks.md#files) | Сумма численности отделов равна числу строк входа | -| file_operations / 3 | Параллельность, DQ, retries | Требует 3.A | По желанию; параллельность также видна в основном ETL-примере | [3.C](../../airflow-docker/educational-tasks.md#files) | Раздельные файлы покрывают все отделы, сборка читает их | -| csv_to_postgres / 1 | Проведение поля через загрузку | После основной загрузки и DQ | По желанию: согласовать все четыре SQL/CSV-участка и проверку схемы | [4.A](../../airflow-docker/educational-tasks.md#load-dq) | status заполнен; DQ проходит; исходная схема восстановлена | -| csv_to_postgres / 2 | Специализированный оператор | Базовая схема orders | По желанию: сравнение двух реализаций уже знакомого действия | [4.B](../../airflow-docker/educational-tasks.md#load-dq) | После замены оператора загрузка и DQ успешны | -| csv_to_postgres_dq / 1 | Новая проверка, ошибка данных | Основная загрузка orders | Основное: положительный и отрицательный сценарии | [4.2](../../airflow-docker/educational-tasks.md#load-dq) | Отрицательная сумма вызывает failed; восстановление возвращает success | -| csv_to_postgres_dq / 2 | Trigger Rules и итог DAG Run | После 4.2 | По желанию: запуск всех проверок и общая сводка | [4.C](../../airflow-docker/educational-tasks.md#load-dq) | Все проверки завершаются, итог сохраняет ошибку | -| data_processing / 1 | Несколько источников | Исходный ETL, свои CSV | По желанию; основной разбор графа добавлен перед расширениями | [5.1, 5.A](../../airflow-docker/educational-tasks.md#etl) | После JOIN остаются 6 заказов и выручка 2105 | -| data_processing / 2 | Отчет pandas | После основного запуска, products не нужен | По желанию | [5.B](../../airflow-docker/educational-tasks.md#etl) | Топ заказов и средние сверяются с шестью исходными строками | -| data_processing / 3 | Валидация, callback, архив | После 5.1; поля продуктов необязательны | По желанию с конкретной ошибкой и восстановлением | [5.C](../../airflow-docker/educational-tasks.md#etl) | При ошибке архив не создается; после исправления совпадает с результатом | -| branching / 1 | Выбор ветки и слияние | Исходный DAG, Variable branch_format | Разделить: две управляемые ветки основные, третья по желанию | [6.1, 6.A](../../airflow-docker/educational-tasks.md#branching) | Выбранная ветка success, другая skipped, слияние success | -| branching / 2 | Работа выбранной ветки с данными | Два явно заданных входных файла | По желанию; использовать XCom текущего запуска вместо старых файлов другой ветки | [6.B](../../airflow-docker/educational-tasks.md#branching) | Оба формата дают одинаковые агрегаты в отдельных запусках | -| branching / 3 | Variables, неизвестный параметр | После 6.1 | Variable включена в основное; fallback объединен с третьей веткой | [6.1, 6.A](../../airflow-docker/educational-tasks.md#branching) | Неизвестное значение дает понятную ошибку либо review-ветку после расширения | -| error_handling / 1 | Повторы и задержка | Исходный DAG, Variable error_mode | Разделить: один управляемый повтор основной, backoff по желанию | [7.1, 7.2, 7.A](../../airflow-docker/educational-tasks.md#errors) | Две попытки; с нулем повторов - failed | -| error_handling / 2 | Типы ошибок и оповещение | После 7.2 | По желанию; локальный callback без внешней почты | [7.B](../../airflow-docker/educational-tasks.md#errors) | После исчерпания повторов есть сообщение с task_id и причиной | -| error_handling / 3 | logging | После 7.1 | Основное небольшое дополнение вместо механической замены всех print | [7.3](../../airflow-docker/educational-tasks.md#errors) | Собственные сообщения видны в логах | -| Дополнительные / 1 | Пулы и слоты | Готовый resource_management_dag, пул из 2 слотов | Основной короткий эксперимент; резервное копирование заменено имитацией чтения | [8.1](../../airflow-docker/educational-tasks.md#resources) | Задачи с весами 2 и 1 выполняются последовательно | -| Дополнительные / 2 | XCom и Jinja | После 8.1 | Основная метрика; шаблон Bash по желанию | [8.2, 8.A](../../airflow-docker/educational-tasks.md#resources) | total_rows = 50, шаблон выводит то же значение | -| Дополнительные / 3 | TaskGroup и алертинг | После 8.2; для почты нужен SMTP | Разделить: группировка основная, внешняя доставка по желанию | [8.3, 8.B](../../airflow-docker/educational-tasks.md#resources) | Группа раскрывается; XCom работает с полными task_id | +| hello_world / 1 | PythonOperator, зависимости | Исходный DAG | Основное: небольшое изменение графа | [1.1](../../educational-tasks.md#first-dag) | Новая задача и ее сообщение видны в UI | +| hello_world / 2 | Расписание, ограничения запусков | После 1.1 | Разделить: расписание основное, конкуренция по желанию | [1.3, 1.A](../../educational-tasks.md#first-dag) | Видно расписание; второй запуск ожидает при лимите 1 | +| hello_world / 3 | BashOperator | После 1.1 | Основное: другой оператор и файл | [1.2](../../educational-tasks.md#first-dag) | Файл содержит ожидаемый текст | +| sql_basic / 1 | SQL в задаче, схема таблицы | Исходный SQL-DAG и training | Разделить: UPDATE основное, схема по желанию | [2.2, 2.A](../../educational-tasks.md#sql) | Возраст изменен; дополнительная колонка заполнена | +| sql_basic / 2 | PostgresHook и отчет | После 2.2, таблица с тремя строками | По желанию: дополнительная обработка | [2.B](../../educational-tasks.md#sql) | Файл: 3 студента, средний возраст 23.33 после UPDATE | +| sql_basic / 3 | Connections и диагностика | Учебная БД, собственный Conn Id | Основное; вместо неопределенного обработчика - найти и исправить ошибку подключения | [2.1](../../educational-tasks.md#sql) | Ошибка видна в логах, после исправления запуск успешен | +| file_operations / 1 | Генерация, валидация | Исходный файловый DAG | Разделить: простая трансформация основная, новые поля по желанию | [3.1, 3.A](../../educational-tasks.md#files) | 100 обработанных строк; поля и фильтр проверены отдельно | +| file_operations / 2 | Отчет по отделам | Требует 3.A | По желанию; график исключен, поскольку требует отдельной зависимости и не знакомит с новой возможностью Airflow | [3.B](../../educational-tasks.md#files) | Сумма численности отделов равна числу строк входа | +| file_operations / 3 | Параллельность, DQ, retries | Требует 3.A | По желанию; параллельность также видна в основном ETL-примере | [3.C](../../educational-tasks.md#files) | Раздельные файлы покрывают все отделы, сборка читает их | +| csv_to_postgres / 1 | Проведение поля через загрузку | После основной загрузки и DQ | По желанию: согласовать все четыре SQL/CSV-участка и проверку схемы | [4.A](../../educational-tasks.md#load-dq) | status заполнен; DQ проходит; исходная схема восстановлена | +| csv_to_postgres / 2 | Специализированный оператор | Базовая схема orders | По желанию: сравнение двух реализаций уже знакомого действия | [4.B](../../educational-tasks.md#load-dq) | После замены оператора загрузка и DQ успешны | +| csv_to_postgres_dq / 1 | Новая проверка, ошибка данных | Основная загрузка orders | Основное: положительный и отрицательный сценарии | [4.2](../../educational-tasks.md#load-dq) | Отрицательная сумма вызывает failed; восстановление возвращает success | +| csv_to_postgres_dq / 2 | Trigger Rules и итог DAG Run | После 4.2 | По желанию: запуск всех проверок и общая сводка | [4.C](../../educational-tasks.md#load-dq) | Все проверки завершаются, итог сохраняет ошибку | +| data_processing / 1 | Несколько источников | Исходный ETL, свои CSV | По желанию; основной разбор графа добавлен перед расширениями | [5.1, 5.A](../../educational-tasks.md#etl) | После JOIN остаются 6 заказов и выручка 2105 | +| data_processing / 2 | Отчет pandas | После основного запуска, products не нужен | По желанию | [5.B](../../educational-tasks.md#etl) | Топ заказов и средние сверяются с шестью исходными строками | +| data_processing / 3 | Валидация, callback, архив | После 5.1; поля продуктов необязательны | По желанию с конкретной ошибкой и восстановлением | [5.C](../../educational-tasks.md#etl) | При ошибке архив не создается; после исправления совпадает с результатом | +| branching / 1 | Выбор ветки и слияние | Исходный DAG, Variable branch_format | Разделить: две управляемые ветки основные, третья по желанию | [6.1, 6.A](../../educational-tasks.md#branching) | Выбранная ветка success, другая skipped, слияние success | +| branching / 2 | Работа выбранной ветки с данными | Два явно заданных входных файла | По желанию; использовать XCom текущего запуска вместо старых файлов другой ветки | [6.B](../../educational-tasks.md#branching) | Оба формата дают одинаковые агрегаты в отдельных запусках | +| branching / 3 | Variables, неизвестный параметр | После 6.1 | Variable включена в основное; fallback объединен с третьей веткой | [6.1, 6.A](../../educational-tasks.md#branching) | Неизвестное значение дает понятную ошибку либо review-ветку после расширения | +| error_handling / 1 | Повторы и задержка | Исходный DAG, Variable error_mode | Разделить: один управляемый повтор основной, backoff по желанию | [7.1, 7.2, 7.A](../../educational-tasks.md#errors) | Две попытки; с нулем повторов - failed | +| error_handling / 2 | Типы ошибок и оповещение | После 7.2 | По желанию; локальный callback без внешней почты | [7.B](../../educational-tasks.md#errors) | После исчерпания повторов есть сообщение с task_id и причиной | +| error_handling / 3 | logging | После 7.1 | Основное небольшое дополнение вместо механической замены всех print | [7.3](../../educational-tasks.md#errors) | Собственные сообщения видны в логах | +| Дополнительные / 1 | Пулы и слоты | Готовый resource_management_dag, пул из 2 слотов | Основной короткий эксперимент; резервное копирование заменено имитацией чтения | [8.1](../../educational-tasks.md#resources) | Задачи с весами 2 и 1 выполняются последовательно | +| Дополнительные / 2 | XCom и Jinja | После 8.1 | Основная метрика; шаблон Bash по желанию | [8.2, 8.A](../../educational-tasks.md#resources) | total_rows = 50, шаблон выводит то же значение | +| Дополнительные / 3 | TaskGroup и алертинг | После 8.2; для почты нужен SMTP | Разделить: группировка основная, внешняя доставка по желанию | [8.3, 8.B](../../educational-tasks.md#resources) | Группа раскрывается; XCom работает с полными task_id | Основной маршрут сохраняет изменения внутри блоков. Общие данные есть только в паре загрузка/DQ; изменение схемы вынесено после основных опытов, с обратным ALTER и возвратом списков колонок. Задания 3.B и 3.C явно зависят от дополнительного 3.A, но дальнейшая основная практика от этой ветки не зависит. Variables возвращаются к исходным значениям перед переходом дальше. diff --git a/docs/validation/2026-09-28-practice.md b/docs/validation/2026-09-28-practice.md index 0a96978..1327230 100644 --- a/docs/validation/2026-09-28-practice.md +++ b/docs/validation/2026-09-28-practice.md @@ -1,6 +1,6 @@ # Проверка обновленной практики -Дата: 2026-09-28. Код примеров: коммит `cf1b770`; задания и навигация: `d8dfe83`. Проверка выполнена авторским прогоном по [заданиям](../../airflow-docker/educational-tasks.md). Это проверка воспроизводимости инструкций; время и трудность прохождения на студентах не измерялись. +Дата: 2026-09-28. Код примеров: коммит `cf1b770`; задания и навигация: `d8dfe83`. Проверка выполнена авторским прогоном по [заданиям](../../educational-tasks.md). Это проверка воспроизводимости инструкций; время и трудность прохождения на студентах не измерялись. ## Окружение diff --git a/airflow-docker/educational-tasks.md b/educational-tasks.md similarity index 98% rename from airflow-docker/educational-tasks.md rename to educational-tasks.md index 78f0c69..b7c9437 100644 --- a/airflow-docker/educational-tasks.md +++ b/educational-tasks.md @@ -19,7 +19,7 @@ | [7. Ошибки и повторы](#errors) | `error_handling_dag.py` | Попытки, обработчики, итог запуска | Главы 6 и 9 | | [8. Пулы и группировка](#resources) | `resource_management_dag.py` | Слоты, XCom, TaskGroup | Глава 9 | -Главы доступны в [оглавлении учебника](../README.md). Инструкция запуска и параметры учебной БД находятся в [README стенда](README.md). +Главы доступны в [оглавлении учебника](README.md). Инструкция запуска и параметры учебной БД находятся в [README стенда](airflow-docker/README.md), файлы примеров - в [airflow-docker/dags/](airflow-docker/dags/). Откройте http://localhost:8080, войдите как `admin` / `admin`. Для выбранного DAG включите переключатель и нажмите Trigger DAG. Дождитесь завершения запуска, затем откройте его граф и логи задач. После правки Python-файла дождитесь обновления вкладки Code; новый запуск должен использовать новый код. Если DAG исчез или перестал обновляться, проверьте Import Errors и логи scheduler. @@ -420,7 +420,7 @@ ADD COLUMN status VARCHAR(20) NOT NULL DEFAULT 'NEW'; По желанию. Сложность: высокая. Требуется 7.B: вернитесь к `error_handling_dag.py` с callback отказа. Нужен отдельно настроенный SMTP и адрес, на который вы можете отправить тестовое письмо. В стандартном стенде SMTP не настроен; отсутствие письма здесь не означает ошибку основного маршрута. -Используйте пример из [раздела об алертинге](../09%20-%20Пулы,%20XCom,%20TaskGroup%20и%20алертинг%20в%20Airflow.md#система-оповещений-алертинг). Добавьте отправку письма в callback отказа. Укажите DAG, задачу, идентификатор запуска и причину ошибки. Для успешного DAG Run задайте отдельный `on_success_callback` у `DAG(...)`. +Используйте пример из [раздела об алертинге](09%20-%20Пулы,%20XCom,%20TaskGroup%20и%20алертинг%20в%20Airflow.md#система-оповещений-алертинг). Добавьте отправку письма в callback отказа. Укажите DAG, задачу, идентификатор запуска и причину ошибки. Для успешного DAG Run задайте отдельный `on_success_callback` у `DAG(...)`. **Что должно получиться:** после постоянной ошибки приходит сообщение об отказе, после успешного запуска - об успехе. Уведомление не меняет результат обработки данных. Запуск с отказом сохраняет `failed`; ошибку доставки ищите в логах callback. Возвращение к основной практике не требует настройки SMTP: отключите почтовый callback и верните исходный режим ошибок. -- 2.54.0 From 26730c648357712a4074207f148fb513549ecb49 Mon Sep 17 00:00:00 2001 From: Dmitry Dementev Date: Mon, 28 Sep 2026 09:44:28 -0400 Subject: [PATCH 6/6] =?UTF-8?q?docs(practice):=20=D1=81=D0=BE=D0=B3=D0=BB?= =?UTF-8?q?=D0=B0=D1=81=D0=BE=D0=B2=D0=B0=D1=82=D1=8C=20=D1=82=D0=B5=D0=BE?= =?UTF-8?q?=D1=80=D0=B8=D1=8E=20=D1=81=20=D1=83=D1=87=D0=B5=D0=B1=D0=BD?= =?UTF-8?q?=D1=8B=D0=BC=20=D1=81=D1=82=D0=B5=D0=BD=D0=B4=D0=BE=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...ное введение в ключевые понятия Airflow.md | 8 +- ...оны, переменные и подключения в Airflow.md | 123 ++++---- ...ы, XCom, TaskGroup и алертинг в Airflow.md | 262 +++--------------- .../2026-09-28-practice-improvement-plan.md | 4 +- docs/validation/2026-09-28-theory-review.md | 50 ++++ educational-tasks.md | 21 +- 6 files changed, 180 insertions(+), 288 deletions(-) create mode 100644 docs/validation/2026-09-28-theory-review.md diff --git a/02 - Понятное введение в ключевые понятия Airflow.md b/02 - Понятное введение в ключевые понятия Airflow.md index ccac530..3050d96 100644 --- a/02 - Понятное введение в ключевые понятия Airflow.md +++ b/02 - Понятное введение в ключевые понятия Airflow.md @@ -107,12 +107,14 @@ dag = DAG('greeting_dag', start_date=datetime(2023, 1, 1), catchup=False) -start_task = DummyOperator(task_id='start_process', retries=2) -greeting_task = PythonOperator(task_id='greeting_task', python_callable=greet_user) +start_task = DummyOperator(task_id='start_process', retries=2, dag=dag) +greeting_task = PythonOperator(task_id='greeting_task', python_callable=greet_user, dag=dag) start_task >> greeting_task ``` +Параметр `dag=dag` связывает каждый оператор с созданным DAG. Если объявить задачи внутри `with DAG(...) as dag:`, привязка произойдет автоматически. Стрелка `>>` задает порядок выполнения задач. + В этом примере задача `greeting_task` использует `PythonOperator` для запуска функции `greet_user`, которая выводит приветственное сообщение в логи. Airflow предоставляет множество готовых операторов: @@ -157,4 +159,4 @@ flowchart LR Task1 -.->|Как делать?| Op1["Оператор\n(например, PythonOperator)"] Task2 -.->|Чего ждать?| Sen1["Сенсор\n(например, S3Sensor)"] Task3 -.->|Как делать?| Op2["Оператор\n(например, PostgresOperator)"] -``` \ No newline at end of file +``` diff --git a/07 - Шаблоны, переменные и подключения в Airflow.md b/07 - Шаблоны, переменные и подключения в Airflow.md index 618fc87..5eb5226 100644 --- a/07 - Шаблоны, переменные и подключения в Airflow.md +++ b/07 - Шаблоны, переменные и подключения в Airflow.md @@ -8,45 +8,53 @@ Синтаксис шаблонов интуитивно понятен — переменная заключается в двойные фигурные скобки с пробелами по краям. Для эффективного использования достаточно знать, какое значение возвращает конкретный шаблон, и разместить его в нужном месте кода. -Подробный справочник доступных шаблонов можно найти в [официальной документации Airflow](https://airflow.apache.org/docs/apache-airflow/stable/templates-ref.html#variables). +Подробный справочник доступных шаблонов можно найти в [документации Airflow 2.9.2](https://airflow.apache.org/docs/apache-airflow/2.9.2/templates-ref.html#variables). -Вот практические примеры использования: +Каждый пример ниже - отдельный DAG. Для запуска сохраните один блок кода в файл `.py` внутри `airflow-docker/dags/`. Затем найдите DAG в UI и запустите его вручную. **Передача даты выполнения в задачу:** -В этом примере создается DAG с идентификатором "template_example", который запускается каждые 15 минут. Задача "display_date" использует шаблон {{ ds }} для вывода текущей даты выполнения в формате YYYY-MM-DD. +В этом примере DAG `template_example` запускается каждые 15 минут. Задача `display_date` выводит логическую дату запуска в формате YYYY-MM-DD через шаблон `{{ ds }}`. ```python -... +from datetime import datetime + +from airflow import DAG +from airflow.operators.bash import BashOperator dag = DAG( dag_id="template_example", schedule_interval="*/15 * * * *", - default_args=default_args -) + start_date=datetime(2023, 1, 1), + catchup=False, +) -t1 = BashOperator(task_id="display_date", bash_command="echo {{ ds }}") - -t1 +t1 = BashOperator(task_id="display_date", bash_command="echo {{ ds }}", dag=dag) ``` **Использование шаблонов для лучшей отслеживаемости задач:** В этом примере создается DAG с идентификатором "template_tracking_example", который запускается каждые 20 минут. Вторая задача использует шаблон {{ ds }} в команде bash, чтобы явно указывать дату обработки в логах. ```python -... +from datetime import datetime + +from airflow import DAG +from airflow.operators.bash import BashOperator dag = DAG( dag_id="template_tracking_example", schedule_interval="*/20 * * * *", - default_args=default_args -) + start_date=datetime(2023, 1, 1), + catchup=False, +) -t1 = BashOperator(task_id="show_date", bash_command="echo {{ ds }}") -t2 = BashOperator(task_id="process_for_date", bash_command="echo Processing for {{ ds }}") +t1 = BashOperator(task_id="show_date", bash_command="echo {{ ds }}", dag=dag) +t2 = BashOperator(task_id="process_for_date", bash_command="echo Processing for {{ ds }}", dag=dag) t1 >> t2 ``` +В первом DAG одна задача, во втором - две связанные задачи. В Details выбранной задачи откройте Rendered Templates: вместо `{{ ds }}` будет дата. Та же дата появится в логе после запуска. Привязку операторов к DAG здесь задает `dag=dag`. + # Безопасные переменные (Variables) Переменные Airflow представляют собой пары "ключ-значение", хранящиеся в метадатабазе системы. Они идеально подходят для хранения конфигурационных параметров, таких как пути к скриптам, имена таблиц или другие настройки, которые должны быть доступны в разных DAG. @@ -134,69 +142,62 @@ task = BashOperator( - Системы уведомлений (Email, Telegram) - И многие другие через Airflow Providers -При использовании операторов, взаимодействующих с внешними системами, Airflow автоматически ищет соответствующее подключение с суффиксом `_default`. Однако можно явно указать альтернативное подключение: +Оператор получает настройки по идентификатору подключения. Например, `PostgresOperator` использует параметр `postgres_conn_id`. PostgreSQL provider уже установлен в учебном стенде. -В приведенном примере используется PostgresOperator для создания таблицы в базе данных. Вместо использования подключения по умолчанию, явно указывается подключение с идентификатором 'my_postgres_conn'. - -```python -from airflow.providers.postgres.operators.postgres import PostgresOperator - -create_table = PostgresOperator( - task_id='create_user_table', - sql=''' - CREATE TABLE users( - user_id integer NOT NULL, - created_at TIMESTAMP NOT NULL - );''', - postgres_conn_id='my_postgres_conn' -) -``` - -Управление подключениями доступно через интерфейс Airflow (Admin → Connections). Если требуемый тип подключения отсутствует, его можно добавить установкой соответствующего Airflow Provider из [официального репозитория](https://airflow.apache.org/docs/#providers-packages-docs-apache-airflow-providers-index-html). - -Управление подключениями доступно через интерфейс Airflow (раздел **Admin → Connections**). Подключения хранятся в метадатабазе Airflow, а пароли и другие чувствительные поля шифруются с помощью Fernet и маскируются в UI и логах. +Подключения хранятся в базе метаданных Airflow. Пароли и другие чувствительные поля шифруются с помощью Fernet и маскируются в UI и логах. ### Как создать подключение к PostgreSQL через UI -Интерфейс ниже соответствует Airflow 2.9.x и стандартному Docker-стенду из документации: +Используйте учебную БД из [README стенда](airflow-docker/README.md#запуск). В контейнере Airflow она доступна по имени сервиса `postgres-training`; адрес `localhost:5432` предназначен для подключения с вашего компьютера. -1. Откройте веб-интерфейс Airflow. -2. В верхнем меню выберите **Admin → Connections**. -3. В правом верхнем углу нажмите кнопку **+ Add a new record**. -4. В форме укажите параметры: +1. Откройте **Admin > Connections** и нажмите **+ Add a new record**. +2. Заполните форму: - - **Connection Id**: `my_postgres_conn` - Это имя мы будем использовать в коде DAG (параметр `postgres_conn_id`). + - **Connection Id**: `my_postgres_conn`. Это имя будет указано в коде DAG. - **Connection Type**: `Postgres` - - **Host**: `postgres` - (так называется контейнер PostgreSQL в типовом `docker-compose.yaml` из официальной инструкции). - - **Schema**: `airflow` - (имя базы данных; в учебном стенде можно использовать стандартную БД). - - **Login**: `airflow` - - **Password**: `airflow` + - **Host**: `postgres-training` + - **Database / Schema**: `training` + - **Login**: `student` + - **Password**: `student` - **Port**: `5432` -5. Нажмите кнопку **Test** (если доступна) — Airflow попробует подключиться к базе. -6. Если тест успешен, нажмите **Save**. +3. Нажмите **Save**. Кнопка **Test** может быть отключена в конфигурации Airflow; подключение можно проверить запуском DAG ниже. -Теперь подключение с идентификатором `my_postgres_conn` доступно во всех DAG’ах. В примере ниже PostgresOperator явно использует это подключение: +База `airflow` на сервисе `postgres-metadata` хранит служебные данные Airflow. Для учебных таблиц используйте `training`. + +Сохраните пример в `airflow-docker/dags/connection_example_dag.py` и запустите `connection_example` через UI: ```python +from datetime import datetime + +from airflow import DAG from airflow.providers.postgres.operators.postgres import PostgresOperator -create_table = PostgresOperator( - task_id="create_user_table", - sql=""" - CREATE TABLE users( - user_id INTEGER NOT NULL, - created_at TIMESTAMP NOT NULL - ); - """, - postgres_conn_id="my_postgres_conn", -) +with DAG( + dag_id="connection_example", + start_date=datetime(2023, 1, 1), + schedule=None, + catchup=False, +) as dag: + create_table = PostgresOperator( + task_id="create_user_table", + sql=""" + CREATE TABLE IF NOT EXISTS public.connection_demo_users ( + user_id INTEGER NOT NULL, + created_at TIMESTAMP NOT NULL + ); + """, + postgres_conn_id="my_postgres_conn", + ) ``` -Для успешной работы с внешними системами сначала необходимо создать соответствующее подключение, а затем использовать его идентификатор в операторах вашего DAG. +Задача должна завершиться успешно. В учебной БД появится пустая таблица `public.connection_demo_users`; повторный запуск сохранит ее. Проверить наличие таблицы можно из SQL-консоли стенда: + +```sql +SELECT to_regclass('public.connection_demo_users'); +``` + +Результат - `connection_demo_users`. Если задача упала, откройте ее лог и сверьте Conn Id в коде с именем подключения, затем Host и остальные поля формы. Правка настроек действует при следующем выполнении задачи. Более подробную информацию о настройке подключений можно найти в [документации Airflow](https://airflow.apache.org/docs/apache-airflow/2.9.3/howto/connection.html). diff --git a/09 - Пулы, XCom, TaskGroup и алертинг в Airflow.md b/09 - Пулы, XCom, TaskGroup и алертинг в Airflow.md index 5cd97b1..7f1f58b 100644 --- a/09 - Пулы, XCom, TaskGroup и алертинг в Airflow.md +++ b/09 - Пулы, XCom, TaskGroup и алертинг в Airflow.md @@ -2,7 +2,7 @@ В этом материале мы познакомимся с управлением ресурсами через пулы задач, обменом данными между задачами через XCom, группировкой задач с помощью TaskGroup и настройкой системы оповещений (алертинга) в Airflow. -Начнем с улучшения нашего базового пайплайна — проведем рефакторинг кода для лучшей читаемости и поддержки. +Опыты с этими возможностями собраны в [блоке 8 практики](educational-tasks.md#resources); callback и ошибки разобраны в [блоке 7](educational-tasks.md#errors). ## Управление ресурсами с помощью пулов задач @@ -151,72 +151,62 @@ XCom упрощает взаимодействие между задачами ## Группировка задач с помощью TaskGroup -Для удобства визуализации задачи можно группировать в веб-интерфейсе Airflow (начиная с версии 2.0). Повторяющиеся или логически связанные задачи можно объединить в группы: +TaskGroup объединяет задачи в сворачиваемую группу на графе. Зависимости и состояния остаются у отдельных задач. Группировку задают в Python-коде; UI позволяет раскрыть ее и посмотреть содержимое. -В этом примере задачи сгруппированы в три логические группы: 'data_extraction', 'data_transformation' и 'data_loading'. Каждая группа содержит несколько задач, которые выполняются последовательно внутри группы. Затем группы связаны между собой, чтобы показать общий порядок выполнения этапов обработки данных. +В примере три группы. Внутри каждой задачи идут последовательно. Группы извлечения и преобразования независимы, а загрузка ждет завершения обеих. `EmptyOperator` ничего не обрабатывает: здесь мы смотрим только на устройство графа. ```python -with TaskGroup("data_extraction") as extraction_group: - extract_1 = DummyOperator(task_id="extract_source_1") - extract_2 = DummyOperator(task_id="extract_source_2") - extract_3 = DummyOperator(task_id="extract_source_3") - extract_1 >> extract_2 >> extract_3 +from datetime import datetime -with TaskGroup("data_transformation") as transformation_group: - transform_1 = DummyOperator(task_id="transform_step_1") - transform_2 = DummyOperator(task_id="transform_step_2") - transform_1 >> transform_2 +from airflow import DAG +from airflow.operators.empty import EmptyOperator +from airflow.utils.task_group import TaskGroup -with TaskGroup("data_loading") as loading_group: - load_1 = DummyOperator(task_id="load_to_target_1") - load_2 = DummyOperator(task_id="load_to_target_2") - load_1 >> load_2 +with DAG( + dag_id="task_group_example", + start_date=datetime(2023, 1, 1), + schedule=None, + catchup=False, +) as dag: + with TaskGroup("data_extraction") as extraction_group: + extract_1 = EmptyOperator(task_id="extract_source_1") + extract_2 = EmptyOperator(task_id="extract_source_2") + extract_3 = EmptyOperator(task_id="extract_source_3") + extract_1 >> extract_2 >> extract_3 -[extraction_group, transformation_group] >> loading_group + with TaskGroup("data_transformation") as transformation_group: + transform_1 = EmptyOperator(task_id="transform_step_1") + transform_2 = EmptyOperator(task_id="transform_step_2") + transform_1 >> transform_2 + + with TaskGroup("data_loading") as loading_group: + load_1 = EmptyOperator(task_id="load_to_target_1") + load_2 = EmptyOperator(task_id="load_to_target_2") + load_1 >> load_2 + + [extraction_group, transformation_group] >> loading_group ``` -Обратите внимание: при использовании TaskGroup последовательность задач указывается внутри группы после объявления всех задач, а в конце DAG описывается последовательность выполнения самих групп. +Здесь TaskGroup создается внутри `with DAG(...)`, поэтому группа и ее задачи получают один DAG. При отдельном объявлении `dag = DAG(...)` группу нужно привязать явно: `TaskGroup("data_extraction", dag=dag)`. -Ниже приведена упрощённая схема зависимостей между тремя группами задач: +Схема повторяет зависимости из кода: ```mermaid flowchart LR - - %% group1 - subgraph G1["group1"] - g1_t1["task1"] - g1_t2["task2"] - g1_t3["task3"] - - g1_t1 --> g1_t2 - g1_t1 --> g1_t3 + subgraph extraction["data_extraction"] + e1["extract_source_1"] --> e2["extract_source_2"] --> e3["extract_source_3"] end - - %% group2 - subgraph G2["group2"] - g2_t1["task1"] - g2_t2["task2"] - - g2_t1 --> g2_t2 + subgraph transformation["data_transformation"] + t1["transform_step_1"] --> t2["transform_step_2"] end - - %% group3 - subgraph G3["group3"] - g3_t1["task1"] - g3_t2["task2"] - - g3_t1 --> g3_t2 + subgraph loading["data_loading"] + l1["load_to_target_1"] --> l2["load_to_target_2"] end - - %% зависимости между группами - g1_t2 --> g3_t1 - g1_t3 --> g3_t1 - g2_t2 --> g3_t1 + e3 --> l1 + t2 --> l1 ``` -Визуально в интерфейсе Airflow группы задач отображаются как один узел с небольшим индикатором. Клик по нему разворачивает или сворачивает вложенные задачи, что значительно улучшает восприятие DAG с большим количеством задач и связей, особенно когда в них десятки и сотни задач. - -TaskGroup — это удобный способ логической группировки задач, который помогает упростить код и представить сложные пайплайны более компактно. +Для запуска сохраните пример в `airflow-docker/dags/task_group_example_dag.py`. После ручного запуска все семь задач должны завершиться успешно. Нажмите название группы со стрелкой, чтобы раскрыть ее. Полный идентификатор первой задачи - `data_extraction.extract_source_1`: имя группы становится префиксом `task_id`. Такой идентификатор нужен при обращении к задаче через XCom. ## Система оповещений (алертинг) @@ -285,174 +275,12 @@ def notify_success(context): Проверка: сначала воспроизведите окончательный отказ, затем успешный запуск. Сверьте текст каждого письма с состоянием соответствующего DAG Run. Упражнения находятся в [блоке ошибок](educational-tasks.md#errors) и [задании на почту](educational-tasks.md#resources). -## Практическое применение: улучшенный DAG +## Практика на учебном стенде -Теперь применим изученные концепции для усовершенствования нашего DAG. Добавим переменные и разобьем задачи на логические группы. +Откройте [resource_management_dag.py](airflow-docker/dags/resource_management_dag.py). Он имитирует чтение двух источников и передает небольшие результаты через XCom. Для запуска нужен пул `training_pool` с двумя слотами; создайте его в Admin > Pools. -Сначала создадим переменную `DATABASE_URL` со строкой подключения к базе данных через веб-интерфейс Airflow и импортируем ее в коде DAG: +В исходном DAG задачи `read_customers` и `read_orders` не зависят друг от друга, но занимают два и один слот соответственно. Поэтому они выполняются последовательно. `calculate_metrics` получает их результаты через `xcom_pull`, а `log_metrics` пишет в лог словарь `customers=20`, `orders=30`. -В этом примере используется переменная 'database_connection_string', предварительно созданная в интерфейсе Airflow, для хранения строки подключения к базе данных. Это позволяет избежать жесткого кодирования конфиденциальной информации в коде DAG и упрощает настройку подключения для разных окружений. +В [заданиях 8.1-8.3](educational-tasks.md#resources) вы измените вес задачи в пуле, добавите метрику и объедините задачи чтения в TaskGroup. Там описаны изменения, ожидаемые результаты и места проверки. Файлы CSV и подключение к БД для этого примера не нужны: чтение источников имитируется. -```python -import os -import datetime as dt -import pandas as pd -from airflow.models import DAG -from airflow.operators.bash import BashOperator -from airflow.operators.python import PythonOperator -from airflow.operators.dummy import DummyOperator -from airflow.utils.task_group import TaskGroup -from airflow.models import Variable -from sqlalchemy import create_engine - -DATABASE_URL = Variable.get('database_connection_string') - -args = { - 'owner': 'analytics_team', - 'start_date': dt.datetime(2021, 6, 15), - 'retries': 2, - 'retry_delay': dt.timedelta(minutes=2), -} - -# функции для обработки данных -def get_file_path(file_name): - return os.path.join(os.path.expanduser('~/data'), file_name) - -def load_customer_data(): - file_path = get_file_path('customer_data.csv') - df = pd.read_csv(file_path) - engine = create_engine(DATABASE_URL) - df.to_sql('customers', engine, index=False, if_exists='replace', schema='staging') - -def aggregate_customer_data(): - engine = create_engine(DATABASE_URL) - customer_df = pd.read_sql('select * from staging.customers', con=engine) - - df = customer_df.groupby(['region', 'category']).agg( - total_orders=('orders', 'sum'), - avg_amount=('amount', 'mean') - ).reset_index() - - df.to_sql('customer_summary', engine, index=False, if_exists='replace', schema='analytics') - -dag = DAG( - dag_id='customer_pipeline_enhanced', - schedule_interval=None, - default_args=args, -) -``` - -Теперь разобьем задачи на логические группы и добавим Jinja-шаблоны для доступа к контексту выполнения: - -В этом примере создается начальная задача 'pipeline_start', которая использует Jinja-шаблоны для вывода информации о запуске DAG, включая идентификатор запуска (run_id) и информацию о DAG Run. Затем задачи группируются в логическую группу 'data_processing_stage', что улучшает структуру и читаемость DAG. - -```python -# Начальная задача с информацией о запуске -start_task = BashOperator( - task_id='pipeline_start', - bash_command='echo "Pipeline started! Run ID: {{ run_id }} | DAG Run: {{ dag_run }}"', - dag=dag, -) - -# Группа задач по предварительной обработке данных -with TaskGroup(group_id="data_processing_stage") as data_processing: - # Загрузка данных - load_customer_dataset = PythonOperator( - task_id='load_customer_data', - python_callable=load_customer_data, - dag=dag, - ) - # Агрегация и запись данных - aggregate_customer_dataset = PythonOperator( - task_id='aggregate_customer_data', - python_callable=aggregate_customer_data, - dag=dag, - ) - load_customer_dataset >> aggregate_customer_dataset - -# Установка последовательности выполнения -start_task >> data_processing -``` - -Поскольку последовательность задач внутри групп указывается при их создании, в конце необходимо определить порядок выполнения самих групп, чтобы планировщик понимал общую логику выполнения. - -Итоговый DAG будет выглядеть следующим образом: - -```python -import os -import datetime as dt -import pandas as pd -from airflow.models import DAG -from airflow.operators.bash import BashOperator -from airflow.operators.python import PythonOperator -from airflow.operators.dummy import DummyOperator -from airflow.utils.task_group import TaskGroup -from airflow.models import Variable -from sqlalchemy import create_engine - -DATABASE_URL = Variable.get('database_connection_string') - -args = { - 'owner': 'analytics_team', - 'start_date': dt.datetime(2021, 6, 15), - 'retries': 2, - 'retry_delay': dt.timedelta(minutes=2), -} - -def get_file_path(file_name): - return os.path.join(os.path.expanduser('~/data'), file_name) - -def load_customer_data(): - file_path = get_file_path('customer_data.csv') - df = pd.read_csv(file_path) - engine = create_engine(DATABASE_URL) - df.to_sql('customers', engine, index=False, if_exists='replace', schema='staging') - -def aggregate_customer_data(): - engine = create_engine(DATABASE_URL) - customer_df = pd.read_sql('select * from staging.customers', con=engine) - - df = customer_df.groupby(['region', 'category']).agg( - total_orders=('orders', 'sum'), - avg_amount=('amount', 'mean') - ).reset_index() - - df.to_sql('customer_summary', engine, index=False, if_exists='replace', schema='analytics') - -dag = DAG( - dag_id='customer_pipeline_enhanced', - schedule_interval=None, - default_args=args, -) - -# Начальная задача -start_task = BashOperator( - task_id='pipeline_start', - bash_command='echo "Pipeline started! Run ID: {{ run_id }} | DAG Run: {{ dag_run }}"', - dag=dag, -) - -# Группа предварительной обработки -with TaskGroup(group_id="data_processing_stage") as data_processing: - load_customer_dataset = PythonOperator( - task_id='load_customer_data', - python_callable=load_customer_data, - dag=dag, - ) - aggregate_customer_dataset = PythonOperator( - task_id='aggregate_customer_data', - python_callable=aggregate_customer_data, - dag=dag, - ) - load_customer_dataset >> aggregate_customer_dataset - -start_task >> data_processing -``` - -В этом материале мы рассмотрели расширенные возможности Airflow, которые помогут улучшить работу ваших пайплайнов: -- Управление ресурсами с помощью пулов задач -- Обмен данными между задачами через XCom -- Логическая группировка задач с TaskGroup -- Настройка системы оповещений - -Помните: не стоит использовать все доступные функции сразу. Выбирайте инструменты последовательно и находите оптимальный набор возможностей под конкретную задачу. +Для опытов с callback используйте [error_handling_dag.py](airflow-docker/dags/error_handling_dag.py) и [блок 7](educational-tasks.md#errors). Отправка настоящей почты остается заданием по желанию. diff --git a/docs/specs/2026-09-28-practice-improvement-plan.md b/docs/specs/2026-09-28-practice-improvement-plan.md index aa860f4..9127f9a 100644 --- a/docs/specs/2026-09-28-practice-improvement-plan.md +++ b/docs/specs/2026-09-28-practice-improvement-plan.md @@ -28,12 +28,14 @@ ## Текущее состояние реализации -Исправления DAG сохранены в `cf1b770`, задания и навигация в `d8dfe83`. Последующие уточнения и этот протокол входят в завершающий коммит ветки `work/airflow-practice-refresh`. +Исправления DAG сохранены в `cf1b770`, задания и навигация в `d8dfe83`, уточнения после прохождения в `1277bff`. В `f6b450c` задания перенесены в корень репозитория. Все 25 исходных заданий сопоставлены с 34 упражнениями: 16 основных и 18 по желанию. Основной маршрут пройден без дополнительных упражнений. Затем проверены дополнительные расширения и возвраты к базовой схеме, режимам и настройкам. Сохранены [условия и результаты проверки](../validation/2026-09-28-practice.md), [состояния 70 контрольных и диагностических запусков](../validation/2026-09-28-practice-runs.jsonl) и снимки UI. Открытых этапов реализации нет. При следующей правке сверять затронутые задания с кодом и протоколом. Проверка не измеряет трудность на студентах; SMTP проверен локальным приемником, без внешнего почтового провайдера. +После независимого педагогического ревью исправлены примеры в главах 2, 7 и 9 и уточнены ссылки на теорию перед практикой. Значимость находок, принятые решения и исполнение примеров записаны в [перепроверке ревью](../validation/2026-09-28-theory-review.md). Предложение расширить основную практику опытом с Clear оставлено до проверки на студентах; состав упражнений сохранен. + ## Согласованные решения 1. **Последовательность внутри блока.** Студент запускает исходный пример, меняет одну существенную настройку или часть графа и наблюдает результат. Изменения основных заданий внутри блока сохраняются. diff --git a/docs/validation/2026-09-28-theory-review.md b/docs/validation/2026-09-28-theory-review.md new file mode 100644 index 0000000..5ece226 --- /dev/null +++ b/docs/validation/2026-09-28-theory-review.md @@ -0,0 +1,50 @@ +# Перепроверка педагогического ревью + +Дата: 2026-09-28. Исходная версия: `f6b450c`. После независимого ревью замечания проверены по текстам, Compose и исполнением примеров в Apache Airflow 2.9.2. + +## Оценка значимости и решения + +Основные задания используют готовые DAG и правильные параметры учебной БД. Подтвержденные ошибки относятся к примерам теоретических глав: они мешают студенту, который переносит код из главы в стенд. Оценка их как блокировки всей основной практики была бы завышенной. + +| Находка | Что подтвердилось | Решение | +|---|---|---| +| Параметры Connections из другого стенда | В главе 7 предлагались `postgres / airflow / airflow`, хотя Compose и практика используют `postgres-training / training / student` | Исправлены поля формы. Добавлен полный DAG проверки подключения и наблюдаемый результат в БД | +| Задачи без привязки к DAG | Примеры `greeting_dag` и `template_tracking_example` падают при объявлении зависимостей; `template_example` создает DAG без задач | Добавлены привязки и объяснение. Примеры шаблонов дополнены импортами и настройками, чтобы их можно было запускать целиком | +| Неподготовленный итоговый пример главы 9 | Нет исходного CSV и подготовки схем БД, расходятся имена Variable. После подстановки Variable пример падает при создании TaskGroup | Вместо отдельного пайплайна глава направляет к существующему `resource_management_dag.py` и блоку 8. Пример самой TaskGroup сделан самостоятельным; схема согласована с кодом | +| Слишком широкое чтение перед упражнениями | Для XCom перед блоком 4 была указана вся глава 9 | В таблице маршрута даны ссылки на нужные разделы. Callback отмечен как подготовка к дополнительному 7.B | +| Дополнительный опыт с Clear | Нехватка понимания повторов у студентов не подтверждена. Основные задания уже показывают новые запуски и retry; в 4.1 есть объяснение различия повторной загрузки и нового CSV | Обязательная практика сохранена: 16 основных и 18 дополнительных заданий. Опыт с Clear имеет смысл добавлять после наблюдения за студентами, если они путают эти случаи | + +## Воспроизведение исходных ошибок + +Код извлечен из Python-блоков глав на `f6b450c` и исполнен в одноразовом контейнере образа `airflow-manual-practice-check:2.9.2`, ранее собранного из Dockerfile стенда. Версия внутри контейнера: `2.9.2`. + +В неполные примеры главы 7 переданы недостающие импорты и `default_args`, чтобы проверить именно привязку задач. Для итогового примера главы 9 `Variable.get` заменен на тестовое значение строки подключения: это позволило дойти до создания TaskGroup без подготовки посторонних данных. + +| Пример | Результат до правок | +|---|---| +| `greeting_dag` | `AirflowException: Tried to create relationships between tasks that don't have DAGs yet` | +| `template_example` | Разбор без исключения, список задач пуст | +| `template_tracking_example` | То же исключение при создании зависимостей | +| `customer_pipeline_enhanced` | `AirflowException: TaskGroup can only be used inside a dag` | + +## Проверка исправленных примеров + +Полные Python-блоки текущих глав исполнены без дополнений к коду. Затем вызван `DAG.test()` для логической даты `2024-01-02 UTC`; SQL-пример дополнительно запущен для `2024-01-03 UTC`. + +Окружение: тот же образ Airflow 2.9.2, отдельная SQLite для метаданных и отдельная PostgreSQL 16 с базой `training`. Подключение `my_postgres_conn` передано через окружение с полями из инструкции. Контейнеры работали во внутренней сети без опубликованных портов. Каталог репозитория смонтирован только для чтения. Временные контейнеры, сеть и данные удалены после проверки. + +| Пример | Проверенный результат | +|---|---| +| `greeting_dag` | Две успешные задачи; функция приветствия исполнилась | +| `template_example` | Одна успешная задача; результат Bash - `2024-01-02` | +| `template_tracking_example` | Две успешные задачи; результаты Bash - `2024-01-02` и `Processing for 2024-01-02` | +| `connection_example` | Оба запуска успешны; `to_regclass` возвращает `connection_demo_users`, таблица содержит 0 строк | +| `task_group_example` | Все семь задач успешны; проверены полные идентификаторы и шесть зависимостей, показанных на схеме | + +Все шесть DAG Run завершились успешно. Ссылки на файлы и разделы проверены после преобразования Markdown в HTML. Содержание упражнений после таблицы маршрута совпадает с исходной версией. + +## Границы проверки + +`DAG.test()` подтверждает разбор графа, выполнение задач, подстановку шаблонов и работу SQL. Этот прогон не проверяет работу планировщика по расписанию, форму Connections и отображение графов в UI. Основные DAG стенда не менялись; прежний [протокол практики](2026-09-28-practice.md) остается отдельной проверкой их поведения. + +Трудность и время прохождения на студентах не измерялись. Педагогическое предположение о пользе нового опыта с Clear остается гипотезой, а не открытой ошибкой реализации. diff --git a/educational-tasks.md b/educational-tasks.md index b7c9437..d4296dd 100644 --- a/educational-tasks.md +++ b/educational-tasks.md @@ -10,14 +10,23 @@ | Блок | Примеры | Что наблюдаем | Перед практикой | |---|---|---|---| -| [1. Первый DAG](#first-dag) | `hello_world_dag.py` | Задачи, зависимости, операторы, расписание | Главы 4, 5 и 8 | -| [2. SQL и подключения](#sql) | `sql_basic_dag.py` | Connection, SQL, ошибка подключения | Глава 7 | +| [1. Первый DAG](#first-dag) | `hello_world_dag.py` | Задачи, зависимости, операторы, расписание | Главы 4 и 5; для 1.3 - [расписания][schedules] | +| [2. SQL и подключения](#sql) | `sql_basic_dag.py` | Connection, SQL, ошибка подключения | [Подключения][connections] | | [3. Работа с файлами](#files) | `file_operations_dag.py` | Результаты отдельных задач | Главы 5 и 6 | -| [4. Загрузка и DQ](#load-dq) | `csv_to_postgres.py`, `csv_to_postgres_dq.py` | XCom, данные в БД, ошибка проверки | Главы 6, 7 и 9 | +| [4. Загрузка и DQ](#load-dq) | `csv_to_postgres.py`, `csv_to_postgres_dq.py` | XCom, данные в БД, ошибка проверки | Глава 6, [шаблоны][templates] и [XCom][xcom] | | [5. Разбор ETL](#etl) | `data_processing_dag.py` | Параллельные шаги и путь данных | Глава 5 | -| [6. Ветвление](#branching) | `branching_dag.py` | Variables, выбранная ветка, `skipped` | Главы 6 и 7 | -| [7. Ошибки и повторы](#errors) | `error_handling_dag.py` | Попытки, обработчики, итог запуска | Главы 6 и 9 | -| [8. Пулы и группировка](#resources) | `resource_management_dag.py` | Слоты, XCom, TaskGroup | Глава 9 | +| [6. Ветвление](#branching) | `branching_dag.py` | Variables, выбранная ветка, `skipped` | Глава 6 и [Variables][variables] | +| [7. Ошибки и повторы](#errors) | `error_handling_dag.py` | Попытки, обработчики, итог запуска | Глава 6; для 7.B - [callback][alerts] | +| [8. Пулы и группировка](#resources) | `resource_management_dag.py` | Слоты, XCom, TaskGroup | [Пулы][pools], [XCom][xcom] и [TaskGroup][taskgroups] | + +[schedules]: 08%20-%20Управление%20временем.md#настройка-автоматических-запусков +[connections]: 07%20-%20Шаблоны,%20переменные%20и%20подключения%20в%20Airflow.md#централизованные-подключения-connections +[templates]: 07%20-%20Шаблоны,%20переменные%20и%20подключения%20в%20Airflow.md#динамические-шаблоны-airflow-на-основе-jinja +[variables]: 07%20-%20Шаблоны,%20переменные%20и%20подключения%20в%20Airflow.md#безопасные-переменные-variables +[xcom]: 09%20-%20Пулы,%20XCom,%20TaskGroup%20и%20алертинг%20в%20Airflow.md#обмен-данными-между-задачами-xcom-и-контекст-выполнения +[pools]: 09%20-%20Пулы,%20XCom,%20TaskGroup%20и%20алертинг%20в%20Airflow.md#управление-ресурсами-с-помощью-пулов-задач +[taskgroups]: 09%20-%20Пулы,%20XCom,%20TaskGroup%20и%20алертинг%20в%20Airflow.md#группировка-задач-с-помощью-taskgroup +[alerts]: 09%20-%20Пулы,%20XCom,%20TaskGroup%20и%20алертинг%20в%20Airflow.md#система-оповещений-алертинг Главы доступны в [оглавлении учебника](README.md). Инструкция запуска и параметры учебной БД находятся в [README стенда](airflow-docker/README.md), файлы примеров - в [airflow-docker/dags/](airflow-docker/dags/). -- 2.54.0