Le Zen du data engineering

Pourquoi cet article ?

J’ai passé deux jours à traquer une duplication silencieuse en production. Le pipeline tournait, les logs étaient verts, les chiffres étaient faux. Quand j’ai fini par trouver la cause — une absence de contrôle d’unicité à l’insertion — je me suis demandé combien de mes pipelines avaient le même défaut. C’est ce genre de moment qui m’a poussé à formaliser les principes que j’applique maintenant systématiquement. Ils sont largement inspirés du Zen of Python.

Explicit is better than implicit

Chaque pipeline doit rendre visible :

  • Ce qu’il consomme : quelles sources, quels schémas attendus
  • Ce qu’il produit : quelles tables, quels fichiers
  • Ses hypothèses : partitions attendues, volumes normaux, format des dates

Un pipeline dont on comprend le comportement en lisant le code, sans lancer de test ni consulter les logs, est un pipeline bien écrit.

Pas de convention cachée. Pas de variable d’environnement non documentée. Pas de dépendance implicite entre DAGs.

Readability counts

Le code data le plus courant n’est pas du Python sophistiqué — c’est du SQL de transformation et du Python de plomberie. Les deux doivent être lisibles.

-- Lisible
WITH daily_orders AS (
    SELECT order_date, customer_id, SUM(amount) AS total
    FROM orders
    WHERE order_date = :execution_date
    GROUP BY order_date, customer_id
)
SELECT * FROM daily_orders WHERE total > 0

-- Illisible
SELECT * FROM (SELECT order_date,customer_id,SUM(amount) t FROM orders WHERE order_date=:d GROUP BY 1,2) x WHERE t>0

Les deux requêtes font la même chose. La première se débugue en 2 minutes. La seconde en 20.

Lisibilité > concision. Toujours.

Errors should never pass silently

Un pipeline silencieux est un pipeline dangereux. Toujours préférer un crash bruyant à une exécution bancale.

  • Vérifier les schémas en entrée avant de transformer
  • Poser des assertions sur les volumes (pas de table vide sans alerte)
  • Remonter les erreurs clairement, avec contexte
if df.empty:
    raise ValueError(f"Aucune donnée reçue pour {execution_date}")

if df["order_id"].duplicated().any():
    raise ValueError(f"{df['order_id'].duplicated().sum()} doublons détectés")

Un pipeline qui échoue avec un message clair se répare en minutes. Un pipeline qui corrompt des données silencieusement se répare en semaines.

Simple is better than complex

Le piège classique : abstraire trop tôt.

# Trop abstrait
pipeline = PipelineFactory.create(
    strategy=IncrementalMergeStrategy(),
    validator=SchemaValidator(strict=True),
    sink=WarehouseSink(mode="upsert")
)

# Clair
df = read_source("orders", date=execution_date)
df = clean_orders(df)
df = compute_metrics(df)
write_to_warehouse(df, table="mart_orders", mode="overwrite")

Le code data n’a pas besoin de design patterns sophistiqués. Il a besoin d’être lisible par quelqu’un qui le découvre pour la première fois.

Si l’implémentation est difficile à expliquer, c’est une mauvaise idée. Si elle est facile à expliquer, c’est probablement la bonne.

Practicality beats purity

L’architecture parfaite n’existe pas. Le pipeline parfait non plus. Ce qui existe, c’est le pipeline qui tourne, qui est fiable et que l’équipe sait maintenir.

  • Pas besoin de streaming si le batch suffit
  • Pas besoin de microservices si un script fait le travail
  • Pas besoin de Kubernetes si Docker Compose convient

Le pragmatisme bat la pureté architecturale. Livrer un pipeline fonctionnel en 2 jours vaut mieux qu’un pipeline parfait en 2 mois.

Now is better than never

Mieux vaut un pipeline imparfait en production qu’un pipeline parfait dans la tête.

  • Livrer un premier flux simple, mesurer, itérer
  • Poser les tests de base maintenant, pas “quand on aura le temps”
  • Documenter les décisions au moment où on les prend

La dette technique s’accumule quand on repousse. Le monitoring, les tests, la documentation : c’est maintenant ou jamais.

Idempotent par défaut

Un pipeline idempotent produit le même résultat qu’on le lance 1 fois ou 5 fois. C’est la propriété la plus importante en data engineering.

  • Utiliser MERGE (PostgreSQL ≥ 15) ou DELETE + INSERT par partition au lieu d’INSERT INTO brut
  • Partitionner par date et écraser la partition à chaque run
  • Ne jamais dépendre de l’état du run précédent

Si un pipeline n’est pas idempotent, chaque incident nécessite une intervention manuelle. Ce n’est pas tenable.

Tester ce qui compte

Tout tester est irréaliste. Tester ce qui casse le plus :

  • Schéma : les colonnes attendues existent-elles ?
  • Volume : le nombre de lignes est-il cohérent ?
  • Unicité : les clés primaires sont-elles uniques ?
  • Fraîcheur : la dernière partition date-t-elle de moins de 24h ?

Ces 4 tests couvrent 80 % des incidents data.

Résumé

Principe (Zen of Python)Application data
Explicit is better than implicitEntrées/sorties/hypothèses visibles
Readability countsSQL formaté, nommage clair, pas de GROUP BY 1,2
Errors should never pass silentlyAssertions, checks de schéma, alertes
Simple is better than complexPas d’abstraction prématurée
Practicality beats purityLivrer, itérer, ne pas sur-architecturer
Now is better than neverTests, monitoring et docs dès le jour 1

Le code data n’a pas besoin d’être élégant. Il a besoin d’être fiable, lisible, et réparable.