Requirements (clarified):- Nightly ETL at 02:00 for previous day's data- Ensure upstream files available, single concurrent run, retries/SLA, lineage metadata, alertingHigh-level DAG design:- schedule_interval="0 2 * * *" (daily at 02:00)- catchup=False, max_active_runs=1, concurrency=1, default_args with retries and SLA at task-level- Sensors: upstream file/object sensors before extract- Tasks: sensor -> extract -> transform -> load -> notify- Operators: File/GCS/S3 sensors; SparkSubmitOperator or DataprocSubmitOperator for heavy transforms; PythonOperator or BashOperator for small tasks; BigQueryInsertJobOperator / PostgresHook for load- Lineage: emit OpenLineage events (openlineage-airflow) or call Marquez/OpenLineage hook; push metadata to XCom; write run metadata to metadata DB- Alerts: on_failure_callback to send Slack/email/PagerDuty; integrate with Sentry for exceptionsExample DAG (concise illustrative snippet):python
from airflow import DAG
from airflow.providers.google.cloud.sensors.gcs import GCSObjectExistenceSensor
from airflow.providers.apache.spark.operators.spark_submit import SparkSubmitOperator
from airflow.providers.google.cloud.operators.bigquery import BigQueryInsertJobOperator
from airflow.operators.python import PythonOperator
from airflow.utils.dates import days_ago
from datetime import timedelta
default_args = {
'owner': 'data_eng',
'retries': 3,
'retry_delay': timedelta(minutes=10),
'email': ['oncall@example.com'],
'email_on_failure': False, # using on_failure_callback instead
}
def failure_alert(context):
# send Slack/PagerDuty/Sentry event with context
pass
with DAG(
dag_id='nightly_etl',
default_args=default_args,
description='Nightly ETL: sensor -> extract -> transform -> load',
schedule_interval='0 2 * * *',
start_date=days_ago(1),
catchup=False,
max_active_runs=1,
tags=['etl'],
on_failure_callback=failure_alert,
) as dag:
wait_for_file = GCSObjectExistenceSensor(
task_id='wait_for_source',
bucket='raw-data-bucket',
object='incoming/{{ ds }}/data_*.parquet',
timeout=60*60,
poke_interval=60,
mode='reschedule'
)
extract = PythonOperator(
task_id='extract',
python_callable=lambda **ctx: None, # implement extraction or use Dataflow
provide_context=True
)
transform = SparkSubmitOperator(
task_id='transform',
application='/opt/airflow/dags/jobs/transform.py',
name='nightly_transform',
conf={'spark.executor.memory': '4g'},
application_args=['--date','{{ ds }}']
)
load = BigQueryInsertJobOperator(
task_id='load',
configuration={
"query": {
"query": "INSERT INTO dataset.table SELECT * FROM staging.table_{{ ds }}",
"useLegacySql": False
}
}
)
publish_lineage = PythonOperator(
task_id='publish_lineage',
python_callable=lambda **ctx: None # call OpenLineage / Marquez client using XCom metadata
)
wait_for_file >> extract >> transform >> load >> publish_lineage
Key operational details and reasoning:- Sensor choice: use cloud-specific object sensors (GCS/S3) instead of generic FileSensor; use mode='reschedule' to avoid worker slot blocking.- Prevent concurrent runs: max_active_runs=1 and concurrency=1 on DAG; set depends_on_past=False but use task-level idempotency (use job-run id).- Retries/SLA: default_args retries=3, retry_delay=10m; set task.sla=timedelta(hours=3) for SLA alerts in Airflow UI; on_failure_callback handles immediate alerting to Slack/PagerDuty.- Lineage/metadata: capture dataset names, row counts, run id and schema in XComs; publish to OpenLineage/Marquez after successful load; store run metadata to a metadata table for auditing.- Idempotency & data quality: each task writes to a staging path with run_id/ds; transform should write atomically (write to temp then rename). Add simple QA task (row count and null checks) before load; fail fast on quality breaches.- Monitoring: use Airflow SLA misses, task failures, DAG duration metrics; integrate with Prometheus/Grafana for pipeline latency and success rate.Edge considerations:- Large upstream files: consider pre-signed URL ingestion or object change notification sensor to trigger earlier.- Long-running Spark jobs: set task-level execution_timeout to avoid runaway jobs.- Security: use least-privileged service accounts and store credentials in Airflow Connections/Secrets backend.This design provides clear ordering (sensor → extract → transform → load), robust retries and alerting, single-run guarantees, and lineage capture using OpenLineage/XComs for traceability.