Tu as une équipe data de 2 à 5 personnes, un dump Postgres qui grossit, et des dashboards qui mettent 12 minutes à charger. Tu hésites entre Databricks, Snowflake et un stack plus léger. La bonne nouvelle : tu n’as pas besoin d’un budget à 6 chiffres pour mettre en place une architecture medallion data lakehouse dbt qui tient la route.
Voici comment on a construit un mini-lakehouse médaillon fonctionnel en 3 semaines chez un client B2B, avec S3, DuckDB et dbt. Et surtout : ce qu’on a mis dans chaque couche, fichier par fichier.
Le contexte client et le problème initial
Le client, une PME B2B du secteur logistique, accumulait depuis 4 ans ses données dans un dump Postgres non versionné. Une équipe de 3 data engineers et analystes passait 60% de son temps à extraire des fichiers plats, les nettoyer dans Excel, et les recharger à la main. Les dashboards Metabase mettaient 12 minutes à s’afficher. Le reporting mensuel était un fichier Excel que la DAF attendait 10 jours après la clôture.
Le constat était clair : impossible de scaler avec cette stack. Mais le budget n’autorisait pas un abonnement à 50 000 €/an. L’objectif du project : industrialiser un vrai lakehouse sans exploser la charge mentale de l’équipe data engineering.
Pourquoi DuckDB + dbt + S3 plutôt que Databricks ou Snowflake
Pour une équipe de 2 à 5 personnes, le critère coût vs valeur est déterminant. Databricks et Snowflake sont excellents, mais ils nécessitent des clusters, des administrateurs et un budget qui peut dépasser 20 000 €/an pour un usage modeste.
DuckDB change la donne : c’est un moteur SQL embarqué, open source, qui s’exécute localement ou sur une petite VM. Zero cluster à gérer, zero licence, et il lit nativement les fichiers Parquet sur S3.
dbt apporte le versionning Git, les tests de qualité et les transformations SQL. C’est le T du ELT moderne. Combiné à S3 comme unique stockage pour les 3 couches, l’ensemble forme une stack facile à configurer et à maintenir. Le setup initial nous a pris 2 jours.
Couche bronze : ce qu’on met dedans et ce qu’on laisse brut
La couche bronze est la source de vérité brute. Concrètement, on a exporté le dump Postgres au format Parquet compressé, et on l’a rangé dans un bucket S3 sous ce chemin :
- S3/bronze/orders/ — les commandes brutes, un fichier par snapshot quotidien
- S3/bronze/customers/ — les clients, versionnés par date d’export
- S3/bronze/products/ — le catalogue produit, sans transformation
- S3/bronze/inventory/ — les états de stock, bruts également
Pourquoi ne rien nettoyer à ce stade ? Parce que la couche bronze est immuable. Les données y sont stockées telles qu’elles ont été reçues, sans modification. C’est la garantie de pouvoir rejouer toute l’histoire en cas de besoin. Si une transformation silver produit un résultat inattendu, on sait que le problème vient de la couche de transformation, pas des données sources.
Côté dbt, on a créé des modèles bronze qui se contentent d’un COPY INTO depuis les fichiers S3. Un exemple :
Le modèle bronze_orders.sql déclare la source raw (les fichiers Parquet dans S3/bronze/orders/) et matérialise une table avec les colonnes exactes du dump, en conservant les types originaux (VARCHAR pour les dates, TEXT pour les descriptions). Pas de casting, pas de renommage. La raw data reste brute.
Ce qu’il faut retenir : la couche bronze est une copie conforme de la staging de données sources. Rien de plus. Si tu veux être rigoureux sur la data quality, c’est à la couche suivante que ça se joue.
Couche silver : typage, nettoyage et déduplication
C’est la couche la plus importante du point de vue data engineering. La silver layer applique les transformations nécessaires pour passer de la raw data à des données fiables et réutilisables.
Concrètement, on a créé un modèle dbt par table source, avec les opérations suivantes :
- Casting explicite des types : les dates VARCHAR deviennent des DATE, les montants TEXT passent en DECIMAL(10,2)
- Dédoublonnage via ROW_NUMBER() sur la clé naturelle, partitionné par date de snapshot
- Renommage des colonnes en snake_case cohérent (ex: « CustomerName » → « customer_name »)
- Standardisation des valeurs manquantes et des formats (téléphone, email, adresse)
Voici un extrait du fichier silver_orders.sql :
Le modèle commence par une CTE qui lit les données depuis la source bronze, applique le casting, dédoublonne avec ROW_NUMBER(), et ne conserve que l’enregistrement le plus récent par commande. La matérialisation est configurée en incremental pour éviter de re-traiter tout l’historique à chaque run.
Un point clé qu’on a appris de l’architecture médaillon : en silver, les systèmes sources ne se croisent pas. Chaque table silver est indépendante. Le croisement entre commandes, clients et stocks se fait plus tard, dans la couche gold.
Côté tests de qualité, dbt nous a permis d’intégrer des singular tests directement dans le modèle :
- Test de non-nullité sur les clés primaires (order_id, customer_id)
- Test d’unicité après dédoublonnage
- Test de plage de valeurs (montants strictement positifs)
- Test d’intégrité référentielle via dbt_utils
Ces data quality checks sont exécutés automatiquement à chaque run. Si un test échoue, la transformation s’arrête et on reçoit une notification. C’est ce qui nous a sauvés 2 fois en 3 semaines.
Le piège qu’on a évité : ne pas sur-modéliser la silver layer trop tôt. On aurait pu créer des jointures entre sources dès cette étape. Mais le principe est clair : la silver reste agnostique, la gold apporte la logique métier.
Couche gold : des tables métier prêtes pour les dashboards
La gold layer est la couche de consommation, directement utilisable par le business et les outils de reporting. On a livré 3 tables gold au client :
| Table gold | Description | Usage métier |
|---|---|---|
| gold_revenue_monthly | CA mensuel par client, segment et catégorie produit | Reporting COMEX, suivi commercial |
| gold_top_customers | Top 100 clients par trimestre avec historique | CRM, fidélisation, analyse portefeuille |
| gold_stock_by_category | Stock par catégorie, avec rotation et valorisation | Supply chain, achats, finance |
On a choisi des tables plates plutôt qu’une modélisation en étoile. Pourquoi ? Parce que l’équipe métier voulait pouvoir requêter directement depuis Metabase sans comprendre les jointures. Ces tables gold sont agrégées, prêtes à l’emploi, avec des noms de colonnes explicites.
Pour les performances, on a optimisé les 3 tables gold avec des vues matérialisées DuckDB. Concrètement, au lieu de lire les fichiers Parquet à chaque requête, DuckDB garde les résultats pré-calculés en mémoire. Les requêtes passent de plusieurs minutes à quelques secondes.
La connexion Metabase s’est faite via le driver JDBC DuckDB, en pointant directement sur le fichier .duckdb qui sert de catalogue aux 3 couches. Metabase voit les vues gold comme des tables classiques. L’analytics est immédiatement accessible sans configuration supplémentaire.
Ce que ça a changé : de 12 minutes à 40 secondes
Les résultats parlent d’eux-mêmes. Avant notre intervention, les dashboards Metabase mettaient 12 minutes à charger le reporting mensuel. Après la mise en place du mini-lakehouse médaillon avec DuckDB et dbt, le même dashboard s’affiche en 40 secondes. Soit un gain de 94% sur le temps de chargement.
L’impact sur l’équipe data a été immédiat : plus de fichiers Excel échangés par mail, plus de copier-coller entre le dump Postgres et le reporting. Les data engineers passent désormais leur temps à améliorer les modèles, pas à corriger des cellules Excel.
Le coût total du projet ? Le stockage S3 revient à environ 15 €/mois pour les 3 couches. DuckDB est gratuit (open source). dbt Cloud en forfait équipe coûte environ 100 €/mois. Soit un coût récurrent d’environ 115 €/mois pour un lakehouse complet. Impossible de faire mieux avec un stack propriétaire.
Le ROI estimé pour le client : 4 mois. En intégrant le temps des 2 data engineers (3 semaines à mi-temps) et l’abonnement dbt Cloud, l’investissement est rentabilisé dès que l’équipe économise 2 jours de manipulation de fichiers par mois.
Les 3 erreurs qu’on a évitées (et que tu risques de faire)
On a fait des erreurs, c’est inévitable. Mais en les anticipant, tu peux les éviter complètement.
1. Ne pas versionner les fichiers bronze
Les données brutes sont souvent traitées comme des fichiers de travail qu’on écrase. C’est une erreur. On a mis en place un versionnement simple : chaque snapshot quotidien est stocké avec un timestamp dans le nom du fichier (orders_2026-07-15.parquet). Comme ça, on peut revenir en arrière si nécessaire. La bronze layer se comporte comme du code : on ne l’écrase pas, on l’ajoute.
2. Vouloir tout mettre dans une seule couche gold trop tôt
On a failli créer une table gold unique avec 50 colonnes mélangeant CA, stocks et clients. Une table fourre-tout qui aurait été impossible à maintenir. On a finalement gardé 3 tables distinctes, chacune avec un objectif métier clair. C’est le pattern qu’on recommande : 1 table gold = 1 cas d’usage business, pas plus.
3. Oublier les tests de qualité sur la couche silver avant d’alimenter gold
Si les tests de data quality échouent en silver, les données en gold sont fausses. Point. On a configuré dbt pour bloquer l’exécution des modèles gold tant que les tests silver ne passent pas. C’est un filet de sécurité indispensable, surtout quand on charge les données en mode incremental.
Cette approche est directement inspirée du principe de QA des données avant mise en production, qu’on retrouve aussi bien en data engineering qu’en SEO programmatique : vérifier avant d’exposer. Les tests automatiques sont le meilleur investissement temps/valeur pour une petite équipe.
Si tu veux suivre l’aventure, abonne-toi !