Extraire, charger et orchestrer vos données avec le framework open-source de référence. Infrastructure as Code pour vos pipelines de données.
Meltano est un framework open-source pour les pipelines de données ELT (Extract-Load-Transform). Créé initialement par GitLab, puis devenu un projet indépendant, Meltano gère l'ensemble du cycle de vie de vos données : extraction depuis les sources, chargement dans un data warehouse, et orchestration du pipeline complet (incluant dbt pour les transformations).
Si dbt est le chef cuisinier qui transforme les ingrédients en plats, Meltano est le livreur qui va chercher les ingrédients chez les fournisseurs (APIs, bases de données, fichiers CSV) et les range dans le frigo (data warehouse). Meltano est aussi le manager du restaurant qui coordonne les livraisons et la cuisine pour que tout se passe dans le bon ordre, au bon moment.
| Critère | Meltano | Fivetran | Airbyte |
|---|---|---|---|
| Prix | Gratuit (open-source) | $$$$ (par ligne synchronisée) | Freemium / payant cloud |
| Hébergement | Self-hosted (vous) | Fully managed (eux) | Les deux |
| Connecteurs | 600+ (Singer Hub) | 300+ (propriétaires) | 350+ (open-source) |
| Customisation | ⭐⭐⭐ Totale | ⭐ Limitée | ⭐⭐ Bonne |
| Config as Code | ✅ meltano.yml natif | ❌ UI web | ⚠️ Partiel (Octavia CLI) |
| Orchestration intégrée | ✅ Airflow intégré | ✅ Intégré | ✅ Intégré |
| dbt intégré | ✅ Natif | ⚠️ dbt Cloud séparé | ⚠️ Via Terraform |
| Vendor lock-in | ❌ Aucun | ⚠️ Fort | ⚠️ Modéré (cloud) |
| Idéal pour | Équipes techniques, startups, contrôle total | Équipes non-techniques, enterprise | Compromis technique/facilitée |
| Étape | Outil | Ce qui se passe |
|---|---|---|
| 1. Extract | Meltano (tap-*) | Récupère les données depuis les APIs, BDD, fichiers |
| 2. Load | Meltano (target-*) | Charge les données brutes dans le warehouse |
| 3. Transform | dbt (via Meltano) | Transforme les données brutes en modèles analytiques |
| 4. Orchestrate | Meltano (Airflow) | Planifie et exécute le pipeline automatiquement |
| 5. Visualize | Metabase / Looker / Evidence | Crée des dashboards sur les marts dbt |
Meltano gère l'extraction et le chargement (EL), dbt gère la transformation (T). Ensemble, ils
forment un pipeline ELT complet, 100% open-source, 100% configuration-as-code, et 100%
reproductible. Meltano intègre dbt nativement : vous pouvez exécuter
meltano run tap-postgres target-snowflake dbt-snowflake:run en une seule commande.
Un tap est un connecteur qui extrait des données depuis une source. Il lit les données et les émet dans un format standardisé (Singer).
# Ajouter un tap pour extraire depuis PostgreSQL
$ meltano add extractor tap-postgres
# Ajouter un tap pour l'API GitHub
$ meltano add extractor tap-github
# Ajouter un tap pour des fichiers CSV
$ meltano add extractor tap-csv
Un target est un connecteur qui charge les données dans une destination. Il reçoit le flux de données du tap et l'écrit dans le warehouse.
# Ajouter un target pour charger dans Snowflake
$ meltano add loader target-snowflake
# Charger dans PostgreSQL
$ meltano add loader target-postgres
# Charger dans des fichiers JSON (debug)
$ meltano add loader target-jsonl
C'est le cœur de votre projet Meltano. Toute la configuration est dans ce seul fichier YAML, versionné dans git :
# meltano.yml
version: 1
project_id: mon-projet-data
plugins:
extractors:
- name: tap-postgres
variant: meltanolabs
pip_url: meltanolabs-tap-postgres
config:
host: localhost
port: 5432
database: app_production
user: readonly_user
select:
- public-orders.* # Sélectionner toutes les colonnes
- public-customers.* # de ces tables
- "!public-logs.*" # Exclure cette table
loaders:
- name: target-snowflake
variant: meltanolabs
pip_url: meltanolabs-target-snowflake
config:
account: xy12345.eu-west-1
database: RAW
schema: POSTGRES
warehouse: LOADING_WH
transformers:
- name: dbt-snowflake
pip_url: dbt-core dbt-snowflake
jobs:
- name: daily-sync
tasks:
- tap-postgres target-snowflake dbt-snowflake:run
schedules:
- name: daily-sync-schedule
job: daily-sync
interval: "@daily"
Meltano supporte des environnements distincts (dev, staging, prod) avec des configurations différentes :
# meltano.yml
environments:
- name: dev
config:
plugins:
extractors:
- name: tap-postgres
config:
host: localhost
database: app_dev
- name: prod
config:
plugins:
extractors:
- name: tap-postgres
config:
host: prod-db.company.com
database: app_production
# Exécution :
$ meltano --environment=dev run tap-postgres target-snowflake
$ meltano --environment=prod run tap-postgres target-snowflake
Meltano garde un état (state) entre chaque exécution pour ne récupérer que les nouvelles données (synchronisation incrémentale) :
Première exécution : récupère tout depuis le début.
Exécutions suivantes : récupère seulement les données créées/modifiées depuis la dernière
exécution.
Cet état est stocké localement ou dans un backend configurable (base de données, S3, etc.).
# 1. Initialiser le projet
$ meltano init mon-pipeline && cd mon-pipeline
# 2. Ajouter les plugins
$ meltano add extractor tap-postgres
$ meltano add loader target-snowflake
# 3. Configurer (ou via meltano.yml)
$ meltano config tap-postgres set host db.company.com
$ meltano config tap-postgres set database production
$ meltano config tap-postgres set user readonly
# 4. Sélectionner les tables
$ meltano select tap-postgres "public-users" "*"
$ meltano select tap-postgres "public-orders" "*"
$ meltano select tap-postgres "public-payments" "*"
# 5. Lancer la synchronisation
$ meltano run tap-postgres target-snowflake
# Synchroniser les issues, PRs et commits
$ meltano add extractor tap-github
$ meltano config tap-github set repositories '["org/repo1", "org/repo2"]'
$ meltano config tap-github set auth_token $GITHUB_TOKEN
$ meltano select tap-github "issues" "*"
$ meltano select tap-github "pull_requests" "*"
$ meltano select tap-github "commits" "*"
$ meltano add loader target-postgres
$ meltano run tap-github target-postgres
# meltano.yml - Jobs composés
jobs:
- name: ecommerce-pipeline
tasks:
# Étape 1 : Extraire Shopify
- tap-shopify target-snowflake
# Étape 2 : Extraire Stripe
- tap-stripe target-snowflake
# Étape 3 : Transformer avec dbt
- dbt-snowflake:run
# Étape 4 : Tester avec dbt
- dbt-snowflake:test
schedules:
- name: ecommerce-daily
job: ecommerce-pipeline
interval: "@daily" # Tous les jours à minuit
# Extraire depuis S3 + charger dans BigQuery
$ meltano add extractor tap-s3-csv
$ meltano config tap-s3-csv set bucket my-logs-bucket
$ meltano config tap-s3-csv set prefix "logs/2026/"
$ meltano add loader target-bigquery
$ meltano run tap-s3-csv target-bigquery
# MySQL production → PostgreSQL analytics
$ meltano add extractor tap-mysql
$ meltano add loader target-postgres
# Sync complet puis incrémental
$ meltano run tap-mysql target-postgres
# La 2e exécution ne prend que les deltas
# meltano.yml — Pipeline multi-sources
jobs:
- name: marketing-domain
tasks:
- tap-google-ads target-snowflake
- tap-facebook target-snowflake
- tap-hubspot target-snowflake
- name: product-domain
tasks:
- tap-segment target-snowflake
- tap-mixpanel target-snowflake
- name: finance-domain
tasks:
- tap-stripe target-snowflake
- tap-quickbooks target-snowflake
| Phase | Commande | Ce qui se passe |
|---|---|---|
| 1. Init | meltano init mon-projet |
Crée la structure de projet avec meltano.yml |
| 2. Add Plugins | meltano add extractor/loader |
Installe les connecteurs depuis le Hub Meltano |
| 3. Configure | meltano config |
Configure les credentials et options de chaque plugin |
| 4. Select | meltano select |
Choisit les tables/streams et colonnes à synchroniser |
| 5. Test | meltano run tap target |
Exécute le pipeline en dev pour valider |
| 6. Schedule | meltano schedule add |
Planifie l'exécution automatique (cron ou Airflow) |
| 7. Deploy | Docker / K8s / VM | Déploie le projet avec orchestration en production |
| 8. Monitor | Logs + alertes | Surveille les exécutions, détecte les échecs |
# Initialiser un projet
meltano init mon-projet
# Lister les plugins disponibles
meltano discover extractors
meltano discover loaders
# Ajouter un plugin
meltano add extractor tap-postgres
# Configurer un plugin
meltano config tap-postgres set host localhost
meltano config tap-postgres list # Voir la config
# Sélectionner des tables
meltano select tap-postgres --list --all # Voir tout
meltano select tap-postgres "public-users" "*"
# Exécuter un pipeline
meltano run tap-postgres target-snowflake
# Exécuter avec dbt
meltano run tap-postgres target-snowflake dbt-snowflake:run
# Gérer l'état
meltano state list
meltano state get dev:tap-postgres-to-target-snowflake
# Orchestration
meltano schedule list
meltano invoke airflow scheduler
# Sélectionner des tables spécifiques
$ meltano select tap-postgres "public-orders" "*"
# Exclure des colonnes sensibles
$ meltano select tap-postgres "public-users" "*"
$ meltano select tap-postgres "public-users" "!password"
$ meltano select tap-postgres "public-users" "!ssn"
# Utiliser des wildcards
$ meltano select tap-postgres "public-*" "*" # Toutes les tables du schéma public
# Option 1 : Variables d'environnement
$ export TAP_POSTGRES_PASSWORD=my_secret
# Dans meltano.yml : password: $TAP_POSTGRES_PASSWORD
# Option 2 : Fichier .env (non versionné)
# .env
TAP_POSTGRES_PASSWORD=my_secret
TARGET_SNOWFLAKE_PASSWORD=another_secret
# Option 3 : Vault (avancé)
# Intégration avec HashiCorp Vault, AWS Secrets Manager, etc.
# Ajouter Airflow comme orchestrateur
$ meltano add orchestrator airflow
# Créer un schedule
$ meltano schedule add daily-sync \
--job daily-pipeline \
--interval "@daily"
# Démarrer le scheduler
$ meltano invoke airflow scheduler
# L'UI Airflow est accessible sur http://localhost:8080
Les mappers permettent des transformations entre le tap et le target (avant le chargement) :
# meltano.yml
plugins:
mappers:
- name: meltano-map-transformer
variant: meltano
pip_url: meltano-map-transformer
mappings:
- name: hash-emails
config:
stream_maps:
users:
email: null # Supprimer le champ email
email_domain: "record['email'].split('@')[1]"
__else__: null # Garder les autres champs
# Exécution avec le mapper
$ meltano run tap-postgres hash-emails target-snowflake
| Méthode | Description | Quand l'utiliser |
|---|---|---|
| FULL_TABLE | Recharge la table entière à chaque run | Petites tables de référence, pas de colonne updated_at |
| INCREMENTAL | Ne charge que les lignes modifiées depuis le dernier run | Tables volumineuses avec colonne updated_at fiable |
| LOG_BASED | Utilise le WAL/binlog de la BDD pour le Change Data Capture | Tables très volumineuses, besoin de temps réel |
Singer est le protocole open-source sous-jacent utilisé par tous les connecteurs Meltano. C'est un standard simple : un tap émet des données en JSON sur stdout, un target les lit sur stdin. Ce découplage permet de combiner n'importe quel tap avec n'importe quel target.
| Message Singer | Rôle | Exemple |
|---|---|---|
| SCHEMA | Déclare la structure d'un stream | {"type": "SCHEMA", "stream": "users", "schema": {...}} |
| RECORD | Émet une ligne de données | {"type": "RECORD", "stream": "users", "record": {"id": 1, "name": "Alice"}}
|
| STATE | Sauvegarde le point de reprise | {"type": "STATE", "value": {"bookmarks": {"users": {"last_id": 42}}}} |
Si votre source n'a pas de connecteur existant, le Meltano Singer SDK facilite la création :
# 1. Créer le squelette du tap
$ pip install cookiecutter
$ cookiecutter https://github.com/meltano/sdk \
--directory="cookiecutter/tap-template"
# 2. Implémenter le stream
# tap_myapi/streams.py
from singer_sdk import typing as th
from singer_sdk.streams import RESTStream
class UsersStream(RESTStream):
name = "users"
path = "/api/v1/users"
primary_keys = ["id"]
replication_key = "updated_at"
schema = th.PropertiesList(
th.Property("id", th.IntegerType),
th.Property("name", th.StringType),
th.Property("email", th.StringType),
th.Property("updated_at", th.DateTimeType),
).to_dict()
def get_url_params(self, context, next_page_token):
params = {"page_size": 100}
if next_page_token:
params["page"] = next_page_token
return params
Le Meltano Hub (hub.meltano.com) est le catalogue central de tous les connecteurs disponibles. Chaque connecteur a :
Chaque source de données a son propre pipeline dédié (tap → target). Avantages :
Séparer clairement l'extraction/chargement (Meltano) de la transformation (dbt). Ne jamais transformer pendant le chargement sauf pour des opérations triviales (hashing PII via mappers).
# Architecture des schémas dans le warehouse
RAW.SHOPIFY ← Meltano charge ici (données brutes)
RAW.STRIPE ← Meltano charge ici
ANALYTICS.STG ← dbt staging (nettoyage)
ANALYTICS.INT ← dbt intermediate (logique)
ANALYTICS.MARTS ← dbt marts (tables finales)
# meltano.yml - Deux schedules
schedules:
- name: daily-incremental
job: daily-sync # Incrémental
interval: "@daily"
- name: weekly-full
job: full-refresh # Full table
interval: "0 2 * * 0" # Dimanche 2h
jobs:
- name: full-refresh
tasks:
- tap-postgres target-snowflake --full-refresh
L'incrémental quotidien est rapide mais peut accumuler des dérives. Le full refresh hebdomadaire corrige les écarts.
Pour les grandes organisations avec plusieurs domaines data :
Extraire toutes les tables de la BDD source « au cas où ». Résultat : des coûts warehouse élevés, des pipelines lents, et des données inutiles qui polluent le warehouse.
Mettre les mots de passe et tokens directement dans meltano.yml et les committer dans git. Utilisez toujours des variables d'environnement ou un gestionnaire de secrets.
Utiliser les mappers pour faire des transformations métier complexes. Les mappers sont pour le masquage PII et le renommage basique. La vraie logique métier appartient à dbt.
Recharger 100 millions de lignes chaque jour alors que seules 10 000 ont changé. Utilisez la réplication incrémentale (INCREMENTAL ou LOG_BASED).
Pas de monitoring, pas d'alertes. Vos données sont stale depuis 3 jours et personne ne le sait. Le CEO prend des décisions sur des données obsolètes.
Un job qui fait tap-a + tap-b + tap-c + target + dbt en séquence. Si tap-b échoue, tout s'arrête. Isolez les sources en jobs séparés.
| Méthode | Complexité | Idéal pour |
|---|---|---|
| Docker + Cron | ⭐ Faible | Petites équipes, projets simples |
| Docker + Airflow | ⭐⭐ Moyenne | Pipelines multiples avec dépendances |
| Kubernetes + Airflow | ⭐⭐⭐ Élevée | Grandes équipes, haute disponibilité |
| GitHub Actions + cron | ⭐ Faible | Projets personnels, POCs |
| Critère | Full Refresh | Incremental | CDC (Log-Based) |
|---|---|---|---|
| Complexité | ⭐ Simple | ⭐⭐ Moyenne | ⭐⭐⭐ Élevée |
| Vitesse | Lente (tout recharger) | Rapide (deltas seulement) | Temps réel |
| Coût warehouse | Élevé | Faible | Très faible |
| Captures les deletes | ✅ Oui | ❌ Non | ✅ Oui |
| Prérequis source | Aucun | Colonne updated_at | Access au WAL/binlog |
pip install meltanomeltano init mon-premier-pipelinemeltano add extractor tap-csv (le plus simple pour commencer)meltano add loader target-jsonl (pour voir les données en sortie)meltano run tap-csv target-jsonl