fix(dags): сделать учебные сценарии воспроизводимыми
Управляемые ветки и ошибки, зависимости общих обработчиков, сохранение SQL-результата и фактическое чтение результатов Extract. Добавлен пример пулов и XCom; DQ-сводка сохраняет отказ при all_done. Проверено на Airflow 2.9.2: импорт и запуск девяти исходных DAG, выбор веток, повтор и окончательные отказы обеих рабочих задач, отрицательный DQ-сценарий и восстановление, фильтрация Extract и ожидание слотов.
This commit is contained in:
1 parent
623d5861c1
commit
cf1b770a65
6 files changed
+225
-211
No files matched your search
@@ -1,103 +1,70 @@
|
|||||||
|
"""Выбор ветки через Variable branch_format: csv или json.
|
||||||
|
|
||||||
|
Обработка файлов имитируется. В основной практике изучаются граф и статусы.
|
||||||
"""
|
"""
|
||||||
DAG для демонстрации условного выполнения задач в Airflow
|
import logging
|
||||||
Уровень: Продвинутый
|
from datetime import datetime
|
||||||
"""
|
|
||||||
from datetime import datetime, timedelta
|
|
||||||
from airflow import DAG
|
from airflow import DAG
|
||||||
from airflow.operators.python import PythonOperator, BranchPythonOperator
|
from airflow.models import Variable
|
||||||
from airflow.operators.dummy import DummyOperator
|
from airflow.operators.empty import EmptyOperator
|
||||||
import random
|
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(
|
def choose_format():
|
||||||
'branching_dag',
|
"""Возвращает task_id выбранной ветки."""
|
||||||
default_args=default_args,
|
file_format = Variable.get("branch_format", default_var="csv")
|
||||||
description='DAG для изучения условного выполнения задач в Airflow',
|
branches = {"csv": "process_csv_branch", "json": "process_json_branch"}
|
||||||
schedule_interval=None,
|
if file_format not in branches:
|
||||||
catchup=False,
|
raise ValueError(f"branch_format должен быть csv или json, получено: {file_format}")
|
||||||
tags=['educational', 'branching', 'advanced']
|
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():
|
def process_csv_data():
|
||||||
"""Обработка CSV данных"""
|
"""Имитирует обработку CSV."""
|
||||||
print("Обработка CSV файла...")
|
logging.info("Учебная CSV-ветка выполнена; файлы не читались")
|
||||||
# Здесь будет логика обработки CSV файла
|
return "csv"
|
||||||
return "CSV данные обработаны"
|
|
||||||
|
|
||||||
def process_json_data():
|
def process_json_data():
|
||||||
"""Обработка JSON данных"""
|
"""Имитирует обработку JSON."""
|
||||||
print("Обработка JSON файла...")
|
logging.info("Учебная JSON-ветка выполнена; файлы не читались")
|
||||||
# Здесь будет логика обработки JSON файла
|
return "json"
|
||||||
return "JSON данные обработаны"
|
|
||||||
|
|
||||||
def merge_results():
|
|
||||||
"""Объединение результатов из разных веток"""
|
|
||||||
print("Объединение результатов из разных веток...")
|
|
||||||
return "Результаты объединены"
|
|
||||||
|
|
||||||
# Определение задач
|
def merge_results(ti):
|
||||||
start_task = DummyOperator(
|
"""Читает результат выполненной ветки текущего запуска."""
|
||||||
task_id='start_task',
|
results = ti.xcom_pull(task_ids=["process_csv_branch", "process_json_branch"])
|
||||||
dag=dag
|
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(
|
with DAG(
|
||||||
task_id='process_csv_branch',
|
"branching_dag",
|
||||||
python_callable=process_csv_data,
|
start_date=datetime(2023, 1, 1),
|
||||||
dag=dag
|
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(
|
start_task >> choose_task >> [process_csv_task, process_json_task]
|
||||||
task_id='process_json_branch',
|
[process_csv_task, process_json_task] >> merge_task >> end_task
|
||||||
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
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
DAG для проверки качества данных (Data Quality) после загрузки в PostgreSQL
|
DAG для проверки качества данных (Data Quality) после загрузки в PostgreSQL
|
||||||
Уровень: Средний
|
Уровень: Средний
|
||||||
|
|
||||||
Этот DAG запускается после csv_to_postgres и проверяет таблицу public.orders:
|
Запустите этот DAG вручную после csv_to_postgres. Он проверяет public.orders:
|
||||||
1. Существование таблицы
|
1. Существование таблицы
|
||||||
2. Соответствие схемы (колонки и типы данных)
|
2. Соответствие схемы (колонки и типы данных)
|
||||||
3. Наличие данных (таблица не пуста)
|
3. Наличие данных (таблица не пуста)
|
||||||
@@ -14,12 +14,11 @@ DAG для проверки качества данных (Data Quality) пос
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime
|
||||||
|
|
||||||
from airflow.operators.python import PythonOperator
|
|
||||||
from airflow.providers.postgres.hooks.postgres import PostgresHook
|
|
||||||
|
|
||||||
from airflow import DAG
|
from airflow import DAG
|
||||||
|
from airflow.operators.python import PythonOperator
|
||||||
|
from airflow.providers.postgres.hooks.postgres import PostgresHook
|
||||||
|
|
||||||
POSTGRES_CONN_ID = "postgres_training"
|
POSTGRES_CONN_ID = "postgres_training"
|
||||||
|
|
||||||
@@ -106,13 +105,20 @@ def _check_no_duplicates():
|
|||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
def _log_dq_summary():
|
def _log_dq_summary(dag_run, ti):
|
||||||
"""Логирует итоговую сводку по качеству данных."""
|
"""Проверяет итог, в том числе при учебном переходе на all_done."""
|
||||||
logging.info("Все проверки качества данных пройдены успешно!")
|
states = {
|
||||||
logging.info("Качество данных в таблице orders соответствует требованиям.")
|
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(
|
with DAG(
|
||||||
dag_id="csv_to_postgres_dq",
|
dag_id="csv_to_postgres_dq",
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ DAG для демонстрации ETL процессов в Airflow
|
|||||||
1. Создание тестовых данных (клиенты + заказы) в CSV
|
1. Создание тестовых данных (клиенты + заказы) в CSV
|
||||||
2. Параллельное извлечение (Extract) клиентов и заказов
|
2. Параллельное извлечение (Extract) клиентов и заказов
|
||||||
3. Трансформация (Transform) — объединение таблиц, вычисляемые поля
|
3. Трансформация (Transform) — объединение таблиц, вычисляемые поля
|
||||||
4. Загрузка (Load) — подготовка SQL для вставки в БД
|
4. Имитация загрузки: число подготовленных строк и пример DDL в логах
|
||||||
5. Генерация текстового отчёта со статистикой
|
5. Генерация текстового отчёта со статистикой
|
||||||
|
|
||||||
Результат: файлы в /opt/airflow/data/output/ (extracted_*, transformed_data.csv, report.txt).
|
Результат: файлы в /opt/airflow/data/output/ (extracted_*, transformed_data.csv, report.txt).
|
||||||
@@ -14,7 +14,6 @@ DAG для демонстрации ETL процессов в Airflow
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from airflow import DAG
|
from airflow import DAG
|
||||||
from airflow.operators.python import PythonOperator
|
from airflow.operators.python import PythonOperator
|
||||||
from airflow.providers.postgres.operators.postgres import PostgresOperator
|
|
||||||
|
|
||||||
# Определение DAG
|
# Определение DAG
|
||||||
default_args = {
|
default_args = {
|
||||||
@@ -85,8 +84,8 @@ def extract_orders():
|
|||||||
def transform_data():
|
def transform_data():
|
||||||
"""Преобразование данных - объединение клиентов и заказов"""
|
"""Преобразование данных - объединение клиентов и заказов"""
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
customers_df = pd.read_csv('/opt/airflow/data/input/customers.csv')
|
customers_df = pd.read_csv('/opt/airflow/data/output/extracted_customers.csv')
|
||||||
orders_df = pd.read_csv('/opt/airflow/data/input/orders.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')
|
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)} записей"
|
return f"Преобразованы {len(merged_df)} записей"
|
||||||
|
|
||||||
def load_to_database():
|
def simulate_load():
|
||||||
"""Загрузка данных в базу данных (симуляция)"""
|
"""Загрузка данных в базу данных (симуляция)"""
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
df = pd.read_csv('/opt/airflow/data/output/transformed_data.csv')
|
df = pd.read_csv('/opt/airflow/data/output/transformed_data.csv')
|
||||||
|
|
||||||
# В реальном сценарии здесь был бы код для загрузки в базу данных
|
# В реальном сценарии здесь был бы код для загрузки в базу данных
|
||||||
# Для учебных целей просто логируем
|
# Для учебных целей просто логируем
|
||||||
print(f"Загружено в базу данных: {len(df)} записей")
|
print(f"Имитация загрузки: подготовлено {len(df)} записей. Записи в БД нет.")
|
||||||
|
|
||||||
# Создаем SQL для создания таблицы (в реальном сценарии)
|
# Создаем SQL для создания таблицы (в реальном сценарии)
|
||||||
create_table_sql = """
|
create_table_sql = """
|
||||||
@@ -185,8 +184,8 @@ transform_task = PythonOperator(
|
|||||||
)
|
)
|
||||||
|
|
||||||
load_task = PythonOperator(
|
load_task = PythonOperator(
|
||||||
task_id='load_to_database',
|
task_id='simulate_load',
|
||||||
python_callable=load_to_database,
|
python_callable=simulate_load,
|
||||||
dag=dag
|
dag=dag
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -197,4 +196,4 @@ report_task = PythonOperator(
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Установка зависимостей
|
# Установка зависимостей
|
||||||
create_data_task >> [extract_customers_task, extract_orders_task] >> transform_task >> load_task >> report_task
|
create_data_task >> [extract_customers_task, extract_orders_task] >> transform_task >> load_task >> report_task
|
||||||
@@ -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 datetime import datetime, timedelta
|
||||||
|
|
||||||
from airflow import DAG
|
from airflow import DAG
|
||||||
|
from airflow.models import Variable
|
||||||
|
from airflow.operators.empty import EmptyOperator
|
||||||
from airflow.operators.python import PythonOperator
|
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(
|
def get_error_mode():
|
||||||
'error_handling_dag',
|
"""Читает режим во время выполнения задачи."""
|
||||||
default_args=default_args,
|
mode = Variable.get("error_mode", default_var="retry_once")
|
||||||
description='DAG для изучения обработки ошибок в Airflow',
|
if mode not in {"success", "retry_once", "fail_first", "fail_second", "fail_both"}:
|
||||||
schedule_interval=None,
|
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,
|
catchup=False,
|
||||||
tags=['educational', 'error_handling', 'advanced']
|
default_args={
|
||||||
)
|
"owner": "student",
|
||||||
|
"retries": 1,
|
||||||
def unreliable_task():
|
"retry_delay": timedelta(seconds=10),
|
||||||
"""Задача, которая может завершиться с ошибкой"""
|
},
|
||||||
# В реальном сценарии это может быть задача, зависящая от внешних факторов
|
tags=["educational", "errors"],
|
||||||
# Для учебных целей случайным образом генерируем ошибку
|
) as dag:
|
||||||
if random.random() < 0.3: # 30% вероятность ошибки
|
start_task = EmptyOperator(task_id="start_task")
|
||||||
print("Ошибка: задача не выполнена успешно!")
|
unreliable_task = PythonOperator(
|
||||||
raise Exception("Случайная ошибка в задаче")
|
task_id="unreliable_task", python_callable=run_first_task,
|
||||||
|
)
|
||||||
print("Задача выполнена успешно!")
|
retry_task = PythonOperator(
|
||||||
return "Задача выполнена"
|
task_id="retry_task", python_callable=run_retry_task,
|
||||||
|
)
|
||||||
def success_handler():
|
success_handler_task = PythonOperator(
|
||||||
"""Обработчик успешного выполнения"""
|
task_id="success_handler",
|
||||||
print("Поздравляем! Все задачи выполнены успешно!")
|
python_callable=handle_success,
|
||||||
return "Успешно завершено"
|
trigger_rule="all_success",
|
||||||
|
retries=0,
|
||||||
def failure_handler():
|
)
|
||||||
"""Обработчик ошибок"""
|
failure_handler_task = PythonOperator(
|
||||||
print("Одна или несколько задач завершились с ошибкой!")
|
task_id="failure_handler",
|
||||||
print("Проверьте логи для получения дополнительной информации")
|
python_callable=handle_failure,
|
||||||
return "Ошибка обработана"
|
trigger_rule="one_failed",
|
||||||
|
retries=0,
|
||||||
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)
|
|
||||||
|
|
||||||
|
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.
|
||||||
@@ -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
|
||||||
@@ -4,7 +4,6 @@ DAG для демонстрации работы с SQL в Airflow
|
|||||||
"""
|
"""
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from airflow import DAG
|
from airflow import DAG
|
||||||
from airflow.operators.python import PythonOperator
|
|
||||||
from airflow.providers.postgres.operators.postgres import PostgresOperator
|
from airflow.providers.postgres.operators.postgres import PostgresOperator
|
||||||
|
|
||||||
# Определение DAG
|
# Определение DAG
|
||||||
@@ -76,11 +75,12 @@ query_data_task = PostgresOperator(
|
|||||||
)
|
)
|
||||||
|
|
||||||
truncate_table_task = PostgresOperator(
|
truncate_table_task = PostgresOperator(
|
||||||
task_id='drop_table',
|
task_id='truncate_table',
|
||||||
postgres_conn_id='postgres_training',
|
postgres_conn_id='postgres_training',
|
||||||
sql=truncate_table_sql,
|
sql=truncate_table_sql,
|
||||||
dag=dag
|
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
|
||||||
Reference in new issue
Block a user