ServicesÀ proposNotesContact Me contacter →
EN FR
Note

Les modèles incrémentaux dans dbt

Comment fonctionnent les modèles incrémentaux dbt, quand les utiliser, les stratégies disponibles et les compromis à comprendre.

Planté
dbtincremental processingdata modelingcost optimization

Les modèles incrémentaux ne traitent que les lignes spécifiées lors des exécutions suivantes, puis les fusionnent ou les insèrent dans une table existante. À la première exécution, le modèle se construit comme une table ordinaire. Ensuite, il ne lit que les données nouvelles ou modifiées, selon la stratégie et la configuration.

Le bénéfice en termes de coût et de durée d’exécution est significatif — un modèle qui prend 45 minutes et scanne 2 To en full refresh peut passer à 2 minutes et 20 Go en mode incrémental. La contrepartie est la complexité : des modèles incrémentaux mal configurés produisent des problèmes de qualité de données silencieux qui s’accumulent dans le temps.

Comment ça fonctionne

Le macro is_incremental() retourne True lorsque trois conditions sont réunies :

  1. Le modèle existe déjà comme table dans le warehouse
  2. Le flag --full-refresh n’est pas passé
  3. Le modèle est configuré avec materialized='incremental'
{{ config(materialized='incremental') }}
SELECT event_id, event_timestamp, user_id
FROM {{ ref('base__events') }}
{% if is_incremental() %}
WHERE event_timestamp > (SELECT MAX(event_timestamp) FROM {{ this }})
{% endif %}

Le SQL généré varie selon votre incremental_strategy. Les stratégies merge créent une instruction MERGE et comparent les lignes. Insert_overwrite remplace des partitions entières de manière atomique. Microbatch exécute des requêtes séparées pour chaque période.

Configuration principale

Trois paramètres définissent votre comportement incrémental :

materialized=‘incremental’ : Active la logique incrémentale.

unique_key : Définit le grain pour la déduplication. Sans lui, la plupart des stratégies ajoutent des doublons. Peut être une colonne unique ou une clé composite, mais toutes les colonnes doivent être non nulles pour correspondre correctement lors des opérations de merge.

incremental_strategy : Détermine comment les nouvelles données fusionnent dans la table existante. Les options varient selon le warehouse — toutes les stratégies ne fonctionnent pas partout.

Les stratégies

La bonne stratégie dépend de vos patterns de données, de la taille de la table et du warehouse. Le cadre de décision détaillé sur les stratégies couvre les patterns d’implémentation et les benchmarks de performance.

Merge : Compare chaque ligne entrante par rapport à la destination en utilisant unique_key. Les lignes correspondantes sont mises à jour ; les lignes sans correspondance sont insérées. Simple et atomique, mais scanne toute la table de destination sans incremental_predicates. Au-delà de 100 M de lignes, ce scan complet devient coûteux. Fonctionne sur BigQuery, Snowflake et Databricks.

Delete+Insert : Supprime d’abord les enregistrements correspondants, puis insère les nouvelles données. Deux opérations séparées signifient qu’il y a une brève fenêtre où les enregistrements supprimés sont absents mais les nouveaux n’ont pas encore atterri — non atomique contrairement au merge. Nettement plus rapide que merge à grande échelle (3,4x plus rapide à 500 M de lignes sur Snowflake). Snowflake et Databricks uniquement.

Insert_Overwrite : Remplace des partitions entières plutôt que de comparer des lignes individuelles. Atomique et efficace pour les données en série temporelle. Sur BigQuery, remplace intelligemment uniquement les partitions affectées. Sur Snowflake, remplace la table entière (pas de sécurité de partition). Disponible sur BigQuery, Snowflake (table entière uniquement) et Databricks.

Microbatch (dbt 1.9+) : Traite les données par lots temporels sans logique is_incremental(). Au lieu d’une requête unique, dbt exécute des requêtes séparées pour chaque période configurée (heure, jour, mois). Support de backfill intégré et reprises au niveau du lot. Pas de SQL conditionnel nécessaire — dbt gère le filtrage automatiquement.

Append : Tables append-only sans déduplication. Le plus rapide mais ne fonctionne que pour des données vraiment immuables. Snowflake et Databricks uniquement.

Quand l’incrémental est réellement utile

Commencer par la matérialisation en table. Passer à l’incrémental lorsque :

  • Les données sources ont des millions ou milliards de lignes
  • Les transformations sont coûteuses en calcul
  • Les exécutions dbt deviennent visiblement lentes ou coûteuses

Les tables sous 10 M de lignes bénéficient rarement de la complexité incrémentale. Le surcoût dépasse souvent les économies. Les performances de merge se dégradent notablement au-delà de 100 M de lignes, et à 500 M+, les stratégies alternatives peuvent être 3 à 5 fois plus rapides.

Gérer les données en retard

Les données en retard — enregistrements qui arrivent après leur période d’horodatage — sont la principale raison pour laquelle les modèles incrémentaux dérivent par rapport à la vérité source. Sans gestion, votre modèle diverge progressivement de la réalité.

La solution standard est une fenêtre de lookback : retraiter une fenêtre glissante de données récentes à chaque exécution.

{% if is_incremental() %}
WHERE event_date >= (
SELECT DATE_SUB(MAX(event_date), INTERVAL 3 DAY)
FROM {{ this }}
)
{% endif %}

Une fenêtre de 3 jours gère la majorité des arrivées tardives dans la plupart des systèmes. Des fenêtres plus petites coûtent moins mais manquent les données retardées. Des fenêtres plus grandes en capturent davantage mais ajoutent un coût de calcul significatif. Mesurer d’abord votre profil de latence : quel pourcentage de vos données arrive le jour même, dans les 3 jours, dans la semaine ? Cette distribution détermine la bonne taille de fenêtre.

Sur les grandes tables, utiliser incremental_predicates pour limiter le scan de la destination lors des opérations de merge. Cela indique au warehouse de ne vérifier les correspondances que dans les partitions récentes, pas dans la table entière :

{{ config(
incremental_predicates=[
"DBT_INTERNAL_DEST.created_at >= DATE_SUB(CURRENT_DATE(), INTERVAL 7 DAY)"
]
) }}

Aucune fenêtre de lookback ne capture tout indéfiniment. Les données peuvent arriver avec un retard arbitraire ou les systèmes externes peuvent avoir des pannes. Planifier des full refreshes périodiques — hebdomadaires ou mensuels — pour réinitialiser la dérive accumulée. Pour les très grandes tables où le full refresh est impraticable, en exécuter au moins un manuellement après des problèmes de pipeline connus.

Comportements spécifiques aux warehouses

BigQuery merge nécessite unique_key (sans lui, l’opération échoue plutôt que d’ajouter des données). Insert_overwrite avec des partitions statiques est extrêmement efficace et évite le surcoût de la découverte de partitions. Mais la stratégie insert_overwrite sur Snowflake remplace la table entière, pas seulement les partitions affectées — un piège critique qui a causé des pertes de données pour de nombreuses équipes.

Le merge de Snowflake atteint des murs de performance autour de 100 M de lignes. Delete+insert devient viable et souvent préféré au-delà de cette échelle. L’insert_overwrite de Snowflake est un piège ; utiliser delete+insert avec des prédicats de date explicites à la place.

Databricks supporte le merge sur Delta Lake avec des garanties ACID et l’évolution de schéma. Replace_where est une alternative basée sur des prédicats, exclusive à Databricks, souvent plus propre qu’insert_overwrite lorsque le comportement des partitions est imprévisible.

Pièges courants

Ne pas définir unique_key avec merge : Résulte en un comportement append-only avec des doublons à chaque exécution incrémentale.

NULL dans les colonnes unique_key : Échecs de correspondance lors du merge, créant silencieusement des doublons.

Pas de fenêtre de lookback : Les données en retard sont manquées ; les modèles dérivent de la source.

Utiliser l’incrémental pour les petites tables : Ajoute de la complexité sans bénéfice mesurable.

Gestion manquante des changements de schéma : L’ajout de colonnes sans configuration on_schema_change provoque des échecs.

Pas de full refresh périodique : La dérive s’accumule indéfiniment.

Utiliser merge pour les très grandes tables sans prédicats : Les scans complets de tables deviennent coûteux et annulent les bénéfices de l’incrémental.

S’appuyer uniquement sur unique_key pour la déduplication : Le full refresh initial n’applique pas la logique de merge. Inclure une déduplication explicite dans votre SELECT :

QUALIFY ROW_NUMBER() OVER (
PARTITION BY event_id
ORDER BY updated_at DESC
) = 1

Choisir entre les stratégies

Merge est le point de départ le plus simple pour les tables sous 100 M de lignes avec des mises à jour au niveau des lignes. Pour les grandes tables Snowflake, delete+insert surpasse souvent merge de manière significative. Sur BigQuery, insert_overwrite avec des partitions statiques est efficace et rentable pour les tables de faits partitionnées par temps. Pour les grands backfills historiques et les situations où une reprise au niveau du lot économiserait du temps de retraitement, microbatch simplifie la logique et fournit un support de backfill intégré.

La stratégie microbatch fonctionne particulièrement bien pour les données d’événements partitionnées par temps où vous ne voulez pas écrire de logique is_incremental(). Compromis : elle utilise UTC pour tous les calculs (convertir d’abord les horodatages locaux), a une taille de lot minimale d’une heure (ne convient pas au streaming sub-horaire) et exécute les lots séquentiellement par défaut.

Choisir la stratégie la plus simple qui couvre vos besoins. Commencer par mesurer à quel point vos données arrivent réellement en retard, comment vos tables vont croître et quel warehouse vous utilisez. Ces données déterminent si merge est suffisant ou si vous devez optimiser davantage.

Pour des patterns d’implémentation complets et des détails spécifiques aux warehouses, voir le guide complet des modèles incrémentaux.