An Airflow upgrade can look green and still be wrong. The new user interface loads, every Directed Acyclic Graph (DAG) parses, and a manually triggered finance workflow finishes successfully. Yet the output lands under the wrong business date because the task kept treating data_interval_start as the date supplied by the operator. Nothing crashed; the control failure appeared only when downstream reconciliation found a duplicate partition and a missing one.
An Airflow 2 to 3 migration is not a package replacement. Verified September 3, 2026, the Apache Airflow 3.3.1 release notes date 3.3.1 to August 12, 2026 and list 2.11.2 as the final 2.x patch. The version-specific 2.11.2 notes use March 14 rather than the stable index’s March 11; the staging version is unchanged. The supported-versions policy records Airflow 2 as end of life on April 22, 2026. Use 2.11.2 only as a temporary migration baseline. Airflow requires 2.7 or later and recommends the latest 2.x first.
A defensible migration proves four surfaces independently: DAG code compatibility; provider, plugin, and executor packaging; metadata and deployment architecture; and schedule-to-business semantics. This Airflow 3 migration runbook treats each surface as a testable control. It stabilizes the 2.x estate, inventories dependencies, rehearses database changes, migrates public interfaces, compares old and new outcomes, and keeps a restore-capable rollback path until the final evidence is signed. Import success is an input, never the acceptance decision.
Understand Why Airflow 3 Is an Architecture Migration
The official Airflow 3 upgrade guidance makes the architectural boundary explicit: tasks and workers no longer receive unrestricted direct access to the metadata database. Runtime interactions such as state transitions, heartbeats, XCom operations, and resource retrieval move through the Task Execution API, with the API server as the controlled path. DAG authors are expected to use the stable Task Software Development Kit (Task SDK) and the documented public interface instead of importing internal models simply because those imports worked in Airflow 2.
This changes threat exposure and failure behavior. Removing database credentials and drivers from ordinary worker images reduces the blast radius of task code, but it also makes API-server availability, authentication, network policy, and latency part of task execution. The public interface for Airflow 3 therefore becomes a production contract. A custom operator that reads TaskInstance rows through an Object-Relational Mapper (ORM) is not merely deprecated; it violates the new isolation model and can fail even when the DAG file imports.
Architecture decision matrix
Surface | Airflow 2 assumption | Airflow 3 migration control |
Task runtime | Workers can reach metadata tables and internal models. | Use Task SDK, supported APIs, hooks, XComs, variables, connections, or an external store. |
Control plane | The webserver mainly serves the UI and API. | Treat the API server as a required runtime dependency with capacity, authentication, and health checks. |
DAG processing | A deployment may rely on scheduler-coupled parsing. | Run and monitor the standalone DAG processor explicitly. |
Compatibility | An import path that still resolves appears safe. | Validate public-interface status, behavior, packaging location, and removal timeline. |
The platform owner should record one architecture decision for each runtime dependency: who operates it, how it is authenticated, which network path it uses, what saturation looks like, and what happens when it is unavailable. Promotion is blocked if workers can bypass the intended API boundary, if API-server failure is absent from resilience testing, or if deployment manifests still assume that airflow webserver is the production entry point. Rollback at this stage means reverting design and code changes on the 2.x baseline; no database restore should yet be required.
Inventory DAG Ownership, Providers, Plugins, and Deployment Paths
An estate inventory is the migration control plane. Package manifests miss dependencies baked into different component images or supplied by a managed service. A low-frequency DAG can also be financially critical. Inventory each workflow, runtime, and business effect as one record.
Refonte Learning separately introduces Kafka, Spark, Airflow, and other data-engineering tools; this runbook asks a narrower question: which estate dependencies must remain compatible while the orchestrator changes?
Map DAG Criticality, Schedules, and Business Consumers
Minimum DAG inventory record
Inventory field | Required value | Why it blocks or shapes migration |
Identity and ownership | DAG ID, repository, technical owner, business owner, support queue. | Prevents unowned findings and establishes who can approve semantic changes. |
Business contract | Criticality, service deadline, timezone, expected partition or event, consumer. | Defines acceptance thresholds beyond task state. |
Scheduling | Timetable or cron, catchup, start date, manual-run path, backfill method. | Exposes logical-date and interval assumptions. |
Execution | Executor, queue, pool, concurrency, task resources, retry policy. | Reveals capacity and behavior differences. |
Dependencies | Provider versions, custom imports, plugins, connections, secrets, callbacks. | Determines package and API remediation. |
Side effects | Tables, files, messages, APIs, notifications, destructive operations. | Defines duplicate protection and rollback limits. |
Classify criticality by business impact, not frequency. Record the latest safe completion time, recovery objective, and whether replay is idempotent. Do not approve a DAG with no business owner or no independent output-reconciliation method.
Build the Provider, Plugin, and Executor Matrix
The Airflow provider packages index documents separately released packages, so core compatibility cannot be inferred from one apache-airflow version. Inventory every scheduler, API-server, DAG-processor, triggerer, worker, CLI, migration, and authoring image, including internal wheels and editable installs.
Dependency matrix
Component | Record | Acceptance evidence |
Provider package | Name, exact version, source, constraints, transitive conflicts. | Clean install and representative operator/hook test on both baselines. |
Plugin/custom wheel | Entrypoint, imported Airflow surfaces, UI or middleware extension. | Import, load, permission, and behavior test under 3.3.1. |
Executor | Executor type, queue routing, pod template, broker/backend, remote logs. | Controlled task matrix with retries, termination, and log retrieval. |
Deployment path | Helm, Compose, systemd, image entrypoint, or managed service. | Rendered manifest and startup contract reviewed by platform owner. |
Use the broader data-engineering tools and technologies landscape to locate adjacent systems. The matrix is complete when every DAG maps to a tested image and every package has an owner, exact version, compatibility result, and rollback artifact. Unknown or “latest” versions stop promotion.
Stabilize the Latest Airflow 2.x Staging Baseline
Do not combine an unresolved 2.x upgrade with Airflow 3 remediation. The floor is 2.7, but the project recommends the latest 2.x first. Use 2.11.2 as the upstream staging baseline unless a managed-service vendor imposes a lower ceiling. Because Airflow 2 is end of life, use the hold period to remove variables and collect evidence, not delay Airflow 3. Record any distribution divergence.
The general Airflow upgrade procedure recommends incremental upgrades and treats database migration as a distinct operation. Move through required intermediate releases, fix deprecations, then hold a stability period on 2.11.2. Freeze the Python version, constraints or lock files, operating-system base, provider set, and internal wheels. Rebuild all component images from the same bill of materials rather than patching live containers.
Baseline verification sequence
# Reference baseline commands; run inside the immutable Airflow image
python -m pip install --require-hashes -r requirements-airflow-2.11.2.lock
airflow version
airflow config lint
airflow dags reserialize2.x stabilization gate
Gate | Pass condition | Evidence retained |
DAG health | No duplicate IDs, import errors, serialization errors, or unexplained parse spikes. | Parser logs, serialized-DAG result, import-error export. |
Task health | Representative scheduled, manual, retry, sensor, and mapped tasks meet current baselines. | Run IDs, logs, output checksums, duration percentiles. |
Platform health | Scheduler, triggerer, workers, database, broker, and remote logs are stable. | Seven-day metric snapshot and incident-free observation window. |
Configuration | All Airflow 3 migration lint findings are resolved or assigned. | Version-controlled lint output and signed exceptions. |
This baseline supplies the control group for every later comparison. Set measurable tolerances now: for example, zero missed Tier 0 runs, zero duplicate external writes, no unexplained increase in scheduler delay, and task-duration variance bounded by a workload-specific threshold. Rollback is the known 2.11.2 image and configuration plus the pre-change provider lock. If 2.x cannot remain healthy under that frozen set, the Airflow 3 cutover clock has not started.
Clean, Back Up, and Rehearse the Metadata Migration
Treat the metadata upgrade as a production database change. Its size affects duration, locks, transaction logs, storage headroom, and restore time. The database owner, platform owner, and incident commander must agree on the sequence and the point where restore becomes mandatory.
Official guidance recommends cleanup and warns about half-migrated states. The Airflow command-line reference documents airflow db clean, including cutoff, dry-run, table, archive, and batch options. Obtain retention approval; never delete history only to shorten the window.
Metadata cleanup rehearsal
# Illustrative cutoff only; obtain retention approval before execution
airflow db clean \
--clean-before-timestamp "2025-09-03T00:00:00+00:00" \
--dry-run
# Execute only after reviewing the dry-run and backup/restore evidence
airflow db clean \
--clean-before-timestamp "2025-09-03T00:00:00+00:00" \
--batch-size 5000 \
--yesQuiesce writers. Pause Airflow components and automation unless a validated consistent hot-backup method exists.
Capture a restorable backup. Record engine version, backup ID, key dependency, recovery position, checksum, location, and expiry.
Restore in isolation. Validate critical row counts, connectivity, authentication, and representative historical records.
Measure recovery. Time restore, startup, and reconciliation against the rollback window.
Metadata safety gates
Stop condition | Required response | Promotion evidence |
Backup cannot be restored. | Cancel migration and repair backup/keys/procedure. | Successful timed restore from the exact backup method. |
Cleanup locks or load exceed bounds. | Reduce batch size, reschedule, or retain more data. | Database metrics and completed dry-run/controlled run. |
Disk or transaction-log headroom is insufficient. | Increase capacity or reduce migration scope safely. | Capacity calculation plus alert thresholds. |
Any component still writes during snapshot. | Stop writers and recapture backup. | Connection/activity evidence showing quiescence. |
Reserialize DAGs and Time the Database Changes
Before leaving 2.x, run airflow dags reserialize after all fixes are processed. Retain output, import errors, serialized-DAG counts, and parser duration. Duplicate IDs or unexplained missing DAGs block migration.
Rehearse airflow db migrate on a production-sized restored clone. The database migration reference lists revisions and warns that the schema is internal. Review generated SQL where supported, then time the real command while recording locks, CPU, I/O, space, log growth, and schema revisions.
Schema review and rehearsal
# Run with the Airflow 3.3.1 migration image against an isolated clone
airflow db migrate --show-sql-only \
--from-version "2.11.2" \
--to-version "3.3.1" > migration-plan.sql
airflow db migrateSet the window from the slowest credible rehearsal plus restore and validation. Do not deploy the old image over a failed new schema. Unless an exact supported downgrade exists, restore the pre-migration database and frozen 2.11.2 stack. Protect that restore point through business reconciliation.
Scan DAGs and Custom Code for Airflow 3 Incompatibilities
Compatibility scanning must cover DAG repositories, plugin packages, shared Python libraries, deployment scripts, Helm values, command wrappers, test fixtures, and notebooks copied into production images. Start with automated discovery, then assign every result. A search hit is not automatically a defect, and a clean linter is not proof that runtime semantics are preserved.
Repository discovery searches
rg -n "airflow\.(models|settings|utils\.session)|create_session|provide_session" dags plugins src
rg -n "SubDagOperator|sla=|execution_date|/api/v1|airflow webserver" dags plugins deploy tests
rg -n "data_interval_(start|end)|xcom_pull\(|TriggerDagRunOperator" dags plugins src
rg -n "AIRFLOW__[A-Z0-9_]+__|webserver:" deploy charts configCompatibility finding register
Finding class | Primary risk | Required disposition |
Internal models or sessions | Task fails under the API boundary or mutates unsupported schema. | Replace with Task SDK, public API, supported resource access, or external state. |
Removed orchestration feature | SubDAGs, Sequential Executor, CeleryKubernetesExecutor, LocalKubernetesExecutor, or SLA behavior is unavailable. | Redesign with TaskGroups or Assets, LocalExecutor or multiple-executor configuration, and Deadline Alerts as appropriate. |
Context/date assumption | Workflow succeeds against the wrong interval or requested date. | Add scheduled, manual, backfill, timezone, and trigger-path tests. |
REST API v1 client | Automation receives route/auth/schema failures. | Migrate and contract-test against API v2. |
Startup/config key | Pod or service starts incorrectly, or ignores intended settings. | Run config lint/update and validate rendered deployment manifests. |
Plugin/UI extension | Flask-AppBuilder view, menu, or blueprint is absent or unauthorized. | Move to supported Airflow 3 plugin interfaces or qualify the FAB compatibility provider. |
Assign each finding an owner, criticality, remediation pull request, test case, target image, and disposition date. For custom operators, inspect both constructor-time code and execute behavior; top-level imports can pass while runtime access fails only on workers. For callbacks and listeners, verify the context keys actually consumed, not just the callback signature. For XCom calls, identify pulls that omit task_ids, because Airflow 3 narrows that default behavior.
Promotion requires zero unowned findings and zero open breaking-change findings for the cutover cohort. A temporary exception must state why the path remains supported, how it is monitored, how long it is allowed, and which rollback trigger applies. “DAG imports on my laptop” is not evidence because it bypasses deployment packaging, secrets, executor, API, and business-side-effect conditions.
Migrate Imports, Operators, and the Standard Provider
Separate Airflow core, the standard provider, third-party providers, and custom packages. Airflow 3 moves authoring interfaces toward airflow.sdk, while common operators and sensors move to apache-airflow-providers-standard. Stage compatible import changes on 2.x to shrink the cutover.
Use the standard provider documentation and each provider’s release notes. Verified September 3, 2026, stable documentation listed apache-airflow-providers-standard 1.18.0 with minimum Airflow 2.11.0, allowing many import changes to be staged on 2.11.2. Pin the approved provider and hashes; a solvable environment can still have wrong extras, libraries, authentication, or behavior.
Representative import migration map
Airflow 2-era import | Airflow 3 authoring import | Migration note |
from airflow.models.dag import DAG | from airflow.sdk import DAG | Move DAG-authoring code to the stable SDK surface. |
from airflow.decorators import task | from airflow.sdk import task | Update shared decorator imports and typing together. |
from airflow.operators.python import PythonOperator | from airflow.providers.standard.operators.python import PythonOperator | Install and pin the standard provider before changing the import. |
from airflow.operators.bash import BashOperator | from airflow.providers.standard.operators.bash import BashOperator | Test environment, exit-code, templating, and log behavior. |
from airflow.datasets import Dataset | from airflow.sdk import Asset | A name change can imply code and terminology changes, not just an import rewrite. |
Build clean 2.11.2 and 3.3.1 images from the same source. Install internal release wheels, then test imports, provider discovery, serialization, hooks, templates, connections, logs, and executor tasks. Compare installed packages with the approved bill of materials.
Use Compatibility Rules and Ruff AIR Checks Deliberately
Use Ruff 0.13.1 or later. AIR301/AIR302 identify breaking changes; AIR311/AIR312 flag recommended updates. Scan DAGs, plugins, and shared code by commit, and preview fixes before applying them.
Compatibility lint workflow
python -m ruff check dags plugins src --select AIR301,AIR302,AIR311,AIR312
python -m ruff check dags plugins src --select AIR301 --show-fixes
# Apply only reviewed fixes on a branch; do not auto-merge unsafe changes
python -m ruff check dags plugins src --select AIR301 --fixTreat automated changes as behavior-affecting. New packages can change optional dependencies, and rewritten context can alter dates. Require a reproducible install plus semantic tests, not only zero Ruff findings. Preserve the previous lock, commit, and artifact tag for rollback.
Remove Direct Metadata-Database Access from Task Code
Direct metadata access is the compatibility issue most likely to be hidden inside shared helpers. Search for Airflow sessions, ORM models, SQL against metadata tables, and utilities that accept a session argument. Then identify the actual intent: retrieve a variable, inspect a connection, pull an XCom, learn the current task identity, query run state, or maintain application data. The replacement should match that intent rather than reproduce an internal table query through another credential.
The stable Task SDK API exposes task context and supported resource access. Use context, variables, connections, XComs, hooks, or the public REST/Python client where the project provides the operation. Move business state to a business-owned database or object store. Airflow metadata is an orchestration record, not a general-purpose application database.
Metadata-access replacement decisions
Old intent | Preferred replacement | Operational test |
Read current task/run identity from TaskInstance ORM. | Use get_current_context() and the task-instance fields exposed there. | Identity, try number, map index, and run ID match logs and API. |
Fetch variable or connection row directly. | Use Task SDK context/resource access or the supported public interface. | Secret masking, authorization, rotation, and unavailable-resource behavior. |
Inspect another task’s value. | Use explicit xcom_pull(task_ids=..., key=...) or an external state store. | Missing, retry, rerun, and cross-run behavior. |
Store business checkpoint in metadata table. | Use an external transactional store with an idempotency key. | Duplicate attempt, partial failure, replay, and reconciliation. |
Query broad administrative history. | Use API v2 or an approved observability/export path outside task execution. | Pagination, authentication, rate limits, and API-server outage. |
Supported task-context access
from airflow.sdk import get_current_context, task
@task
def publish_checkpoint() -> dict[str, str]:
context = get_current_context()
ti = context["ti"]
return {
"dag_id": ti.dag_id,
"task_id": ti.task_id,
"run_id": ti.run_id,
"try_number": str(ti.try_number),
}Account for the new failure path. API access adds token acquisition, authorization, network, timeout, and API-capacity dependencies. Tests must distinguish retryable transport errors from authorization or invalid-request failures; uncontrolled retries can amplify an API-server incident. A database hook aimed at Airflow metadata may exist as a documented workaround, but the project warns that it breaks task isolation and is not a durable public contract. Treat it as a time-bounded exception with read-only temporary credentials, not as the target design.
Adapt to the API Server, Task SDK, and Process Layout
Airflow 3 separates responsibilities often bundled in 2.x: API service, scheduling, DAG parsing, triggering, task execution, and administration. Give each component an image, command, identity, network policy, resource budget, logging path, and health model.
Airflow 3 process contract
Process | Required connectivity | Migration evidence |
API server | Metadata database, auth dependencies, clients, workers/task runtime. | Authenticated UI/API tests, readiness, error/latency load test, outage behavior. |
Scheduler | Database, executor/broker, DAG definitions and control-plane dependencies. | Heartbeat, scheduling delay, queued-task flow, failover. |
DAG processor | DAG bundles/files, database where required, secrets needed at parse time. | Independent startup, parse throughput, import-error and serialization metrics. |
Triggerer | Database/API paths and external endpoints used by triggers. | Deferred task resume, restart recovery, trigger capacity. |
Workers | API server, broker/executor dependencies, business systems, remote logs. | No unintended metadata DB route; task, retry, termination, and log tests. |
CLI/migration job | Administrative auth and metadata DB for approved migration operations. | Command-specific least privilege, audited use, deterministic image. |
The Airflow production deployment guidance covers external databases, remote logging, controlled configuration, and executor choice. Map every component to the API server, database, broker, logs, secrets, and business endpoints. Deny worker-to-metadata-database traffic after compatibility proof.
Replace Webserver and Startup Assumptions Safely
The webserver command becomes airflow api-server, and the DAG processor starts independently. Search units, entrypoints, Deployments, probes, services, ingress, dashboards, and runbooks for webserver. A renamed process without network and probe changes can look running while unusable.
Startup contract
# Airflow 3.3.1 component commands
airflow api-server
airflow scheduler
airflow dag-processor
airflow triggerer
# Verify the deployed version from the same image used by each process
airflow versionThe Airflow security model documents trust boundaries and /api/v2/monitor/health. Also test authenticated routes, scheduler and worker heartbeats, DAG processing, database access, and a synthetic DAG. Tune startup probes for initialization and readiness for traffic removal.
Scale the resource that saturates: API concurrency, scheduler delay, parse queue, triggers, or workers. Cap scaling at database and broker capacity. Block promotion if ownership is unclear, workers retain unnecessary DB credentials, or service-name rollback is untested.
Qualify Authentication, Plugins, Executors, and Helm Packaging
Authentication is not a post-upgrade login check. Airflow 3 changes defaults, routes, compatibility providers, and plugin interfaces. Auth-manager routes use /auth; update redirect URIs and contract-test browser and automated clients. Inventory the auth manager, Single Sign-On (SSO) protocol, role mapping, token flow, service accounts, API clients, and emergency access. Test authorization boundaries, not only successful login.
Core Airflow 3 defaults to Simple auth, while the official Helm chart’s upgrade path defaults to FabAuthManager for continuity. Set the auth manager explicitly. Airflow’s Simple auth manager documentation says it is for development and testing; production requires other access controls. Teams retaining Flask-AppBuilder must pin the FAB provider and verify routes, permissions, callbacks, and failure isolation. Move unsupported Flask blueprints to external views, FastAPI applications, or middleware.
Platform qualification matrix
Qualification area | Test set | Stop condition |
Auth/UI/API | SSO login/logout, redirect, role mapping, token issue/rotation, API v2, break-glass. | Privilege expansion, failed automation, or unowned emergency access. |
Secrets and connections | Backend lookup, masking, rotation, unavailable backend, worker access. | Secret exposure, fallback to stale value, or missing least privilege. |
Executor | Queue routing, pod launch, retries, termination, deferral, remote logs, multi-executor rules. | Lost task, duplicate execution, stuck state, or missing logs. |
Plugins | Load, route, authorization, callbacks/listeners, serialization, API-server restart. | Control-plane crash, missing capability, or unsupported internal import. |
Managed service | Vendor-supported core/provider ceiling, image controls, auth, upgrade/rollback path. | Upstream instructions cannot be executed within vendor contract. |
For Kubernetes, follow the official Airflow 3 Helm upgrade guide and compare rendered manifests. Verified September 3, 2026, stable chart documentation was 1.22.0; the guide requires at least 1.19.0, sets both defaultAirflowTag and airflowVersion, and moves webserver values under apiServer. Chart and core versions are independent, and chart 1.22.0 still defaulted to Airflow 3.2.2, so pin 3.3.1. Also move [webserver] secret_key to [api] secret_key and provision the JWT secret before components start.
Minimal version-explicit Helm values fragment
airflowVersion: "3.3.1"
defaultAirflowTag: "3.3.1"
apiServer:
replicas: 2
dagProcessor:
replicas: 2The chart 1.22.0 parameters reference exposes separate apiServer and dagProcessor controls. The guide also requires Kubernetes 1.30 or later for chart 1.19.0. Validate rendered manifests, permissions, services, storage, logs, migration jobs, network policies, and secrets against the process map. Preserve the previous chart, values, image digests, and manifests.
Test Scheduling, Logical Dates, Backfills, and Manual Runs
Scheduling is where green upgrades become business incidents. For each critical DAG, record the expected business key and source window for scheduled, manual, backfill, catchup, asset, downstream-trigger, retry, clear, rerun, and timezone cases. Compare outputs, not only UI timestamps.
Airflow 3 defaults catchup_by_default and create_cron_data_intervals to false; bare cron schedules therefore use trigger-oriented behavior unless configured otherwise. The timetable documentation explains logical dates and intervals. Decide and test the intended contract before creating Airflow 3 runs.
Scheduling semantics test matrix
Run path | Business assertion | Required cases |
Scheduled | The same intended partition/window is produced at the same timezone boundary. | Normal day, month/quarter end, daylight-saving change, late scheduler. |
Manual | A supplied logical date selects the requested business date; no-date trigger follows an approved rule. | UI, API v2, CLI/automation, downstream trigger. |
Backfill/catchup | Exactly the intended historical intervals run once or idempotently. | Range edges, existing output, partial failure, retry, rerun. |
Asset/event | The intended event and partition mapping triggers the correct consumer run. | Duplicate event, delayed event, missing event, replay. |
Retry/clear | Business key is stable across attempts unless explicitly versioned. | Task retry, DAG clear, partial clear, executor restart. |
Deadline | Late completion produces the approved alert and escalation. | On-time, late, failed, recovered, manually triggered. |
The Airflow DAG run guidance says to use logical_date when a manual task needs the supplied date. The following 3.3.1 DAG imports and runs, yet can select the wrong finance date.
Worked example: code-compatible but business-wrong
import pendulum
from airflow.sdk import DAG, get_current_context, task
with DAG(
dag_id="finance_daily_close_wrong",
schedule="0 2 * * *",
start_date=pendulum.datetime(2026, 1, 1, tz="UTC"),
catchup=False,
):
@task
def close_ledger() -> str:
context = get_current_context()
# WRONG for a manual run when the operator-supplied date is required.
business_date = context["data_interval_start"].date().isoformat()
return f"closing ledger for {business_date}"
close_ledger()An operator supplies logical date 2026-08-31T00:00:00Z for the August 31 close. The resolved interval is not guaranteed to start there, so the task can succeed against another partition. Imports, task state, and row count may all pass.
Corrected manual-run business-date logic
import pendulum
from airflow.sdk import DAG, get_current_context, task
with DAG(
dag_id="finance_daily_close",
schedule="0 2 * * *",
start_date=pendulum.datetime(2026, 1, 1, tz="UTC"),
catchup=False,
):
@task
def close_ledger() -> str:
context = get_current_context()
# CORRECT when the contract is the operator-supplied manual date.
business_date = context["logical_date"].date().isoformat()
return f"closing ledger for {business_date}"
close_ledger()Do not replace every interval field mechanically: scheduled interval processing often needs both boundaries. Test fixed context, every production trigger path, output partition, and downstream ledger. Any unexplained date, timezone, run-count, or partition difference blocks promotion.
Run Shadow Workloads and Compare Business Outcomes
Run Airflow 3 against controlled inputs without production side effects. Use immutable fixtures, cloned sources, isolated outputs, stubbed notifications, and synthetic APIs. Permit production reads only with read-only credentials; capture unsafe writes as comparison events.
Pair every 2.11.2 and 3.3.1 run by DAG, case, logical date, interval, fixture, commit, package lock, and configuration. Compare graph, mapped tasks, states, retries, callbacks, XComs, checksums, business totals, and downstream events per run.
Shadow-run comparison controls
Control | Implementation | Acceptance threshold |
Duplicate-side-effect guard | Idempotency key includes DAG/business key/operation; shadow credentials cannot reach production writes. | Zero unapproved production side effects. |
Input equivalence | Same frozen files, rows, messages, parameters, logical date, and timezone. | Fixture hashes and run manifest match. |
Data reconciliation | Compare counts, sums, keys, nulls, partitions, schema, and sampled records. | Tier-specific tolerance; Tier 0 financial controls normally require exact agreement. |
Event comparison | Capture API calls, messages, callbacks, and notifications in a test sink. | Same semantic payload and intended recipient/routing. |
Performance comparison | Measure queue, start, execution, retry, and completion timing. | Within predeclared bounds without database/API saturation. |
The discipline of building reliable data pipelines combines correctness, recoverability, observability, and controlled change. Here, evidence must also be Airflow- and run-specific; a green dashboard cannot waive failed reconciliation.
Prove Interval, Retry, XCom, and Callback Equivalence
Test the drift points: logical date and intervals per trigger path, retry after an external action, task clears, worker and triggerer restarts, mapped-task cardinalities, callback order, permissions, payload, and duplicate suppression.
In Airflow 3, xcom_pull() without task_ids checks the current task. Use an explicit producer and key, then test missing values and reruns.
Business-equivalence evidence
Evidence item | Compare | Block promotion when |
Run semantics | Logical date, interval start/end, run type, timezone, business key. | Any unexplained difference changes selected data or downstream identity. |
Retries | Attempt count, delay, idempotency key, final side effect, alerting. | A retry duplicates, skips, or hides an external action. |
XCom | Producer task, key, value type/size, retrieval scope, cleanup behavior. | Consumer reads a different or stale value. |
Callbacks/listeners | Invocation condition, context, sequence, error isolation, event payload. | Monitoring or business notification is lost, duplicated, or blocks task state. |
Require signed evidence per critical pattern. Shared operators may reuse a pattern test, but distinct trigger paths need separate cases. Exceptions need residual risk and reconciliation; the shadow stage ends only when every material difference is explained.
Cut Over with Observability and Promotion Gates
Cutover observability must show platform health and business completion in the same timeline. Establish dashboards and alerts before the change, then annotate the exact times of traffic shifts, scheduler pauses, database migration, component starts, DAG enablement, and first runs. Retain raw logs and metrics long enough to investigate a defect discovered after a monthly or quarter-end workflow.
Cutover control board
Signal | Cutover question | Example promotion gate |
Scheduler and DAG processor | Are heartbeats current, parses completing, and expected runs created once? | No critical import error; schedule delay within baseline; expected run count exact. |
API server | Are auth, task-runtime calls, and UI/API routes healthy under load? | Error and latency within declared bounds; no repeated token or permission failure. |
Workers/executor | Are queued tasks starting, heartbeating, logging, retrying, and terminating correctly? | No growing unexplained queue; no zombie/duplicate pattern; logs retrievable. |
Metadata database | Are locks, connections, CPU, I/O, storage, and transaction logs safe? | Below database-owner thresholds with stable recovery capacity. |
Workflow performance | Did task duration, queue delay, and completion time remain acceptable? | Tier-specific deadline and variance gates met. |
Business outcome | Did expected partitions, balances, messages, files, and notifications arrive? | Independent reconciliation passes for every enabled critical DAG. |
Use DataOps workflow automation principles to automate evidence collection, but keep promotion authority explicit. Automation can compare run manifests, query health endpoints, fetch metrics, and reconcile outputs; it should not silently promote a cohort whose business owner has not accepted the result. The incident commander owns the stop/continue decision, while platform, database, security, and business owners sign their gates.
Canary enablement. Start with non-destructive representative DAGs, then one critical pattern with strong reconciliation and an open rollback window.
Cohort promotion. Enable DAGs by dependency and side-effect group, not arbitrary count; watch at least one full relevant schedule cycle.
Hold points. Pause after metadata migration, component health, first scheduled run, first manual run, and each critical cohort.
Closure. Preserve the 2.11.2 artifacts and backup until the agreed observation period, backlog, and business reconciliation are complete.
Use Airflow audit logging guidance to retain who triggered, cleared, edited, or changed state during the migration. Stop promotion on any unexplained missed run, duplicate side effect, wrong business date, privilege regression, rising database saturation, or API failure that threatens task progress. A stop is not automatically a rollback; first determine whether freezing further enablement preserves a safer recovery path.
Rehearse Rollback from Every Migration Stage
Rollback differs across package, configuration, metadata, and business-state boundaries. Rehearse each stage with production images, backups, manifests, credentials, and roles, recording when each path expires.
Stage-specific rollback matrix
Failure stage | Rollback path | Restore requirement |
Before metadata migration | Stop candidate components; redeploy frozen 2.11.2 images/config/providers; resume after health checks. | No restore unless unrelated data corruption occurred. |
After package deployment, before schema change | Revert image digests and manifests; verify startup commands and routes; replay only approved runs. | Usually no restore if the database was unchanged. |
During or after metadata migration, before production work | Stop all Airflow 3 writers; restore the consistent pre-migration backup; redeploy 2.11.2; reconcile run boundary. | Mandatory unless an exact project-supported downgrade is proven. |
After partial DAG cutover | Freeze new cohorts; choose forward fix or full restore based on schema and side effects; disable duplicates. | Database restore may be mandatory, but external effects require separate reconciliation/compensation. |
After business side effect | Do not rely on metadata restore to undo external writes; apply idempotent replay or approved compensation. | Metadata restore alone is insufficient and may increase duplication risk. |
Name who stops components, identifies active jobs, retrieves backup keys, switches endpoints, verifies schema, authenticates old images, and calculates the resumption boundary. Test under pressure; an inaccessible key or deleted image invalidates rollback.
Rollback triggers: failed schema change, corruption, unrecoverable auth/API outage, wrong dates, duplicate Tier 0 effects, or an expiring recovery window.
Recovery conditions: known schema, healthy 2.11.2 control plane, no ambiguous run, reconciled effects, and approval to resume.
Post-rollback: preserve logs, migration output, database diagnostics, manifests, and events before another attempt.
Practice a forward stop too. A semantically wrong cohort may be paused and fixed while validated cohorts remain. A half-migrated database is platform-wide: stop writers and restore.
Execute the 60-Day Airflow 3 Migration Plan
A 60-day plan sequences risk retirement. Smaller estates may compress elapsed time, not evidence gates. Managed services can control versions, schema, images, or rollback; document that constraint and use the supported path.
60-day migration timeline
Days | Primary work | Exit evidence |
1-8 | Inventory DAGs, owners, schedules, business consumers, side effects, providers, plugins, executors, images, and deployment paths. | Complete ownership/criticality register; no unknown production runtime. |
9-16 | Reach and stabilize Airflow 2.11.2 where supported; freeze Python, providers, custom wheels, config, and image digests. | Seven-day health baseline; clean DAG reserialization; assigned config findings. |
17-28 | Run Ruff and repository scans; migrate SDK/import paths; install standard provider; remove direct DB access; redesign removed features. | Reviewed remediation merged; clean reproducible 2.x and 3.3.1 candidate builds. |
29-36 | Clean approved metadata, restore backup, rehearse schema migration, time locks/storage, and validate process/network architecture. | Timed restore and migration; signed database/window/rollback plan. |
37-46 | Qualify auth, API v2, plugins, executors, secrets, logs, Helm/manifests, and startup commands; execute semantic test matrix. | Platform qualification plus scheduled/manual/backfill/date evidence. |
47-54 | Run shadow workloads, failure injection, XCom/retry/callback comparison, downstream reconciliation, and rollback exercises. | Signed pattern evidence; unresolved differences dispositioned. |
55-60 | Migrate database, start Airflow 3.3.1, enable canary and phased cohorts, hold at gates, close exceptions or roll back. | Business and platform acceptance; protected rollback artifacts; exception closure owner/date. |
Maintain one board for surface, component/DAG, owner, evidence, gate, blocker, rollback dependency, due date, and exception expiry. Review risk changes daily; status colors without evidence are not progress.
For adjacent skills, see must-have tools for data engineers and the data engineering career roadmap. Keep learning goals outside the release-critical path: approval depends on ownership and evidence.
Day-one actions: freeze the estate, name critical-DAG owners, and create the two-version build matrix.
Weekly evidence: risk register, package results, migration timing, semantic mismatches, and rollback readiness.
Exception rule: require an owner, control, test, expiry, and rollback consequence.
Completion rule: retire Airflow 2 artifacts only after observation, reconciliation, and recovery approval.
Reserve parallel compute, isolated outputs, database headroom, test executor capacity, and reconciliation staff. They buy evidence before rollback narrows. Close by operationalizing dashboards, retiring exception credentials, deleting shadow data under policy, and transferring runbooks to on-call.
A successful Airflow 2 to 3 migration proves four things at once: DAG code uses supported interfaces; the metadata database can be migrated and recovered within an accepted window; the Airflow 3 process and security architecture remains healthy under real workload; and every critical workflow preserves its intended business date, interval, retries, XCom behavior, callbacks, and downstream effects. Any one of those can fail while the user interface remains green.
Start with three actions: freeze and inventory the current estate, stabilize the 2.11.2 staging baseline, and run a production-sized backup/restore plus metadata-migration rehearsal. Then move code and packages through reproducible images, test schedule semantics explicitly, compare shadow outcomes, and promote only at signed gates. Readers considering the wider profession can separately explore whether data engineering is future-proof, but this cutover should remain an engineering-control decision.
For structured practice in the broader foundations behind this work, the Refonte Learning Data Engineering Program describes a three-month commitment of 12–14 hours per week and covers data warehousing and ETL, scalable data pipelines, batch and streaming processing, data governance, Hadoop and Spark, and practical projects. Its page also advertises a Training Certificate and Certificate of Internship upon successful completion. The page does not explicitly name Airflow 3, the Task SDK, provider packages, Helm, or metadata migration, so the connection is to verified pipeline and governance foundations rather than an asserted product-specific module.
