Guide pratique : mettre en place ETL sur Dolibarr sans casser l’existant

Version : 2025 – destiné aux administrateurs système, aux développeurs et aux chefs de projet qui utilisent Dolibarr en production.


1. Pourquoi un ETL sur Dolibarr ?

Besoin Solution ETL Bénéfice attendu
Exploitation des données Extraction des tables Banque, Facture, Produit, Client, etc. Rapports historisés, analyses de ventes, tableaux de bord BI
Migration ou synchronisation Consolidation vers un entrepôt de données (Snowflake, PostgreSQL, MySQL, etc.) Uniformisation des sources, réduction de la duplication
Archivage et conformité Copie des enregistrements dans un bucket S3/Blob Conformité RGPD, rétention légale
Intégration avec des outils tiers Chargement dans un data‑lake ou un moteur de recherche (Elasticsearch) Recherche full‑text, scoring de leads, etc.

Le défi principal : ajouter ce processus sans impacter la disponibilité et la performance du ERP de production.


2. Principes de conception « sans casser l’existant »

Principe Description Implémentation concrète
Isolation Le flux ETL doit être décorrélé du core Dolibarr. Utiliser des schémas de tables en lecture seule (read‑only view) ou des réplicas de la base.
Idempotence Rejouer le même job ne doit pas créer de doublons. Design de la charge incrémentale (timestamp updated_at, last_update).
Non‑bloquant Pas de lock durable sur les tables de production. Scans de petites sections, utilisation de READ UNCOMMITTED ou de snapshots (MySQL 8+).
Retry & Resilience Gérer les pannes réseau ou serveur sans perte de données. Bibliothèques retry (ex. tenacity en Python), logs détaillés.
Observabilité Suivi temps réel des volumes, erreurs, latence. Métriques Prometheus + Grafana, alertes Slack/Email.
Versionning & Tests Tous les scripts sont versionnés (Git) et testés en environnement sandbox. CI/CD (GitHub Actions, GitLab CI) avec jeux de données de test.


3. Architecture proposée (schéma simplifié)

+-------------------+      +-------------------+      +---------------------+
| Dolibarr (Prod) | ---> | Extraction | ---> | Staging Area |
| (MySQL/PSQL) | | (logical dump) | | (Parquet/CSV) |
+-------------------+ +-------------------+ +---------------------+
|
v
+--------------------+
| Transformation |
| (SQL / Python) |
+--------------------+
|
v
+--------------------+
| Chargement final |
| (Data Warehouse |
| / Data Lake) |
+--------------------+

Points clés

  • Extraction → utilisation d’un snapshot (ex. mysqldump --single-transaction) ou d’un reader via les API REST de Dolibarr (si le module d’API est activé).
  • Staging → stockage temporaire dans un répertoire /etl/staging/ ou dans un bucket S3. Les fichiers sont immutables (naming yyyyMMdd_HHmmss_<table>.parquet).
  • Transformation → scripts Python (pandas/SQLAlchemy) ou SQL stored procedures dans le DW.
  • ChargementCOPY vers Snowflake/Redshift, INSERT bulk vers PostgreSQL, ou INSERT INTO … SELECT … vers un entrepôt analytique. —

4. Étapes détaillées pour un premier pilote (exemple: sauvegarde des Factures)

Objectif : Extraire les factures created/updated depuis les 24 h précédentes, les transformer en format parquet et les charger dans un entrepôt PostgreSQL dwh.billing.

4.1. Pré‑requis

Élément Version / Note
Dolibarr 22.x ou supérieur (API disponible)
DBMS MySQL 8 ou PostgreSQL 13
Outils Python pandas, sqlalchemy, psycopg2-binary, pymysql, pyarrow
Croissance Crontab ou Scheduler (Airflow, Prefect)
Accès réseau IP autorisée du serveur ETL vers le serveur DB cible

4.2. Extraction (read‑only)

-- 1. Créez une vue READ‑ONLY (MySQL example)
CREATE OR REPLACE VIEW v_facture_incr AS
SELECT
f.rowid,
f.facnumero,
f.fadate,
f.total_ht,
f.status,
f.date_modif -- champ datetime « updated_at »
FROM llx_facture f
WHERE f.date_modif >= NOW() - INTERVAL 1 DAY;

# extract_factures.py
import pandas as pd
from sqlalchemy import create_engine
# connexion read‑only (MySQL)
engine_src = create_engine(
"mysql+pymysql://etl_user:secure_pwd@db-prod.dolibarr.local:3306/dolibarr",
isolation_level="READ UNCOMMITTED"
)
sql = """
SELECT rowid, facnumero, fadate, total_ht, status, date_modifFROM v_facture_incr
WHERE date_modif >= %(start)s
"""
df = pd.read_sql(
sql,
con=engine_src,
params={"start": (pd.Timestamp.utcnow() - pd.Timedelta(days=1)).strftime("%Y-%m-%d %H:%M:%S")}
)
df.to_parquet("/etl/staging/factures_{{ts}}.parquet", index=False)

Pas de lock durable sur la table llx_facture grâce à READ UNCOMMITTED et à la fenêtre temporelle très courte.

4.3. Transformation

# transform_factures.py
import pyarrow.parquet as pq
import pandas as pd
def clean(df: pd.DataFrame) -> pd.DataFrame:
# Normaliser les types et supprimer les doublons éventuels
df["total_ht"] = df["total_ht"].astype(float)
df["status"] = df["status"].astype("category")
df = df.drop_duplicates(subset=["rowid"])
return df
raw = pd.read_parquet("/etl/staging/factures_*.parquet")
clean_df = clean(raw)
# Ajout d’une clé de chargement et d’une partition par date
clean_df["etl_ts"] = pd.Timestamp.utcnow()
clean_df["load_date"] = clean_df["etl_ts"].dt.date
clean_df["year_month"] = clean_df["etl_ts"].dt.to_period("M")
clean_df.to_parquet(
f"/etl/staging/factures_clean_{clean_df['load_date'].iloc[0]}.parquet",
index=False,
compression="snappy"
)

4.4. Chargement (bulk load) dans le DW « `sql

— 4. Chargement dans PostgreSQL (exemple)
CREATE TABLE IF NOT EXISTS dwh.billing_fact (
rowid bigint PRIMARY KEY,
facnumero varchar(30),
fadate date,
total_ht numeric(15,2),
status varchar(20),
date_modif timestamp,
etl_ts timestamp,
load_date date,
year_month varchar(7)
);

COPY dwh.billing_fact
FROM ‘/etl/staging/factures_clean_20251102.parquet’
WITH (FORMAT parquet);


> **Astuce** : Si votre DW accepte le format `parquet` natif (ex. Snowflake `COPY INTO ... FROM @stage/file.parquet`), vous n’avez pas besoin de passer par CSV intermédiaire.
### 4.5. Nettoyage & archivage
* Déplacer les fichiers `staging/*.parquet` vers `/etl/archive/yyyyMMdd/` après validation (checksum MD5).
* Supprimer les anciens fichiers après 7 jours (politique de rétention).
---
## 5. Intégration dans un scheduler (exemple : Apache Airflow)
```python
# dag_etl_dolibarr_factures.pyfrom airflow import DAG
from airflow.operators.python import PythonOperator
from datetime import datetime, timedelta
default_args = {
"owner": "etl-team",
"retries": 2,
"retry_delay": timedelta(minutes=5),
"depends_on_past": False,
}
dag = DAG(
"dolibarr_etl_factures",
default_args=default_args,
description="ETL quotidien des factures Dolibarr",
schedule_interval="0 2 * * *", # 02h00 chaque jour
start_date=datetime(2025, 1, 1),
catchup=False,
)
extract_task = PythonOperator(
task_id="extract_factures",
python_callable=lambda: __import__("extract_factures").extract_factures.main()
)
transform_task = PythonOperator(
task_id="transform_factures",
python_callable=lambda: __import__("transform_factures").transform_factures.main()
)
load_task = PythonOperator(
task_id="load_into_dw",
python_callable=lambda: __import__("load_factures").load_factures.main()
)
extract_task >> transform_task >> load_task

Airflow assure le retry, la visibilité des logs, et le déclenchement à heure fixe, tout en gardant l’isolation du moteur de production.


6. Bonnes pratiques & check‑list de non‑impact

✅ Action Pourquoi Comment vérifier
Planifier pendant les créneaux de basse charge Réduction de la contrainte I/O sur la base source Monitorer SHOW ENGINE INNODB STATUS ou les métriques CPU/DB pendant 1‑2 jours de pilotage
Utiliser des vues ou des snapshots Pas de lock logique sur les tables de production SELECT COUNT(*) FROM llx_facture WHERE updated_at > NOW() - INTERVAL 1 DAY doit rester constant
Limiter la taille du « slice » Éviter un flood de scans complets
Mettre en place un replay‑proof** (ex. ON CONFLICT DO NOTHING) Garantir l’idempotence du chargement Tester plusieurs exécutions du même ETL sur un jeu de données fixe
Sauvegarder les logs de chaque étape Déboguer facilement en cas d’erreur logging avec rotation de fichiers (logging.handlers.RotatingFileHandler)
Tester en environnement sandbox Valider les transformations avant le prod Jeu de données de 10 000 enregistrements aléatoires, comparatif de checksum après charge
Versionner les scripts Traçabilité et rollback Git‑repo dédié etl-dolibarr/, tags v1.2.0
Informer les parties prenantes Transparence sur les fenêtres d’exécution Mail + tableau partagé des fenêtres d’ETL


7. Solutions de contournement selon les contraintes

Contrainte Solution alternative Pro / Con
Aucun accès réseau direct à la base source Replication logique via binlog (MySQL) ou log‑decoder (PostgreSQL) → canaliser dans Kafka → ETL depuis Kafka + Sécurité renforcée, – Complexité supplémentaire
Pas de READ UNCOMMITTED autorisé Snapshot via mysqldump --single-transaction toutes les tables pertinentes toutes les heures + Simple, – Consommation I/O plus élevée
Besoin d’un chargement en temps réel (< 5 min) Change Data Capture (CDC) avec Debezium → topic Kafka → micro‑service de transformation → upsert dans le DW + Latence faible, – Nécessite infrastructure Kafka
Budget limité (pas de licence) Utiliser uniquement des outils opensource (Python + PostgreSQL) et des jobs cron + Rapide à mettre en place, – Manque de dashboard native


8. Monitoring & alerting (exemple)

Métrique Seuil d’alerte Source de collecte
Durée d’extraction > 10 min Prometheus etl_extraction_duration_seconds
Taux d’erreurs de transformation > 1 % Logs transform_factures.py → Loki
Volumes de lignes chargés < expected‑volume * 0.9 SELECT COUNT(*) FROM staging.table → Grafana
Latence de montée en charge > 5 min Heapster / kube‑state‑metrics
Espace disque du staging > 80 % du quota du -sh /etl/staging → alertes Slack

Exemple d’alerte Slack

# alertmanager.yml – example rule
- alert: ETLStagingFull
expr: job_disk_bytes{mount="/etl/staging"} > 8e+9
for: 5m
labels:
severity: critical
annotations:
summary: "Space de staging ETL plein>"
description: "Le disque /etl/staging dépasse 80 % de sa capacité."


9. Checklist finale avant le basculement

  1. Tests unitaires : chaque script passe sur le jeu de données de test (ex. 5 k factures).
  2. Tests d’intégration : exécution complète du pipeline dans l’environnement staging pendant 2 cycles complets.
  3. Revue de code : audit de sécurité (pas de DROP TABLE ou DELETE en écriture).
  4. Plan de rollback : script de restauration du state de la base source (ex. copie de la réplique avant le run).
  5. Documentation : README avec diagrammes, variables d’environnement, secrets (Vault ou AWS Secrets Manager). 6. Communication : diffuser le planning aux équipes support, aux équipes BI et aux auditors.


10. Conclusion

Mettre en place un processus ETL sur Dolibarr tout en préservant la disponibilité de l’ERP repose sur trois piliers :

Pilier Action clé
Isolement technique Snapshot / read‑only view + scheduling hors‑pic
Fiabilité mechanismnelle Idempotence, retries, logs détaillés
Observabilité Métriques, alertes, audit des chargements

En suivant le plan ci‑dessus – extraction via vues ou snapshots, stockage en format columnar (Parquet), transformation avec Python/SQL, chargement bulk dans un entrepôt dédié, puis automatisation via Airflow (ou tout autre orchestrateur) – vous obtiendrez un flux de données fiable, scalable et sans impact perceptible sur votre Dolibarr de production.

"Le secret n’est pas de toucher la base de production, mais de la lire sans qu’elle Notice que vous êtes là." – Proverbe des data‑engineers Dolibarr.

Bonne implémentation ! 🚀

Publications similaires