Refactorer un modèle dbt sans moyen de prouver que le résultat n’a pas changé, c’est du pari. Vous pouvez examiner le SQL à l’œil, lancer quelques vérifications ponctuelles et espérer que tout va bien. Ou vous pouvez utiliser dbt-audit-helper pour comparer votre nouveau modèle avec l’original, ligne par ligne et colonne par colonne, et savoir exactement ce qui a changé.
dbt-audit-helper est maintenu par dbt Labs, actuellement en version 0.13.0. Il est compatible Fusion (require-dbt-version: >=1.2.0, <3.0.0), dépend de dbt-utils, et supporte Snowflake, BigQuery, Postgres et Redshift. Les macros rapides basées sur le hash sont limitées à Snowflake et BigQuery.
Le workflow de validation progressive
Audit-helper fonctionne mieux quand on commence large puis qu’on affine. Ne vous lancez pas directement dans une comparaison ligne par ligne sur une table de 100 millions de lignes. Suivez plutôt cette séquence :
- Vérification du schéma avec
compare_relation_columns(les colonnes sont-elles identiques ?) - Vérification du nombre de lignes avec
compare_row_counts(même nombre de lignes ?) - Vérification rapide par hash avec
quick_are_relations_identical(résultat rapide pass/fail, Snowflake/BigQuery uniquement) - Comparaison ligne par ligne avec
compare_relationsoucompare_queries(quelles lignes diffèrent ?) - Identification des colonnes problématiques avec
compare_which_relation_columns_differ(où sont les différences ?) - Analyse approfondie par colonne avec
compare_column_values(qu’est-ce qui a exactement changé dans une colonne spécifique ?) - Audit complet des colonnes avec
compare_all_columns(résumé par colonne sur l’ensemble des colonnes) - Classification des lignes avec
compare_and_classify_relation_rows(chaque ligne est-elle identique, modifiée, ajoutée ou supprimée ?)
Chaque étape coûte moins cher que la suivante. Si le schéma ou le nombre de lignes révèle déjà un écart, inutile de lancer une comparaison complète des lignes. Cela économise du temps et des ressources warehouse.
Référence des macros
compare_relation_columns
Compare les métadonnées de schéma entre deux relations : noms de colonnes, positions ordinales et types de données. Cela détecte les colonnes manquantes, les changements de types et les réordonnancements avant de dépenser des cycles sur la comparaison des données.
{{ audit_helper.compare_relation_columns( a_relation=ref('mrt__finance__revenue'), b_relation=api.Relation.create( database='analytics_db', schema='production', identifier='mrt__finance__revenue' )) }}Lancez ceci en premier. Si les schémas ne correspondent pas, vous devrez résoudre ce problème avant que les comparaisons ligne par ligne aient un sens.
compare_row_counts
Une simple comparaison du nombre de lignes entre deux relations. Ajouté en v0.11.0 (mars 2024).
{{ audit_helper.compare_row_counts( a_relation=ref('mrt__finance__revenue'), b_relation=api.Relation.create( database='analytics_db', schema='production', identifier='mrt__finance__revenue' )) }}Si les nombres correspondent, passez à la comparaison des données. Sinon, vous avez soit un problème de filtrage, soit une jointure qui multiplie les lignes.
quick_are_relations_identical
Utilise une comparaison par hash pour un résultat rapide pass/fail. Snowflake et BigQuery uniquement.
{{ audit_helper.quick_are_relations_identical( a_relation=ref('mrt__finance__revenue'), b_relation=api.Relation.create( database='analytics_db', schema='production', identifier='mrt__finance__revenue' )) }}Si ça passe, c’est terminé. Pas besoin de comparaison plus approfondie. Si ça échoue, passez à compare_relations pour comprendre pourquoi.
Il existe aussi une variante basée sur les requêtes : quick_are_queries_identical.
compare_relations
La macro principale. Effectue une validation ligne par ligne en utilisant une approche basée sur UNION.
{{ audit_helper.compare_relations( a_relation=ref('mrt__finance__revenue'), b_relation=api.Relation.create( database='analytics_db', schema='production', identifier='mrt__finance__revenue' ), exclude_columns=["_fivetran_synced"], primary_key="revenue__id", summarize=true) }}Avec summarize=true (la valeur par défaut), la sortie affiche des comptages et des pourcentages :
- True/True : lignes présentes dans les deux relations (correspondances)
- True/False : lignes uniquement dans A
- False/True : lignes uniquement dans B
Définissez summarize=false pour voir les données réelles des lignes avec les indicateurs in_a/in_b, ce qui aide à identifier les lignes spécifiques qui diffèrent.
compare_queries fonctionne de manière identique mais accepte des requêtes SQL SELECT brutes au lieu de relations. Utilisez-le quand vous devez filtrer, renommer ou recaster des colonnes avant la comparaison.
compare_which_relation_columns_differ
Quand compare_relations montre des écarts mais que vous ne savez pas quelles colonnes en sont la cause, cette macro identifie les coupables. Ajouté en v0.12.0 (juin 2024).
{{ audit_helper.compare_which_relation_columns_differ( a_relation=ref('mrt__finance__revenue'), b_relation=api.Relation.create( database='analytics_db', schema='production', identifier='mrt__finance__revenue' ), primary_key_columns=['revenue__id']) }}Lancez ceci avant compare_column_values pour éviter de vérifier les colonnes une par une manuellement.
compare_column_values
Analyse en profondeur les valeurs d’une seule colonne entre deux requêtes jointes sur une clé primaire.
{{ audit_helper.compare_column_values( a_query="SELECT revenue__id, revenue__amount FROM " ~ ref('mrt__finance__revenue'), b_query="SELECT revenue__id, revenue__amount FROM analytics_db.production.mrt__finance__revenue", primary_key="revenue__id", column_to_compare="revenue__amount") }}La sortie catégorise chaque ligne comme : correspondance parfaite, les deux nuls, absent de A, absent de B, nul dans A uniquement, nul dans B uniquement, ou valeurs différentes.
Piège : la méthode .print_table() n’est pas compatible avec dbt Cloud. Utilisez un contournement avec log() pour afficher les résultats dans l’IDE Cloud.
compare_all_columns
Compare toutes les colonnes entre deux relations, colonne par colonne. La sortie résumée affiche des comptages par colonne de perfect_match, null_in_a, null_in_b, missing_from_a, missing_from_b et conflicting_values.
C’est la macro conçue pour la CI. Créez un test qui filtre sur les conflits :
models: - name: mrt__finance__revenue data_tests: - dbt_utils.expression_is_true: expression: "conflicting_values = 0" where: "1=1"Ou utilisez --store-failures pour persister les écarts en vue d’une investigation.
compare_and_classify_query_results
Ajouté en v0.12.0 et marqué “v0, sujet à changement.” Classifie chaque ligne comme identique, modifiée, ajoutée ou supprimée.
{{ audit_helper.compare_and_classify_relation_rows( a_relation=ref('mrt__finance__revenue'), b_relation=api.Relation.create( database='analytics_db', schema='production', identifier='mrt__finance__revenue' ), primary_key_columns=['revenue__id'], columns=['revenue__amount', 'revenue__currency', 'revenue__booked_at'], sample_limit=20) }}Le paramètre sample_limit (par défaut 20) limite le nombre de lignes d’exemple retournées par classification. Le wrapper pour les relations est compare_and_classify_relation_rows.
Workflows concrets
Migration SQL-vers-dbt
Lors d’une migration de SQL legacy vers dbt, la question est toujours “est-ce que mon nouveau modèle dbt produit le même résultat que l’ancienne requête ?” Chargez vos tables existantes dans le même warehouse que votre projet dbt, construisez les modèles dbt équivalents, puis comparez-les systématiquement.
Le workflow que je recommande :
- Commencez par
compare_relation_columnspour vérifier que les schémas correspondent. Les noms de colonnes, types et ordres doivent être identiques. Corrigez tout écart ici avant d’aller plus loin. - Lancez
compare_row_countscomme vérification rapide de cohérence. Des comptages différents pointent généralement vers des différences de filtrage ou des fan-outs de JOIN. - Lancez
compare_relationspour un pourcentage de correspondance global. Un match à 100 % signifie que c’est terminé. En dessous, il faut creuser. - Si en dessous de 100 %, utilisez
compare_which_relation_columns_differpour isoler les colonnes problématiques. Cela vous évite de vérifier chaque colonne manuellement. - Utilisez
compare_column_valuespour investiguer chaque colonne problématique et comprendre la nature exacte des écarts.
Un conseil pratique de l’équipe Indicium Tech : comparez 5 colonnes à la fois, confirmez qu’elles correspondent, puis élargissez. Cela maintient une boucle de feedback serrée et vous empêche de courir après trop de problèmes à la fois. Essayer de débuguer tous les écarts simultanément mène généralement à la confusion sur quel changement a corrigé quoi.
Validation de refactor
Le refactoring est le cas d’usage quotidien principal d’audit-helper. Vous réécrivez un CTE, changez une stratégie de jointure ou restructurez un modèle, et vous devez vérifier que la sortie reste identique. Utilisez compare_all_columns en référençant votre modèle dev par rapport à la production :
{{ audit_helper.compare_all_columns( a_relation=ref('mrt__finance__revenue'), b_relation=api.Relation.create( database='analytics_db', schema='production', identifier='mrt__finance__revenue' ), primary_key_columns=['revenue__id']) }}Dans cette configuration, ref('mrt__finance__revenue') résout vers votre schéma dev tandis que api.Relation.create pointe directement vers la production. Toute différence apparaît immédiatement.
Intégration CI/CD
Dans dbt Cloud : incluez les tests compare_all_columns dans les jobs CI déclenchés sur les pull requests. Le ref() compile automatiquement vers le schéma PR, donc la comparaison dev-vs-prod se fait à chaque PR.
Dans dbt Core avec GitHub Actions : construisez les modèles modifiés avec --defer --state pour cibler uniquement les modèles changés, puis lancez les tests d’audit avec --store-failures pour persister les écarts en vue d’une revue.
Pour l’investigation interactive, Hex propose un template de notebook qui se marie bien avec audit-helper pour des workflows visuels de validation de PR.
Migrations ETL à grande échelle
Pour les migrations d’entreprise (comme Informatica vers dbt), le package dbt-audit-helper-ext d’Infinite Lambda étend audit-helper avec une table de log de validation, une vue de synthèse, des scripts de codegen pour générer automatiquement les macros de validation, et une acceptation basée sur des seuils : 100 % de correspondance = succès, 99 %+ = avertissement, en dessous de 99 % = échec. Il est disponible sur le dbt package Hub sous infinitelambda/audit_helper_ext (v0.8.0).
Limitations et pièges
Exigence de clé primaire. La plupart des macros nécessitent une clé primaire unique et non nulle pour joindre les deux relations. Assurez-vous que vos modèles passent les tests unique et not_null avant de lancer les comparaisons audit-helper. Sans clé fiable, les macros basées sur les jointures produisent des résultats trompeurs.
Précision des nombres à virgule flottante. Toutes les comparaisons utilisent l’égalité exacte. Il n’y a pas de tolérance epsilon pour les valeurs à virgule flottante. Si vous comparez des champs calculés avec des décimales, castez d’abord à une précision spécifique :
{{ audit_helper.compare_queries( a_query="SELECT revenue__id, ROUND(revenue__amount, 2) AS revenue__amount FROM " ~ ref('mrt__finance__revenue'), b_query="SELECT revenue__id, ROUND(revenue__amount, 2) AS revenue__amount FROM analytics_db.production.mrt__finance__revenue", primary_key="revenue__id") }}Performance sur les grandes tables. Les macros utilisent FULL OUTER JOIN et UNION ALL en interne, ce qui signifie des scans complets de tables. Sur les grandes tables, cela devient coûteux. Utilisez quick_are_relations_identical pour une vérification rapide initiale, filtrez avec des clauses WHERE pour réduire le périmètre, ou comparez un sous-ensemble borné par date avant de lancer une comparaison complète.
Un modèle à la fois. Contrairement à des outils comme Datafold qui découvrent et comparent automatiquement tous les modèles impactés, audit-helper nécessite une configuration manuelle par modèle. C’est acceptable pour de la validation ciblée, mais fastidieux pour des migrations couvrant des dizaines de modèles.
Compatibilité dbt Cloud. La méthode .print_table() ne fonctionne pas dans l’IDE dbt Cloud. Utilisez {{ log(result, info=true) }} comme solution de contournement.
Quand audit-helper ne suffit pas
audit-helper excelle dans la comparaison ponctuelle : “est-ce que mon nouveau modèle produit le même résultat que l’ancien ?” Pour un monitoring continu, vous avez besoin d’autres outils.
Elementary gère l’observabilité des données : détection d’anomalies, suivi des changements de schéma et monitoring des volumes dans le temps. C’est complémentaire à audit-helper, pas un remplacement.
dbt-expectations fournit plus de 40 tests génériques pour les plages de valeurs, les patterns et les vérifications statistiques. Utilisez-le pour la validation continue de la qualité plutôt que pour des comparaisons ponctuelles.
Datafold Cloud est l’alternative commerciale. Le data-diff open-source n’est plus activement maintenu depuis mai 2024. La force de Datafold est la découverte automatique des modèles à travers l’ensemble de votre DAG, ce qui économise un temps de configuration significatif sur les grandes migrations. Si vous migrez des dizaines de modèles et ne voulez pas configurer manuellement audit-helper pour chacun, Datafold vaut la peine d’être évalué.
Soda adopte une approche basée sur YAML pour les vérifications de qualité des données avec un périmètre plus large que la comparaison de tables. Il peut servir de complément à audit-helper pour les équipes qui souhaitent une validation continue au-delà des fenêtres de migration.
Pour la plupart des équipes, audit-helper couvre bien le workflow de validation. C’est gratuit, maintenu par dbt Labs, et s’intègre naturellement dans votre projet dbt existant. L’approche progressive (schéma, puis comptages, puis lignes, puis colonnes) détecte les problèmes tôt sans brûler des crédits warehouse sur des comparaisons complètes de tables inutiles.