migrating-dagster-to-airflow

Par astronomer · agents

Guide pour migrer des projets Dagster vers Apache Airflow 3 sur Astro. À utiliser lorsque l'utilisateur mentionne la migration, la conversion ou le portage de code Dagster (ou Dagster+) vers Airflow ou Astro, souhaite planifier ou évaluer une telle migration, ou demande à quel concept Airflow correspond un construct Dagster. Couvre les assets, partitions, schedules, sensors, l'automatisation déclarative, les resources, les IO managers, les ops/jobs, dbt, Pipes, Components et la configuration de la plateforme Dagster+. Toujours charger cette skill en premier pour toute demande de migration Dagster vers Airflow.

npx skills add https://github.com/astronomer/agents --skill migrating-dagster-to-airflow

Migration Dagster → Airflow 3 (Astro)

Migrer un projet Dagster vers Airflow 3 sur Astro Runtime, honnêtement. La migration est asset-first (les graphes d'assets Dagster se traduisent en assets Airflow et schedules asset-aware, pas en DAGs aplatis), incrémentale (domaine par domaine, Dagster reste autoritaire jusqu'à la parité), et honnête (chaque définition reçoit une disposition explicite ; les déltas sémantiques sont documentés, jamais occultés).

Tu conduis ça pour la première fois ? Lis d'abord reference/quickstart.md : les commandes de première heure, le glossaire, et ce qui peut et ne peut pas casser.

Migration en un coup d'œil

  1. Baseline la suite de tests du projet source, puis l'inventorie en lecture seule (scripts/inventory.py → manifest).
  2. Revois les classifications (MECH/JUDG/REDESIGN/NONE par reference/mapping.md) ; fais l'appel go/no-go (trois résultats ; migrate-with-conditions est le cas courant, stay est le cas rare) ; planifie les frontières de DAG, les décisions IO par arête, et les attentes Gate 3 dans le manifest.
  3. Effectue un essai de migration 2-3 unités représentatives de bout en bout à travers chaque gate de validation.
  4. Migre domaine par domaine par l'échelle six-gate (reference/validation.md), en suivant l'état par unité (scripts/status.py) ; corrige les classes d'échec via reference/troubleshooting.md, jamais de stubs.
  5. Mappe la couche plateforme (secrets, alertes, CI/CD, Deployments) par reference/astro-deployment.md.
  6. Exécute en parallèle, puis bascule par domaine (les consommateurs reprennent d'abord ; voir la checklist), en gardant le rollback à un pas.
  7. Livre le rapport de migration : chaque définition disposée, une ligne d'équivalence par déclencheur, les pertes énoncées clairement.

Dérive de version

Vérifié contre Airflow 3.3.0 / Astro Runtime 3.3-2 / astronomer-cosmos 1.15 / Dagster 1.13 (2026-07). Les lignes sensibles aux versions dans les références portent leur étage (notamment la surface partition 3.2-vs-3.3). Avant de te fier à une affirmation version-gated : vérifie la cible (airflow version, astro deployment inspect), sonde les imports pour la surface SDK (python3 -c "from airflow.sdk import X"), et préfère --help / découverte API spec aux contrats CLI/REST verbatim supposés sur les versions plus récentes. Les entrées du playbook sont version-scoped par entrée.

Exigences

  • Target Astro Runtime 3.3+ (Airflow 3.3+) ; la surface asset-partition native l'exige. Sous 3.2 le mapping se dégrade mal ; dis-le et recommande une mise à niveau avant migration.
  • Le repo Dagster, et idéalement une instance Dagster en cours d'exécution (ses métadonnées de matérialisation fournissent les fixtures de test de parité).
  • CLI astro pour le projet cible.

Règles dures

  1. Jamais de stubs. Une unité traduite fonctionne soit par sa gate de validation, soit est reportée avec raison écrite. Les corps fake-success et code workaround avec longs commentaires justificatifs sont des échecs.
  2. Aucune omission silencieuse. Chaque enregistrement du manifest d'inventaire se termine par complete ou deferred (reason). scripts/status.py summary sort nonzero sinon ; lance-le avant de déclarer done.
  3. Lignes d'équivalence pour chaque déclencheur. Chaque schedule/sensor/automation condition obtient une ligne de rapport : orthographe source, orthographe cible, delta en une phrase. Les deltas sémantiques existent (catchup, inversion on_cron, garanties eager) ; le péché n'est pas le delta, c'est le delta indocumenté.
  4. Corrige les classes, pas les instances. Quand un pattern de traduction échoue la validation, corrige le pattern (et enregistre-le dans reference/troubleshooting.md), puis réapplique ; ne fais pas de patch manuel sur une unité.
  5. N'invente pas d'APIs. Les références ne contiennent que des noms vérifiés. Tout ce qui n'y est pas couvert est vérifié contre les docs officielles avant utilisation.

Workflow

Phase 0 : Preflight

Confirme la version Runtime cible, la présence CLI astro, et l'accès au repo. Détecte la disposition du projet : classique (@repository/workspace.yaml), moderne (Definitions), ou Components (pyproject.toml [tool.dg], fichiers defs.yaml) ; les trois se produisent, parfois ensemble. Baseline la suite de tests du projet source maintenant : les échecs préexistants sont enregistrés et exclus du blâme de migration.

Phase 1 : Inventory (lecture seule)

python3 scripts/inventory.py <dagster_repo> --out manifest.json          # static scan
python3 scripts/inventory.py <dagster_repo> --runtime --out manifest.json # + runtime introspection when the project imports

Le manifest liste chaque définition avec file:line, paramètres capturés, orthographe current-vs-deprecated, et arêtes de dépendance avec leur IO manager ; chaque enregistrement commence par classification: "pending". Classer est TA première tâche de jugement : assigne à chaque enregistrement MECH / JUDG / REDESIGN / NONE depuis sa ligne dans reference/mapping.md et écris-la dans le manifest. Le scanner énumère (complétude déterministe) ; l'agent classe (jugement). Un enregistrement que tu ne peux pas mapper à une ligne mapping.md est lui-même un finding : enregistre-le, ne devines pas. Grep aussi pour DAGSTER_CLOUD_ et EnvVar( (couche plateforme, Phase 5).

Conventions de manifest : le manifest canonique vit dans le répertoire d'exécution de migration. Une fois le projet Astro créé (Phase 2 scaffold), copie le manifest à son include/inventory/manifest.json pour que Gate 3 pytest et les défauts status.py le trouvent ; jusque-là il reste juste dans le run dir (garde les deux en sync après, la copie run-dir gagne). Les enregistrements statiques sont les unités canoniques de migration ; l'enrichissement mode runtime fusionne en eux, et seules les définitions genuinely runtime-only (factory-generated) deviennent de nouvelles unités.

Émets le squelette du rapport de migration maintenant : une section par enregistrement manifest, plus la carte de nommage secrets/env de reference/astro-deployment.md. Scale le squelette au projet : un projet local sans secrets obtient une note one-line "no secrets/platform layer", pas des sections boilerplate vides.

Phase 1.5 : Go/no-go (la gate honnête)

Avant de traduire quoi que ce soit, réponds à la question au niveau du projet que l'inventaire rend répondable : qu'est-ce que cette équipe abandonne en migrant, et chaque perte a-t-elle une réponse acceptable ? Évalue les lignes NONE et REDESIGN contre ce qui est load-bearing pour CETTE équipe, en évaluant la mitigation, pas juste la perte :

Si load-bearing La réponse Airflow-world Signal stay seulement si
dbt rebuild-on-code-change (code_version_changed()) Builds dbt state-aware sur un cron (Fusion / dbt State skip unchanged models par run, donc le tick post-deploy rebâtit exactement ce qui a changé), et/ou dbt build déclenché par CI en merge (PR-gated, souvent une upgrade) L'équipe ne peut ni exécuter une stack dbt state-aware ni dbt depuis CI
Freshness driving materialization Astro Observe freshness SLAs / Timeliness alerts + scheduled runs dimensionnées à la SLA Freshness-triggered compute est genuinely irremplaçable par schedule+alerting
Per-asset cost accounting (Insights) Astro Observe pipeline-level warehouse cost management ; la granularité per-asset est perdue La chargeback par ASSET est une exigence contractuelle/organisationnelle
Asset catalog / column-level lineage comme outils quotidiens Airflow 3 asset views + OpenLineage/Astro lineage (asset-level) La column-level lineage est intégrée aux workflows quotidiens sans catalog externe
Deep AutomationCondition compositions, can_subset, selective per-partition materialization La plupart se décomposent en cron/asset schedules (voir reference/automation.md) ; le résidu est redesigné par domaine Multiple domains dépendent de compositions qui se décomposent à rien
Sensor cursor transactionality, run-scoped teardown Idempotent consumers + max_active_runs ; context managers dans les corps de tâche L'exactly-once event coalescing est une exigence de correctness que l'idempotency ne peut pas absorber

Une règle que la table implique, énoncée clairement : **aucune condition dbt-only n'atteint « stay ». Entre Cosmos, les builds dbt state-aware, et les builds déclenché par CI, chaque perte de workflow dbt a une mitigation de pratique acceptée (execution-proven dans le programme eval de cette skill, y compris sur un vrai warehouse) ; les items dbt sont des conditions à enregistrer, jamais des blockers. Les lignes d'observabilité (per-asset cost, column-level lineage) sont des conditions séparées et sont évaluées sur leurs propres, même pour les équipes dbt-heavy.

L'issue de la gate est trois-valué, et le milieu est le cas courant :

  • Migrate : aucun stay-signal ; procède à Phase 2.
  • Migrate with conditions (la plupart des vrais projets) : les pertes existent, les mitigations sont nommées et acceptées par écrit dans la première section du rapport, les domaines spécifiques peuvent porter du travail REDESIGN ; procède à Phase 2 avec ces conditions enregistrées.
  • Stay on Dagster, today : réservé au cas où MULTIPLE conditions stay-signal dans la colonne droite tiennent genuinely à la fois et la migration n'est pas mandatée en externe. Alors le livrable honnête est cette recommandation, par écrit, avec les pertes spécifiques non-mitigées nommées, et l'exécution s'arrête là. Un guide de migration qui ne peut pas dire « ne fais pas » ne peut pas être de confiance quand il dit « fais », mais « ne fais pas » est gagné par les pertes non-mitigables, pas par la simple existence de deltas.

Phase 2 : Plan

  • DAG boundaries : décide quelles arêtes de dépendance d'assets deviennent des schedules asset-aware (cross-DAG) vs task ordering (intra-DAG). Groupe par domaine/schedule cadence/team ownership ; les sélections define_asset_job nomment généralement les domaines naturels.
  • Per-edge IO decisions via l'arbre dans reference/io-and-data-passing.md (fuse / explicit storage / XCom).
  • Order : domaines feuilles d'abord, ordre dépendance après ; la couche plateforme en dernier.
  • Remplis les attentes cible de chaque unité planifiée dans le manifest : dag_id, task_count, edges, schedule, asset_outlets par unité. Gate 3 asserte contre exactement ces champs ; une unité sans eux est skippée par la validation, donc un manifest non-enrichi veut dire que Gate 3 ne vérifie rien (validate_dag rapporte les counts skipped bruyamment, ne les ignore pas).
  • Scaffold la cible : astro dev init, helpers partagés sous include/. Les conventions de maison que le scaffold impose (p. ex. un test exigeant retries >= 2) N'OVERRIDIENT PAS la source fidelity : le comportement source gagne ; l'adoption de convention est une amélioration post-cutover listée dans le rapport, et le test scaffold est skippé avec une raison explicite.

Phase 3 : Trial

Migre 2-3 unités représentatives de bout en bout à travers chaque gate avant de fanner out. Choisis un asset MECH, un asset partitionné, un cas JUDG, ou le mix le plus proche disponible (les petits projets peuvent n'avoir aucun asset partitionné ou aucun asset MECH ; choisis un chemin complet à travers un vrai DAG à la place). Ce que l'essai enseigne va dans reference/troubleshooting.md avant le scaling ; si l'essai échoue structurellement, arrête-toi et retravaille le plan, pas les unités.

Phase 4 : Migrate, domaine par domaine

Par unité, la machine d'état (suivie dans le manifest) :

pending → translate → fix-import → fix-lint → fix-tests → verify-parity → complete
                                    ↘ deferred (reason required)
  • Traduis en utilisant le fichier de référence pour la construction (table de routage ci-dessous). Le contexte riche bat la cleverness : lis l'unité source, ses lignes mapping, et un exemple déjà migré à proximité.
  • Valide à travers les gates : python3 scripts/validate_dag.py <astro_project> --manifest manifest.json (gates 1-3), puis exécution et parité par reference/validation.md.
  • En cas d'échec de gate, réessaie avec la dernière sortie du validateur en contexte (cap ~10 attempts, puis defer avec la classe d'échec).
  • Avance l'état seulement au passage de gate : python3 scripts/status.py advance <unit-id> .... Une disposition incorrecte se corrige avec status.py reopen <unit-id> --reason .... La règle no-hand-editing s'applique au champ STATE (status) seulement ; les champs PLAN (dag_id, task_count, edges, schedule, asset_outlets, target) sont à écrire du planificateur en Phase 2.
  • Les unités qui se traduisent délibérément en NO DAG de leur propre (helpers absorbés dans les tâches, policies devenues alerts, resources devenues connections) sont disposées complete avec target: "none" et evidence nommant où elles sont allées ; Gate 3 les saute par design.
  • Commit par unité, atomiquement.

Phase 5 : Couche plateforme

reference/astro-deployment.md : topologie Deployments, carte de nommage secrets/connection, CI/CD et Deployments preview, mapping de policy d'alerte, attentes Observe/lineage, la checklist rewrite in-code DAGSTER_CLOUD_*.

Phase 6 : Side-by-side et cutover

Dagster reste autoritaire. Exécute les DAGs migrés shadowed/paused ; compare les outputs sur la même fenêtre logique (row counts + checksums ; recalcule les valeurs attendues depuis l'output produit par Dagster lui-même, en utilisant les métadonnées de matérialisation enregistrées seulement opportunistically, par reference/validation.md Gate 5). Flip les schedules un domaine par change window : pause la schedule Dagster, unpause le DAG Airflow ; rollback est l'inverse. Garde Dagster lisible après cutover (la run history ne migre pas).

Phase 7 : Rapport final

scripts/status.py summary doit passer. Le rapport contient : l'évaluation go/no-go (Phase 1.5) et sa raison, la table de disposition pour chaque définition, tous les lignes d'équivalence, les pertes NONE/REDESIGN énoncées clairement (lineage depth, triggers code_version, Insights cost accounting, sensor cursor transactionality), la carte des secrets, et la liste deferred avec raisons. Spot-check dix affirmations complete avant de la livrer.

Routage de référence

Construction rencontrée Lis
Première heure, glossaire, ce qui peut casser reference/quickstart.md
N'importe quoi (première étape : une ligne par construction) reference/mapping.md
Asset-key → convention URI, granularité traduction, assets external/observable reference/assets.md
Asset deps, IO managers, XCom, décisions storage reference/io-and-data-passing.md
N'importe quel partitions_def, mappings partition, backfills reference/partitions.md
Schedules, sensors, AutomationCondition, freshness reference/automation.md
@dbt_assets, translators, dbt Cloud reference/dbt.md
Components (defs.yaml), custom Component subclasses, dynamic generation reference/components.md
dagster_cloud.yaml, secrets, alerts, CI/CD, cutover reference/astro-deployment.md
Gates, parity testing, machine d'état reference/validation.md
Classes d'échec vues avant reference/troubleshooting.md

Scripts

Script Objectif
scripts/inventory.py Scanne le repo Dagster → manifest JSON (mode statique + optional runtime)
scripts/validate_dag.py Gates 1-3 contre le projet Astro généré
scripts/status.py Machine d'état par unité + gate de complétude

Skills similaires