dbt : fondamentaux

Résumé

dbt (data build tool) transforme des données déjà chargées dans un entrepôt : c’est l’approche ELT (charger d’abord, transformer ensuite, dans l’entrepôt lui-même), à l’inverse de l’ETL qui transforme avant de charger. Chaque modèle est un SELECT versionné. dbt s’occupe de créer la table ou la vue, de déterminer l’ordre d’exécution, d’exécuter les tests et de générer la documentation.

Prérequis

SQL, et la distinction entre une table, une vue et une table matérialisée. Glossaire : Glossaire data engineering (DAG, Jinja, ELT).

Un modèle minimal

Un modèle est un fichier .sql qui contient un SELECT. ref() désigne un autre modèle, source() une table brute.

-- models/staging/shop/stg_orders.sql
select
    id          as order_id,
    customer_id,
    cast(created_at as timestamp) as ordered_at,
    amount_cents / 100.0          as amount
from {{ source('shop', 'orders') }}
-- models/marts/fct_daily_revenue.sql
select
    date(ordered_at) as order_date,
    sum(amount)      as revenue
from {{ ref('stg_orders') }}
group by 1

Parce que le second modèle appelle ref('stg_orders'), dbt sait qu’il doit construire le premier avant. L’ensemble de ces dépendances forme un DAG (graphe orienté sans cycle).

La source brute et les tests se déclarent dans un fichier .yml à côté :

sources:
  - name: shop
    tables:
      - name: orders
 
models:
  - name: stg_orders
    columns:
      - name: order_id
        data_tests:
          - unique
          - not_null

La clé data_tests est valable à partir de dbt 1.8 ; les versions précédentes utilisent tests.

Lancer le projet

CommandeEffet
dbt runConstruit les modèles dans l’ordre du DAG
dbt testExécute les tests
dbt buildConstruit les modèles, exécute les tests, les snapshots et les seeds, dans l’ordre du DAG
dbt snapshotCapture l’état des tables suivies par un snapshot

Concepts clés

ConceptRôle
source()Désigne une table brute chargée hors dbt, déclarée dans un .yml
ref()Désigne un autre modèle et construit le DAG
MatérialisationLa forme physique du modèle : voir ci-dessous
TestAssertion déclarative sur un modèle (not_null, unique, relationships, ou test SQL personnalisé). Sévérité error (échoue le run) ou warn
SnapshotCapture l’état d’une table à chaque exécution de dbt snapshot, pour reconstituer un historique de type SCD 2
MacroFonction écrite en Jinja (langage de gabarit) qui génère du SQL réutilisable

Matérialisations

TypeCe que dbt construitCoût principal
viewUne vue, recalculée à chaque lectureCalcul à chaque lecture
tableUne table recréée à chaque runCalcul à chaque run, lecture rapide
incrementalUne table où seules les nouvelles lignes sont ajoutées ou fusionnéesCalcul réduit, logique de fenêtre à maîtriser
ephemeralAucun objet : le modèle est inséré en CTE dans ses consommateursAucun stockage
materialized_viewUne vue matérialisée gérée par l’entrepôt (selon l’adapter)Dépend du moteur

Un modèle incrémental ne traite que les nouvelles lignes après le premier run :

{{ config(materialized='incremental', unique_key='order_id') }}
 
select order_id, customer_id, ordered_at, amount
from {{ ref('stg_orders') }}
{% if is_incremental() %}
where ordered_at > (select max(ordered_at) from {{ this }})
{% endif %}

is_incremental() est faux au premier run (la table est créée en entier) et vrai ensuite ; {{ this }} désigne la table déjà construite. Ce filtre ignore les lignes en retard : c’est la limite que la note dbt Incremental Models vs Snowflake Dynamic Tables détaille.

La stratégie microbatch des modèles incrémentaux (traitement par tranches de temps) existe dans les versions récentes de dbt ; la disponibilité dépend de la version et de l’adapter. Voir dbt Incremental Models vs Snowflake Dynamic Tables.

Les contraintes ne sont pas toujours appliquées

Sur BigQuery, déclarer une clé primaire ou étrangère n’impose rien : ces contraintes sont informatives. Seul un test dbt vérifie réellement l’unicité.

Organisation en couches

La convention dbt recommande trois couches. Elle ressemble au flux décrit dans Staging, ODS, DWH et architecture médaillon sans lui être identique.

CoucheRôleMatérialisation courante
staging (stg_)Un modèle par source brute : renommage, cast de types, aucune jointureview
intermediate (int_)Jointures et logique métier partagées entre plusieurs martsview ou ephemeral
marts (fct_, dim_)Modèles finaux pour la BI, faits et dimensionstable ou incremental

Le coût d'une vue

Une vue n’occupe pas de stockage, mais son calcul est refait à chaque lecture. Sur BigQuery, interroger une vue de staging relit et refacture les tables sous-jacentes. En staging, c’est acceptable quand le volume est petit ou que la vue n’est lue que par les modèles suivants lors d’un run. Si une vue de staging est lue souvent par des outils externes, une table devient plus économique.

Exemple d’arborescence

Un projet organisé en couches :

models/
├── staging/          view
│   ├── shop/
│   ├── payments/
│   └── reference/
├── intermediate/     view
└── marts/            table
    ├── finance/
    └── product/

Choix courants :

  • Staging et intermediate en view quand les volumes sont modestes : le recalcul à la lecture reste peu coûteux.
  • Marts en table : les modèles finaux sont recalculés à chaque run pour des lectures rapides côté BI.
  • Monitoring dans le même projet : voir dbt_bigquery_monitoring pour surveiller le coût du projet.

Notes liées