ServicesÀ proposNotesContact Me contacter →
EN FR
Note

Processus de migration dbt vers Dataform

Le processus étape par étape pour migrer un projet dbt vers Dataform — auditer l'existant, exécuter l'outil automatisé, convertir les macros en includes JavaScript, recréer les tests comme assertions et configurer l'orchestration.

Planté
dbtdataformbigquerydata engineeringdata modeling

La migration de dbt vers Dataform suit une séquence prévisible, mais l’effort n’est pas uniformément distribué. Les étapes 1 et 2 — l’audit et l’exécution de l’outil automatisé — sont rapides. L’étape 3 — la conversion des macros en includes JavaScript — est là où les projets ralentissent ou s’arrêtent complètement. Les étapes 4 à 6 sont mécaniques mais requièrent une exécution soigneuse.

Avant de commencer, vérifiez que la migration a du sens pour votre projet. Vérifiez si vous dépendez de fonctionnalités sans équivalent Dataform : snapshots, utilisation intensive de packages, stratégie incrémentale microbatch ou Slim CI. Si l’un de ces éléments s’applique significativement, la migration pourrait coûter plus qu’elle ne rapporte.

Étape 1 : Auditer votre projet

Avant de toucher le moindre code, comprenez exactement ce que vous avez. Les surprises pendant la migration sont coûteuses.

Terminal window
# Compter les modèles par type
find models -name "*.sql" | wc -l
# Lister les fichiers de macros
find macros -name "*.sql"
# Vérifier les dépendances dans packages.yml
cat packages.yml

Documentez :

  • Nombre total de modèles — détermine l’estimation globale de l’effort
  • Nombre de macros personnalisées — le principal facteur de coût ; budgétisez 40-60 % du temps total de migration si vous avez 20+ macros personnalisées
  • Packages externes utilisés — chaque dépendance de package est une tâche de conversion ; dbt_utils est gérable, dbt_expectations est significatif, les packages spécifiques à une source (dbt_ga4, dbt_shopify) sont substantiels
  • Stratégies de modèles incrémentaux — un updated_at > last_run standard est de faible complexité ; tout ce qui implique microbatch ou une logique de merge complexe nécessite un travail manuel
  • Tables snapshot — chaque snapshot nécessite une implémentation SCD2 manuelle dans Dataform
  • Configuration CI/CD — si vous dépendez du Slim CI de dbt Cloud, budgétisez du temps pour reconstruire l’automatisation CI

Cet audit détermine si vous regardez un projet de 1-2 semaines ou de 2-3 mois. Ne le sautez pas.

Étape 2 : Exécuter l’outil de migration automatisé

L’outil ra_dbt_to_dataform gère les parties mécaniques de la conversion :

Terminal window
# Cloner l'outil de migration
git clone https://github.com/rittmananalytics/ra_dbt_to_dataform.git
cd ra_dbt_to_dataform
# Installer les dépendances
pip install -r requirements.txt
# Exécuter la migration
python migrate.py --dbt-project /path/to/dbt --output /path/to/dataform

L’outil convertit :

  • Les références de modèles — {{ ref('model') }} vers ${ref("model")}
  • Les déclarations de sources — génère des fichiers de déclaration Dataform depuis votre sources.yml
  • Les fonctions dbt_utils courantes — surrogate_key, star et un sous-ensemble d’utilitaires de dates
  • La logique incrémentale basique — {% if is_incremental() %} vers ${when(incremental(), ...)}

Il ne gère pas :

  • Les seeds (les fichiers CSV deviennent des tables BigQuery + fichiers de déclaration, nécessitant un travail manuel)
  • Les snapshots (pas d’équivalent ; implémentation SCD2 manuelle requise)
  • Les macros personnalisées complexes (l’outil utilise GPT-4 pour la traduction de macros — un aveu que la conversion Jinja vers JavaScript est trop irrégulière pour un outillage déterministe)
  • Les définitions de couche sémantique

Traitez la sortie de l’outil comme un point de départ, pas un produit fini. Prévoyez de revoir chaque fichier converti. L’outil gère la conversion structurelle de manière fiable mais peut produire des fichiers d’includes JavaScript qui compilent mais se comportent différemment de l’original Jinja. La précision numérique, la gestion des nulls et la coercition de chaînes se comportent différemment entre les deux systèmes de template — voir JavaScript vs Jinja en analytics engineering pour les détails.

Étape 3 : Convertir les macros en includes JavaScript

Cette étape domine le calendrier de migration pour tout projet non trivial. Chaque macro Jinja personnalisée devient une fonction JavaScript dans le répertoire includes/ de Dataform.

Le pattern de conversion est cohérent pour les fonctions utilitaires :

Macro dbt (macros/generate_surrogate_key.sql) :

{% macro generate_surrogate_key(field_list) %}
TO_HEX(MD5(CONCAT(
{% for field in field_list %}
COALESCE(CAST({{ field }} AS STRING), '')
{% if not loop.last %}, '|', {% endif %}
{% endfor %}
)))
{% endmacro %}

JavaScript Dataform (includes/utils.js) :

function generateSurrogateKey(fields) {
const fieldExpressions = fields
.map(f => `COALESCE(CAST(${f} AS STRING), '')`)
.join(", '|', ");
return `TO_HEX(MD5(CONCAT(${fieldExpressions})))`;
}
module.exports = { generateSurrogateKey };

Utilisation dans SQLX :

config { type: "table" }
js {
const { generateSurrogateKey } = require("includes/utils");
}
SELECT
${generateSurrogateKey(["customer_id", "order__created_at"])} AS surrogate_key,
customer_id,
order__created_at,
order__total_usd
FROM ${ref("base__source__orders")}

Les fonctions utilitaires simples comme celle-ci se traduisent proprement. Les cas problématiques :

  • Macros qui appellent run_query() — Les macros Jinja peuvent exécuter du SQL à la compilation. Dataform n’a pas d’équivalent ; la logique doit être restructurée.
  • Macros utilisant des objets de contexte dbtthis, target, model, graph n’ont pas d’équivalents directs dans Dataform.
  • Macros de packages — Toute macro de dbt_utils, dbt_expectations ou d’autres packages doit être réimplémentée ou remplacée par des assertions personnalisées.
  • Méthodes JavaScript sans équivalent Jinja — Le problème inverse : Jinja a des boucles for, des blocs if, set et namespace, mais pas de .map(), .filter() ou .reduce(). Si vos macros utilisent une manipulation complexe de tableaux ou d’objets, vous réécrivez des algorithmes, pas seulement de la syntaxe.

Budgétisez de manière conservatrice. Un projet avec 20 macros personnalisées et plusieurs dépendances de packages peut consacrer l’essentiel de son calendrier de migration à cette seule étape.

Étape 4 : Recréer les tests comme assertions

Les assertions intégrées de Dataform gèrent trois cas en ligne dans le bloc de configuration :

config {
type: "table",
assertions: {
uniqueKey: ["order_id"],
nonNull: ["order_id", "customer_id", "order__created_at"],
rowConditions: [
"order__total_usd >= 0",
"order__created_at <= CURRENT_DATE()"
]
}
}

Cela couvre l’unicité, les nulls et les conditions simples au niveau de la ligne — l’équivalent des tests unique, not_null et expression_is_true de dbt.

Pour tout ce qui va au-delà de ces trois types — intégrité référentielle, validation de pattern regex, vérifications de distribution, comparaisons entre tables — vous avez besoin de fichiers d’assertion séparés :

-- definitions/assertions/assert_valid_customer_emails.sqlx
config { type: "assertion" }
SELECT
customer_id,
customer__email
FROM ${ref("mrt__marketing__customers")}
WHERE customer__email NOT LIKE '%@%.%'
OR customer__email IS NULL

Si la requête retourne des lignes, l’assertion échoue. C’est l’équivalent Dataform des tests singuliers de dbt. Ça fonctionne, mais chaque test non standard est un nouveau fichier avec du SQL écrit à la main. Il n’y a pas de paramétrage ni de bibliothèque de tests disponible.

Les tests de dbt_expectations qui nécessitent des fichiers d’assertion personnalisés incluent :

  • Vérifications de distribution (moyenne/médiane dans une plage)
  • Validation de pattern regex
  • Comparaisons entre tables (intégrité référentielle)
  • Vérifications de fraîcheur au-delà de ce qui est dans le bloc assertions

Voir Limitations des tests Dataform pour la portée complète de ce que vous perdez côté tests.

Étape 5 : Configurer l’orchestration

dbt Cloud a une planification intégrée. Dataform non — ou plutôt, il a une planification basique via les configurations de workflow, mais déclencher depuis des événements git, construire des environnements PR et exécuter selon des planifications personnalisées nécessite une orchestration externe.

Deux options natives à GCP :

Cloud Composer (Airflow géré) — Pour les pipelines complexes avec des branchements conditionnels, des dépendances entre systèmes ou une logique de retry avancée :

from airflow.providers.google.cloud.operators.dataform import (
DataformCreateCompilationResultOperator,
DataformCreateWorkflowInvocationOperator,
)
compile_task = DataformCreateCompilationResultOperator(
task_id="compile",
project_id="my-project",
region="us-central1",
repository_id="my-repo",
)
run_task = DataformCreateWorkflowInvocationOperator(
task_id="run",
project_id="my-project",
region="us-central1",
repository_id="my-repo",
compilation_result="{{ task_instance.xcom_pull('compile') }}",
)

Composer coûte minimum 300-400 $/mois. Voir Coût et capacités de Cloud Composer pour savoir quand ce coût est justifié.

Cloud Scheduler + Cloud Workflows — Pour des besoins de planification plus simples sans la surcharge de Composer :

main:
steps:
- compile:
call: http.post
args:
url: https://dataform.googleapis.com/v1beta1/projects/PROJECT/locations/REGION/repositories/REPO/compilationResults
auth:
type: OAuth2
- run:
call: http.post
args:
url: https://dataform.googleapis.com/v1beta1/projects/PROJECT/locations/REGION/repositories/REPO/workflowInvocations

Cloud Workflows coûte 0,01 $ pour 1 000 étapes — essentiellement gratuit pour les pipelines de transformation typiques. Cloud Scheduler gère le déclencheur cron. Cette combinaison couvre la plupart des besoins de planification Dataform sans le coût fixe de Composer.

Si vous avez besoin d’un comportement similaire au CI (construire uniquement les modèles modifiés sur les PRs), vous aurez besoin de GitHub Actions ou Cloud Build appelant l’API REST Dataform. Budgétisez du temps supplémentaire pour cette infrastructure.

Étape 6 : Exécution parallèle et validation

Ne basculez pas immédiatement. Faites tourner les deux systèmes simultanément jusqu’à ce que vous soyez certain que les sorties correspondent.

  1. Déployez le projet Dataform dans un dataset séparé (par ex., analytics_dataform)
  2. Planifiez dbt et Dataform pour s’exécuter selon la même cadence
  3. Comparez les sorties via des comptages de lignes et des checksums

Une requête de validation pratique :

SELECT
'dbt' AS source,
COUNT(*) AS row_count,
FARM_FINGERPRINT(TO_JSON_STRING(ARRAY_AGG(t ORDER BY order_id))) AS checksum
FROM `analytics.mrt__sales__orders` t
UNION ALL
SELECT
'dataform' AS source,
COUNT(*) AS row_count,
FARM_FINGERPRINT(TO_JSON_STRING(ARRAY_AGG(t ORDER BY order_id))) AS checksum
FROM `analytics_dataform.mrt__sales__orders` t
  1. Validez les dashboards en aval par rapport aux deux sources
  2. Surveillez pendant 2-4 semaines avant de désactiver dbt

La note Patterns de validation de migration dbt couvre cette étape en profondeur — niveaux de requêtes de comparaison (comptages de lignes, agrégats, requêtes EXCEPT au niveau ligne), tests de régression de pipelines ML et stratégie de bascule progressive. Lisez-la avant de déclarer la migration terminée.

Les surprises les plus courantes lors de l’exécution parallèle : l’état des modèles incrémentaux ne se transfère pas (la première exécution Dataform doit être un full refresh sur tout modèle incrémental, ce qui peut être coûteux pour les grandes tables), et des différences subtiles de comportement entre l’arithmétique JavaScript et Jinja qui produisent des résultats légèrement différents sur les champs calculés.