Les modèles incrémentiels ont un problème que les matérialisations en table n’ont pas : le schéma peut changer. Quand une colonne est ajoutée au SELECT, les données entrantes ont cette colonne mais la table existante ne l’a pas. Quand une colonne est supprimée, la table existante l’a encore mais les nouvelles données ne l’ont pas. La config on_schema_change contrôle comment dbt gère ce décalage.
Par défaut, dbt utilise ignore, qui est l’option la plus susceptible de causer de la confusion en production. Comprendre les quatre options — et leur limitation commune — prévient les types d’échecs silencieux qui prennent des semaines à remarquer.
Les quatre options
ignore (défaut)
Les nouvelles colonnes dans le modèle sont silencieusement supprimées lors de la fusion dans la table existante. La colonne existe dans la requête de staging mais n’atteint jamais la cible. Les colonnes supprimées font échouer le modèle car dbt essaie d’INSERT des données qui n’incluent pas une colonne que la table cible attend.
C’est le défaut car c’était le comportement original avant l’existence du support d’évolution de schéma. C’est le pire défaut pour les équipes développant activement des modèles — une colonne est ajoutée, l’exécution réussit, et on suppose qu’elle est là. Elle ne l’est pas.
fail
Le modèle génère une erreur immédiatement si le schéma des données entrantes ne correspond pas à la table existante. Aucune donnée n’est écrite.
C’est l’option la plus sûre pour les modèles de production où les changements de schéma doivent passer par un processus délibéré. Elle force soit à exécuter --full-refresh, soit à gérer explicitement la migration. Combinez-la avec les contrats de modèles pour une rigueur maximale.
append_new_columns
Les nouvelles colonnes sont ajoutées à la table existante via ALTER TABLE. Les colonnes supprimées sont laissées telles quelles — elles restent dans la table avec des valeurs NULL pour les nouvelles lignes.
C’est l’option la plus pratique pour les modèles en développement actif. Une colonne est ajoutée au SELECT, et lors de la prochaine exécution incrémentielle dbt l’ajoute automatiquement à la table cible. Le piège : les lignes existantes obtiennent NULL pour la nouvelle colonne. Si la nouvelle colonne doit être peuplée pour les données historiques, un --full-refresh est nécessaire.
sync_all_columns
Synchronisation bidirectionnelle complète. Les nouvelles colonnes sont ajoutées, les colonnes supprimées sont retirées. C’est l’option la plus agressive — elle supprime activement des colonnes de la table cible si elles ne sont plus dans le SELECT du modèle.
À utiliser avec prudence. Si une colonne est accidentellement supprimée du modèle (par exemple lors d’un refactoring), sync_all_columns la supprimera de la table cible lors de la prochaine exécution. Cette donnée est perdue sauf s’il y a des sauvegardes ou si un full-refresh depuis la source est possible.
La limitation commune : pas de réalimentation
Aucune de ces options ne réalimente les enregistrements historiques quand une nouvelle colonne est ajoutée. C’est la chose la plus importante à comprendre sur on_schema_change.
Quand append_new_columns ou sync_all_columns ajoute une colonne, les lignes existantes obtiennent des valeurs NULL pour cette colonne. Seules les lignes traitées après l’ajout de la colonne auront des valeurs. Si la table a 2 ans de données et qu’une colonne channel est ajoutée, seules les données de la prochaine exécution incrémentielle onwards auront channel peuplé. Les 2 années précédentes afficheront NULL.
| event_id | event_date | channel ||----------|------------|----------|| 001 | 2024-01-15 | NULL | <- Existait avant l'ajout de la colonne| 002 | 2024-06-20 | NULL | <- Existait avant l'ajout de la colonne| 003 | 2026-03-27 | organic | <- Traité après l'ajout de la colonnePour réalimenter, deux options existent :
-
Full refresh :
dbt run --select model_name --full-refresh. Reconstruit toute la table depuis zéro. Simple mais coûteux sur les grandes tables, et il faut être vigilant avec les [[fr/modeles-incrementaux-dbt|modèles qui ontfull_refresh: falsedéfini]]. -
UPDATE manuel : Exécuter une instruction UPDATE ciblée en dehors de dbt pour peupler la nouvelle colonne pour les enregistrements historiques. Plus chirurgical mais nécessite un accès à l’entrepôt et n’est pas suivi par dbt.
Configuration
Définissez on_schema_change dans le bloc config du modèle :
{{ config( materialized='incremental', unique_key='event_id', on_schema_change='append_new_columns') }}Ou dans dbt_project.yml pour les défauts à l’échelle du projet :
models: my_project: marts: +on_schema_change: fail staging: +on_schema_change: append_new_columnsUn pattern raisonnable : fail pour les marts de production (où les changements de schéma doivent être délibérés), append_new_columns pour le staging et les modèles intermédiaires (où l’itération est fréquente).
Interaction avec les stratégies
La gestion des changements de schéma fonctionne légèrement différemment selon les stratégies incrémentielles :
- merge : Les changements de schéma s’appliquent à la table cible avant l’exécution du MERGE. Les nouvelles colonnes sont ajoutées via ALTER TABLE, puis le MERGE les inclut.
- insert_overwrite : Puisque des partitions entières sont remplacées, les changements de schéma dans les partitions remplacées sont naturellement gérés. Mais les partitions non remplacées auront encore des NULLs pour les nouvelles colonnes.
- microbatch : Chaque batch s’exécute indépendamment. Si une colonne est ajoutée au milieu d’un backfill, seuls les batchs traités après le changement de schéma incluront la nouvelle colonne.
Quand les changements de schéma signalent un full refresh
Certains changements de schéma ne devraient pas être gérés incrementalement du tout. Si le grain du modèle change (ajout d’une colonne à unique_key), si le schéma de partitionnement change, ou si la transformation est fondamentalement restructurée, un --full-refresh est le bon choix. Les options on_schema_change gèrent les ajouts et suppressions de colonnes — elles ne gèrent pas les changements structurels dans la façon dont les données sont organisées dans la table.
L’option fail rend cela explicite : si le schéma a changé, quelque chose de significatif s’est produit, et un humain doit décider comment le gérer. Pour les modèles critiques en production, cette délibération vaut la légère friction.