La recommandation conventionnelle de matérialisation dbt est : vues pour la couche base/staging, ephemeral pour l’intermédiaire, et tables pour les marts. L’argument ici est que choisir les tables par défaut à chaque couche produit des projets plus déboguables, stables et maintenables. Les seules exceptions sont incrémental (quand le volume l’exige) et vue (quand les données doivent être fraîches à la minute).
La distribution conventionnelle par couche :
| Couche | Conventionnel | Justification |
|---|---|---|
| Base/Staging | view | Données fraîches, économise le stockage |
| Intermédiaire | ephemeral | Juste des CTE, pas de vraies tables |
| Marts | table | Performance pour les utilisateurs finaux |
L’Argument pour les Tables Partout
Le débogage nécessite des résultats intermédiaires
Quand quelque chose casse en production — et ça arrivera — vous devez pouvoir interroger les résultats intermédiaires. Avec les modèles ephemeral, ces résultats n’existent tout simplement pas dans votre entrepôt. Vous ne pouvez pas exécuter SELECT * FROM int__session LIMIT 100 pour vérifier votre logique de sessionisation. Vous ne pouvez pas compter les lignes dans base__ga4__event pour vérifier que le déboîtement a fonctionné. Vous volez à l’aveugle à travers une pile de CTE compilés en Jinja, en essayant de reconstituer ce qui s’est passé.
Les tables vous donnent un point de contrôle à chaque couche. Quand mrt__marketing__campaign_performance semble erroné, vous pouvez interroger int__session, puis int__session__session_lj_conversion, puis base__ga4__event. Vous tracez le problème jusqu’à sa source en minutes plutôt qu’en heures.
Les modèles ephemeral optimisent la simplicité à la compilation au détriment de la déboguabilité à l’exécution. C’est un mauvais compromis en production.
Les vues propagent instantanément les ruptures de schéma
Les vues sont réévaluées à chaque requête. Si une source upstream ajoute, supprime ou renomme une colonne, chaque vue downstream se casse immédiatement — au moment de la requête, devant vos utilisateurs de dashboard.
Les tables font tampon. Votre source change son schéma. Votre pipeline s’exécute, votre modèle base échoue à construire (un échec contrôlé que vous pouvez investiguer), et vos tables contiennent toujours les données d’hier. Le dashboard fonctionne toujours. Vous avez le temps de corriger le problème avant qu’il se propage. C’est la différence entre un incident pipeline et un incident données.
Le stockage est bon marché par rapport au calcul
La justification courante des vues est le coût : éviter le stockage inutile. Sur les entrepôts cloud modernes, les coûts de stockage sont négligeables par rapport au calcul.
BigQuery facture 0,02 $ par Go par mois pour le stockage actif. Une table avec 10 millions de lignes coûte peut-être 0,50 $ par mois à stocker. Un modèle intermédiaire avec un an de données de session — 500 millions de lignes, disons — coûte peut-être 3 $ par mois.
Les vues recalculent à chaque requête. Chaque rechargement de dashboard, chaque requête d’analyste, et chaque construction de modèle downstream scanne toute la chaîne upstream. Une seule requête BigQuery scannant 500 Go coûte 2,50 $. Lancée dix fois par jour dans un dashboard, le coût de calcul est de 750 $ par mois — contre un coût de stockage mensuel de 3 $ pour la table équivalente.
Une stratégie unique est plus facile à enseigner et maintenir
La cohérence réduit la charge cognitive. Avec les tables comme défaut, il n’y a qu’une règle : tout est une table, incrémental si grand, vue si vraiment temps réel. Les cas d’exception sont évidents parce qu’ils sont des exceptions. Les nouveaux membres d’équipe s’intègrent avec un modèle mental plus simple, et les conventions de dbt_project.yml sont simples.
Quand Remplacer la Valeur par Défaut
La valeur par défaut “tables partout” a deux exceptions légitimes.
Incrémental quand le volume l’exige
Passez à la matérialisation incrémentale quand :
- Les tables dépassent des millions de lignes
- Les appends quotidiens sont le pattern dominant
- Le rafraîchissement complet prend plus de 5 minutes
Pour les analytics marketing spécifiquement : les tables d’événements GA4 sont le candidat principal (incrémental avec un lookback de 3 jours pour les événements tardifs). Les extracts quotidiens des plateformes publicitaires en sont un autre (incrémental par date, avec un lookback de 30 jours pour les mises à jour de la fenêtre d’attribution).
Le seuil est important. Les modèles incrémentaux sont nettement plus complexes — ils nécessitent une réflexion approfondie sur votre stratégie incrémentale, la gestion des données tardives, et les cas limites à la frontière entre rafraîchissement complet et exécutions incrémentales. N’ajoutez pas cette complexité tant que la matérialisation en table n’est pas vraiment un goulot d’étranglement. Pour la plupart des tables de moins de 10 millions de lignes, le coût de rafraîchissement complet est négligeable.
Vues quand les données doivent vraiment être fraîches à la minute
Certains cas d’usage nécessitent des données en direct : détection de fraude alimentant un moteur de décision temps réel, niveaux de stock pour une plateforme e-commerce, performances de campagne lors d’un lancement à forts enjeux. Pour ceux-ci, le coût de calcul au moment de la requête est un compromis acceptable pour la fraîcheur.
La plupart des cas d’usage analytics tolèrent une latence horaire ou quotidienne. Avant d’ajouter une vue pour la “fraîcheur”, vérifiez si quelqu’un a réellement signalé des données périmées et à quelle granularité il en a besoin de manière actuelle.
Configuration dans dbt_project.yml
Définissez le défaut au niveau du projet et ne remplacez que là où c’est nécessaire :
models: my_project: +materialized: table # Tout mettre en table par défaut base: +schema: base ga4: +materialized: incremental # Fort volume +incremental_strategy: insert_overwrite intermediate: +schema: intermediate marts: +schema: martsCette configuration est déclarative et révisable. Quiconque la lit peut voir que les modèles base GA4 sont incrémentaux pour des raisons de volume, et tout le reste est une table. L’exception est explicite ; le défaut est implicite.
Sur les Modèles Ephemeral
Les modèles ephemeral sont inlinés comme CTE dans chaque modèle downstream qui les référence. Un modèle ephemeral référencé par trois marts aboutit à ce CTE inliné dans trois requêtes SQL séparées — la même transformation s’exécute trois fois. Une table s’exécute une fois, stocke le résultat, et est référencée par les trois marts. Aucun calcul redondant, interrogeable, cohérent. La seule raison légitime d’utiliser ephemeral est d’éviter les limites de comptage d’objets de base de données, ce qui est une contrainte d’entrepôt, pas un choix de modélisation.
Sur la Fraîcheur des Vues
Les vues se réévaluent à chaque requête, ce qui signifie que chaque changement de schéma source les casse au moment de la requête, que les requêtes de dashboard sont lentes quand les tables upstream sont grandes, et que la logique intermédiaire recalcule depuis zéro à chaque requête. Les tables donnent le contrôle sur le moment où les données se rafraîchissent — au moment de l’exécution du pipeline, pas à l’ouverture du dashboard. Cette prévisibilité est plus précieuse qu’une amélioration marginale de fraîcheur dans la plupart des cas d’usage analytics.
Résumé
Les vues et les modèles ephemeral optimisent pour éviter le recalcul. Les tables optimisent pour la fiabilité : sorties interrogeables, déboguables, stables. L’exception incrémentale est une optimisation des performances appliquée quand les rafraîchissements en table deviennent vraiment coûteux. L’exception vue est une optimisation de fraîcheur appliquée uniquement quand des données temps réel sont requises. Tout le reste : table.