Day 1 · Apache Airflow

Apache Airflow:
Basic concepts

About me

  • Big Data Engineer with extensive experience in Python
  • Enthusiastic about math and machine learning
  • Senior Big Data Software Engineer at Softserve
Roman Dryndik
Roman Dryndik
Senior Big Data Software Engineer

Introduction to Apache Airflow

S4
Evolution of orchestration

From Cron scripts to DAG orchestration

Cron

  • Isolated scripts without knowledge of dependencies.
  • Difficult to scale safely and handle retries.

Airflow

  • Explicit dependency graph and order control.
  • Run history, observability, and backfill out of the box.
script_1.sh script_2.sh script_3.sh load transform
S5
Workflow Orchestration

What is workflow orchestration?

S6
ETL/ELT as a DAG

Why a real ETL pipeline is a graph

Extract API Extract Postgres Extract S3 Load Transform Notify
S7
Limits of applicability

Batch Orchestration vs Streaming

S8
Key advantages

Why teams choose Airflow

FeatureValue for the team
DAG as Python codeGit, PR, review, CI/CD.
ProvidersIntegrations with databases, clouds, and messaging systems.
UI and observabilityVisibility into dependencies, statuses, and logs.
ExtensibilityCustom hooks/operators for your platform.
BackfillReloading and recalculating historical windows.
S9
Trade-offs

What to prepare for during adoption

S10
Version comparison

Airflow 2.x vs 3.x

Aspect2.x3.x
Task executionDirect access to the databaseVia Task SDK and API
Worker isolationLimitedExplicit isolation
DAG versioningLimitedDAG Versioning
AssetsNo native modelHas data-aware scheduling
S11
Airflow 2.x vs 3.x

The main shift: worker isolation

2.x Worker Metadata DB 3.x Worker Task SDK API Server Metadata DB
S12
3.x architecture

Why part of the core was rewritten

S13
Migration risks

The main trap when moving to 3.x

from airflow.models.taskinstance import TaskInstance

# Anti-pattern for 3.x: directly reading internal models from task code
def bad_task(**ctx):
    ti = TaskInstance.get_task_instance(
        dag_id="demo",
        task_id="step",
        run_id=ctx["run_id"]
    )
    return ti.state

Deploying Airflow

S15
Deployment options

Ways to deploy Airflow

The choice depends on the team stage, SLA, security, and budget.
Option For whom Pros Limitations
Local Python
(pip + LocalExecutor)
Learning,
quick experiments
Up and running in minutes,
minimal infrastructure
Does not reflect
real production
Docker Compose Team dev environment,
workshop, pre-prod
Close to production
topology, predictable
Manual administration
and upgrades
Kubernetes + Helm
(self-managed)
Mature
platform/data team
Scalability,
flexibility, control
High operational
complexity
Managed Airflow
(MWAA / Composer / Astronomer)
Focus on the data product,
not on ops
Less routine,
faster time-to-value
Cost,
provider limitations
Hybrid
(control plane managed + compute self-hosted)
Strict requirements
for networking and security
Balance of control
and speed
More complex architecture
and governance
The right choice is context-dependent and should reflect the team's current needs.
S16
Managed Airflow

Composer vs MWAA vs Astronomer

PlatformAdvantageDrawback
GCP ComposerDeep integration with GCPVendor lock-in
AWS MWAAManaged service in AWSLimited configuration flexibility
AstronomerStrong DX and toolingAdditional cost
S17
Installation

Installing Airflow with Docker Compose

curl -LfO 'https://airflow.apache.org/docs/apache-airflow/stable/docker-compose.yaml'

mkdir -p ./dags ./logs ./plugins ./config

# AIRFLOW_UID — for correct permissions on mounted folders
# FERNET_KEY — encrypts sensitive fields in the metadata DB
echo -e "AIRFLOW_UID=$(id -u)\nFERNET_KEY=$(openssl rand -base64 32 | tr '+/' '-_')" > .env

docker compose up airflow-init   # one-time initialization
docker compose up
S18
Practice

Step-by-step Docker startup

  1. Download the template docker-compose.yaml.
  2. Create folders dags, logs, plugins.
  3. Prepare a .env file with AIRFLOW_UID and keys.
  4. Run docker compose up airflow-init.
  5. Start the services: docker compose up -d.
S19
Initialization

Why AIRFLOW_UID and FERNET_KEY are needed

export AIRFLOW_UID=$(id -u)
export AIRFLOW_GID=0
export FERNET_KEY=$(python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())")

docker compose up airflow-init
S20
Compose topology

Services and connections in Airflow 3.x

Postgres API Server Scheduler Worker Triggerer Redis DAG Processor Web UI
S21
Component roles

Airflow 3.x components

ComponentPurpose
Metadata DBStores the state of DAGs, DagRuns, TaskInstances, connections, variables, and execution history.
API ServerA single access point to Airflow state and operations for the UI, SDK, and internal services.
SchedulerSchedules runs, evaluates dependencies, and decides which tasks to start next.
WorkerRuns task instances in the execution environment and sends results/statuses back.
TriggererHandles deferrable tasks and asynchronous waits without blocking worker slots.
RedisBroker/queue for delivering tasks and control messages (for example, in a Celery topology).
DAG ProcessorParses DAG files, validates DAG code, and serializes definitions for the scheduler/UI.
Web UIVisualization of DAGs and runs, viewing logs, and manual operations (trigger, clear, pause/unpause).
S22
Troubleshooting

Common startup issues

SymptomCauseSolution
UI is blankinit was skippedRun airflow-init again
DAG is not visibleImport error / path issueCheck the scheduler and dag-processor logs
No runsDAG pausedUnpause in the UI or CLI

UI

S24
Web UI

Airflow interface

Airflow UI screenshot
S25
Web UI

Anatomy of the Airflow 3.x interface

S26
Day-to-day work

Grid, Graph, and Gantt

ScreenWhen to open itWhat to look for
GridProduction incidentProblematic run, task status, and failure pattern by date
GraphThe chain breaksBroken upstream, incorrect trigger rule, excess dependencies
GanttSLA degradationLong tasks, executor queue, and time bottleneck

Architecture and key concepts

S28
Terminology basics

DAG, DagRun, Operator, Task, TaskInstance

Airflow entityWhat it isAnalogy
DAGGraph of work and dependenciesRecipe/blueprint of the process
DagRunOne DAG run over timeOne run of the recipe (for example, for a day)
OperatorStep typeClass in OOP
TaskA concrete step in the DAGObject (instance of a class)
TaskInstanceTask in a specific runState of the object over time
S29
Example

Operator as a template: BashOperator

from airflow.operators.bash import BashOperator

say_hello = BashOperator(
    task_id="say_hello",
    bash_command="echo Hello",
)
say_hello here is a Task created from the Operator template.
S30
Operators

Operator categories

S31
Cheat sheet

Top 5 operators

OperatorPackagePurpose
PythonOperatorcorePython functions
BashOperatorcoreShell commands
EmptyOperatorcoreHelper graph nodes
TriggerDagRunOperatorcoreLaunch another DAG
PostgresOperatorpostgresSQL in Postgres
S32
Example DAG

DAG example


from airflow import DAG
from airflow.operators.bash import BashOperator
from datetime import datetime

with DAG(
    dag_id="bash_dependencies_demo",
    start_date=datetime(2026, 1, 1),
    schedule="@daily",
    catchup=False,
) as dag:
    start = BashOperator(task_id="start", bash_command="date")
    task_a = BashOperator(task_id="task_a", bash_command="echo 'Processing A'; sleep 2")
    task_b = BashOperator(task_id="task_b", bash_command="echo 'Processing B'; sleep 3")
    finish = BashOperator(task_id="finish", bash_command="echo 'Done'")

    start >> [task_a, task_b] >> finish
S33
Practice DAG

Parallel and sequential execution

Parallel

start >> [task_a, task_b] >> finish
Both tasks run in parallel; the branches join at finish.

Sequential

start >> task_a >> task_b >> finish
task_b starts only after task_a succeeds.
S34
Visualization

DAG example in the UI

DAG example screenshot
S35
Execution model

A DAG file is a blueprint, not a process

DAG file dag_id: my_first_dag DagRun 2026-01-01 A: success B: success DagRun 2026-01-02 A: success B: failed DagRun 2026-01-03 A: running B: queued
A single DAG file produces many DagRuns, and each DagRun contains its own TaskInstances.
S36
Code vs process

How Airflow sees a DAG file

DAG file Scheduler parses file DagRun TaskInstances
The scheduler parses the DAG definition, creates a DagRun, and tracks each task in that run as a separate TaskInstance.
S37
TaskInstance states

Task lifecycle

StatusMeaning
queuedWaiting for an execution slot
runningCurrently running on a worker
successCompleted successfully
failedCompleted with an error
skippedSkipped by DAG logic
upstream_failedWas not started due to upstream failure
S38
Connection & Hook

Hooks

from airflow.providers.postgres.hooks.postgres import PostgresHook

def read_users():
    hook = PostgresHook(postgres_conn_id="analytics_pg")
    rows = hook.get_records("SELECT id, email FROM users LIMIT 100")
    return rows
S39
Sensors

Sensors

from airflow.providers.standard.sensors.filesystem import FileSensor

wait_for_file = FileSensor(
    task_id="wait_for_drop",
    filepath="/opt/airflow/data/input.csv",
    poke_interval=30,
    timeout=1800,
    deferrable=True,
)
S40
Variables

Variables

from airflow.models import Variable

@task
def send_report():
    channel = Variable.get("alerts_channel", default_var="#data-alerts")
    return channel
S41
XCom

XCom

  • XCom (cross-communication) — a mechanism for exchanging data between Airflow tasks.
  • Stores small values in the metadata DB.
  • Each value is tied to DAG + run + task + key.
  • The data belongs to a specific run, rather than being global.
  • Returning a value from one @task and passing it to another happens via XCom.

TaskFlow

@task
def extract():
    return {"cnt": 42}

Classic API

ti.xcom_push(key="cnt", value=42)
ti.xcom_pull(task_ids="extract", key="cnt")
S42
TaskFlow data passing

XCom in TaskFlow

@task
def extract():
    return {"raw_value": 42}

@task
def transform(data: dict):
    return data["raw_value"] * 2

transform(extract())
S43
Component roles

How Airflow moves tasks through the system

ComponentRoleWhat it does
DAG ProcessorParsingReads DAG files and prepares the graph for execution.
SchedulerSchedulingDecides when to run task instances and in what order.
ExecutorDispatcherRoutes tasks to the appropriate execution environment.
WorkerExecutorActually executes the task code.
Metadata DBStateStores statuses, logs, metadata, and run history.
API ServerAPIHandles web endpoints, the UI, and orchestration control.
TriggererWaitingSupports deferrable tasks and asynchronous waits.
S44
Executor vs Worker

Executor vs Worker

RoleExecutorWorker
PurposeTask routingActual execution
AnalogyTaxi dispatcherDriver
Where it livesOrchestration serviceExecution environment
S45
Executors

Most common executors

ExecutorPrincipleBackendWhen to use it
SequentialOne task at a timeNoneLocal experiments
LocalParallel execution on a single serverNoneSmall installations
CeleryTask queueRedis/RabbitMQClassic production
KubernetesPod per taskK8s APICloud elasticity
S46
Task SDK

Task Execution API in Airflow 3.x

Worker Task SDK API Server Metadata DB

Practical lab

Day 1 complete

1 / 48