La stratégie incrémentale détermine comment les nouvelles données entrent dans une table existante, ce qui se passe en cas d’échec, et comment les coûts évoluent à mesure que les tables grandissent. La stratégie par défaut (merge) fonctionne pour les tables de taille petite à moyenne, mais devient coûteuse au-delà de 100 M de lignes sans configuration supplémentaire.
Cette note couvre ce que fait chaque stratégie, quand chacune convient, et les comportements spécifiques aux warehouses qui affectent le choix.
Disponibilité des stratégies par warehouse
Toutes les stratégies ne fonctionnent pas partout. Ce tableau vous évite de configurer quelque chose que votre warehouse rejettera.
| Stratégie | BigQuery | Snowflake | Databricks |
|---|---|---|---|
| append | Non disponible | Disponible | Disponible |
| merge | Défaut | Défaut | Défaut |
| delete+insert | Non disponible | Disponible | Disponible (v1.11+) |
| insert_overwrite | Disponible | Table entière uniquement | Disponible |
| replace_where | Non disponible | Non disponible | Delta Lake uniquement |
| microbatch | Disponible | Disponible | Disponible |
Le piège critique : insert_overwrite sur Snowflake remplace la table entière, pas les partitions individuelles. Cette incohérence de nommage a causé des pertes de données. Sur Snowflake, utiliser delete+insert avec des prédicats de date pour un comportement de type partition.
Ce que fait chaque stratégie
Merge
Compare chaque ligne entrante avec la table de destination en utilisant votre unique_key. Les lignes correspondantes sont mises à jour ; les lignes sans correspondance sont insérées. Le SQL généré est une instruction MERGE INTO standard.
Le problème est la portée du scan. Sans incremental_predicates, le merge scanne chaque ligne de la destination pour trouver des correspondances — même si vous insérez 1 000 enregistrements dans une table de 500 M de lignes. Ce scan complet se produit à chaque exécution, ce qui signifie que les coûts évoluent avec la taille de la table plutôt qu’avec le volume de nouvelles données. Au-delà de 100 M de lignes, cela devient visiblement lent et coûteux.
Merge est atomique : il se complète entièrement ou effectue un rollback. C’est l’option la plus sûre lorsque vous avez besoin de mises à jour au niveau des lignes sur des tables de petite à moyenne taille.
Delete+Insert
Exécute deux opérations séparées : supprime tous les enregistrements correspondants par unique_key, puis insère le nouveau lot. La nature en deux étapes est le principal compromis. Si le processus échoue entre la suppression et l’insertion, les enregistrements disparaissent jusqu’à la prochaine exécution réussie ou un full refresh.
Sur Snowflake, delete+insert est 3,4x plus rapide que merge à 500 M+ de lignes. L’écart de performance s’élargit à mesure que les tables grandissent, car delete+insert évite la comparaison ligne par ligne qui rend merge coûteux.
Insert_Overwrite
Remplace des partitions entières plutôt que de comparer des lignes individuelles. Sur BigQuery, découvre quelles partitions contiennent des nouvelles données et les échange atomiquement. Pas de comparaison de lignes, pas de scan complet de table. Vous pouvez utiliser des listes de partitions statiques (plus rapide, pas de requête de découverte) ou la découverte dynamique de partitions.
Cette stratégie nécessite des tables partitionnées — généralement partitionnées par temps sur une colonne date ou timestamp. Elle ne peut pas mettre à jour des lignes spécifiques dans une partition ; la partition entière est remplacée.
Les partitions statiques sont l’approche la plus rentable sur BigQuery :
{% set partitions_to_replace = [ 'CURRENT_DATE()', 'DATE_SUB(CURRENT_DATE(), INTERVAL 1 DAY)', 'DATE_SUB(CURRENT_DATE(), INTERVAL 2 DAY)'] %}
{{ config( materialized='incremental', incremental_strategy='insert_overwrite', partition_by={'field': 'event_date', 'data_type': 'date'}, partitions=partitions_to_replace) }}Les équipes passant de merge à insert_overwrite avec des partitions statiques rapportent des réductions de coûts de 100 à 200x sur les grandes tables.
Replace_Where (Databricks uniquement)
Remplace toutes les lignes correspondant à une condition de prédicat, en utilisant la syntaxe INSERT INTO ... REPLACE WHERE de Delta Lake. Plus flexible que le remplacement basé sur les partitions car le prédicat peut être n’importe quelle expression, pas seulement une limite de partition. Atomique.
{{ config( materialized='incremental', incremental_strategy='replace_where', incremental_predicates=["event_date >= CURRENT_DATE - INTERVAL 3 DAY"]) }}C’est souvent le choix le plus propre sur Databricks pour les mises à jour délimitées par date, notamment sur les SQL Warehouses où le comportement de partition d’insert_overwrite peut être imprévisible.
Microbatch (dbt 1.9+)
Traite les données par lots temporels — une requête par heure, jour ou mois — sans écrire de logique is_incremental(). Vous configurez event_time, batch_size et lookback ; dbt s’occupe du reste.
Avantages clés par rapport à l’incrémental traditionnel : reprise au niveau du lot (un échec sur le lot d’un jour ne nécessite pas de retraiter toute l’exécution), backfill intégré via les flags --event-time-start et --event-time-end, et filtrage automatique en amont lorsque les modèles référencés ont également event_time configuré.
Sous le capot, microbatch délègue à différentes stratégies par warehouse : insert_overwrite sur BigQuery (nécessite partition_by), delete+insert sur Snowflake, et replace_where sur Databricks.
Limitations : taille de lot minimale d’une heure (pas de sub-horaire), tous les calculs de temps utilisent UTC, et les lots s’exécutent séquentiellement par défaut.
Append
Insert-only sans déduplication. La stratégie la plus rapide car il n’y a pas d’étape de comparaison ou de suppression. Appropriée uniquement lorsque les données sont vraiment immuables — journaux d’événements bruts, pistes d’audit, flux append-only. Vous gérez la déduplication en aval. Non disponible sur BigQuery.
Cadre de décision
Commencer par le pattern de données et la taille de la table, puis tenir compte de votre warehouse.
Utiliser merge quand :
- Les tables ont moins de 100 M de lignes
- Vous avez besoin de mises à jour au niveau des lignes (les enregistrements individuels changent dans le temps)
- Vous gérez des patterns CDC avec des insertions, mises à jour et suppressions mixtes
- L’atomicité est importante et vous ne pouvez pas tolérer des états intermédiaires d’échec
Ajouter incremental_predicates dès que la taille de la table rend les scans complets perceptibles. Cela limite le scan de destination aux partitions récentes lors du merge et constitue le levier de performance unique le plus important pour merge sur les grandes tables.
Utiliser delete+insert quand :
- Les tables dépassent 100 M de lignes sur Snowflake
- Les mises à jour sont orientées par lots (remplacement de tous les enregistrements pour une fenêtre temporelle)
- Vous pouvez tolérer un bref état intermédiaire si le job échoue en cours d’exécution
- Vous avez besoin d’un comportement de type partition sur Snowflake (puisqu’insert_overwrite n’aide pas)
Utiliser insert_overwrite quand :
- Les tables sont partitionnées par temps sur BigQuery ou Databricks
- Vous traitez des données d’événements ou de faits où des partitions entières sont retraitées
- L’optimisation des coûts est une priorité (les partitions statiques sont particulièrement économiques)
- Les données en retard tombent dans votre fenêtre d’écrasement
Utiliser microbatch quand :
- Vos données ont une colonne timestamp claire et vous traitez des lots délimités dans le temps
- Vous voulez un support de backfill intégré sans écrire de scripts personnalisés
- La reprise au niveau du lot économiserait un temps de retraitement significatif en cas d’échec
- Vous préférez configurer la logique incrémentale plutôt qu’écrire du SQL
is_incremental()
Utiliser append quand :
- Les données sont vraiment append-only sans corrections ni mises à jour
- La déduplication se produit dans une couche en aval
- Vous êtes sur Snowflake ou Databricks
Recommandations spécifiques aux warehouses
BigQuery : Préférer insert_overwrite avec des listes de partitions statiques pour les tables de faits. Utiliser merge avec incremental_predicates pour les tables de dimensions nécessitant des mises à jour au niveau des lignes. Microbatch nécessite une configuration partition_by correspondant à votre batch_size.
Snowflake : Commencer par merge pour les tables sous 100 M de lignes. Passer à delete+insert lorsque les tables grandissent ou pour les mises à jour orientées par lots. Ne jamais utiliser insert_overwrite pour le traitement incrémental — cela remplace la table entière. Microbatch utilise delete+insert en interne et fonctionne sans configuration supplémentaire.
Databricks : Merge fonctionne bien avec les garanties ACID de Delta Lake et supporte l’évolution de schéma. Replace_where est souvent plus propre qu’insert_overwrite pour les mises à jour délimitées par date. Vérifier que spark.sql.sources.partitionOverwriteMode=DYNAMIC est défini si vous utilisez insert_overwrite — une régression en v1.8.0 causait le remplacement de la table entière sans cela.
Benchmarks de performance
Ces chiffres proviennent de systèmes en production et illustrent pourquoi le choix de stratégie est important à grande échelle :
| Scénario | Merge | Meilleure alternative | Amélioration |
|---|---|---|---|
| 500 M de lignes, Snowflake | 44 min | 13 min (delete+insert) | 3,4x |
| Partition 192 Go, BigQuery | 43 min | 26 min (insert_overwrite) | 1,7x |
| Scan complet vs incrémental, BigQuery | 24,6 Go scannés | 500 Mo scannés | Réduction 50x |
| Avec incremental_predicates ajoutés | 9,5 s | 4 s | 2,4x |
Le pattern est cohérent : merge fonctionne à plus petite échelle, mais les stratégies alternatives prennent clairement l’avantage une fois que les tables dépassent 100 M de lignes.
Pièges courants
Les valeurs null dans les colonnes unique_key causent des échecs de correspondance lors du merge, créant silencieusement des doublons au lieu de mises à jour. Chaque colonne dans votre unique_key doit être non nulle.
L’insert_overwrite de Snowflake remplace la table entière. Si votre requête incrémentale ne sélectionne que les 3 derniers jours, vous perdez tout ce qui est plus ancien. Utiliser delete+insert à la place.
Pas de fenêtre de lookback signifie que les données en retard sont ignorées définitivement. Un lookback de 3 jours gère la majorité des arrivées tardives dans la plupart des systèmes. Pour des patterns plus approfondis, voir l’article sur les stratégies pour les données en retard.
Merge sans incremental_predicates sur les grandes tables annule le bénéfice du traitement incrémental — vous scannez encore toute la destination à chaque exécution.
S’appuyer uniquement sur unique_key pour la déduplication échoue sur le full refresh initial, qui n’applique pas la logique merge. Inclure une déduplication explicite (QUALIFY/ROW_NUMBER) dans votre SELECT.