Idempotence : rejouer sans doublon
Une tâche idempotente produit le même résultat qu'on la lance une fois ou dix. C'est la condition pour relancer un pipeline après une panne sans créer de doublons ni perdre de lignes.
- upsert
- merge
- on conflict
- clé naturelle
- rejeu
- doublon
- reprise
Syntaxe
-- 1. Upsert sur une clé naturelle (SQLite, PostgreSQL)INSERT INTO cible (cle, col1, col2) VALUES (…)ON CONFLICT (cle) DO UPDATE SET col1 = excluded.col1, col2 = excluded.col2;-- 2. MERGE (PostgreSQL, SQL Server, Snowflake, BigQuery, Spark…)MERGE INTO cible cUSING lot l ON c.cle = l.cleWHEN MATCHED THEN UPDATE SET col1 = l.col1WHEN NOT MATCHED THEN INSERT (cle, col1) VALUES (l.cle, l.col1);-- 3. Remplacer la partition traitée, dans une transactionDELETE FROM cible WHERE jour = :jour;INSERT INTO cible SELECT … WHERE jour = :jour;Exemple
CREATE TABLE dim_produit ( ref TEXT PRIMARY KEY, -- clé naturelle venue de la source libelle TEXT, prix REAL, maj_le TEXT);-- Le même lot est chargé deux fois : la tâche a été relancée après-- un délai dépassé, alors que le premier essai avait en fait abouti.INSERT INTO dim_produit (ref, libelle, prix, maj_le) VALUES ('P01', 'Clavier', 39.9, '2026-10-31'), ('P02', 'Souris', 19.9, '2026-10-31')ON CONFLICT (ref) DO UPDATE SET libelle = excluded.libelle, prix = excluded.prix, maj_le = excluded.maj_leWHERE excluded.maj_le >= dim_produit.maj_le; -- jamais de retour en arrièreINSERT INTO dim_produit (ref, libelle, prix, maj_le) VALUES ('P01', 'Clavier', 39.9, '2026-10-31'), ('P02', 'Souris', 19.9, '2026-10-31')ON CONFLICT (ref) DO UPDATE SET libelle = excluded.libelle, prix = excluded.prix, maj_le = excluded.maj_leWHERE excluded.maj_le >= dim_produit.maj_le;SELECT COUNT(*) AS nb_produits FROM dim_produit;-- 2 (et non 4 : le second passage n'a rien ajouté)À retenir
- Le motif « supprimer puis réinsérer la partition du jour » dans une seule transaction est le plus simple pour les tables de faits : relancer le 31 octobre réécrit exactement le 31 octobre.
- Dédupliquez sur une clé naturelle ou métier (numéro de commande, référence produit), jamais sur un identifiant auto-incrémenté généré au chargement : il change à chaque essai.
- Faites dépendre la tâche de la date logique du lot qu'on lui passe, pas de l'heure courante : relancée trois jours plus tard, elle doit traiter le même jour.
- Piège : un simple INSERT suivi d'une relance automatique (retries) double les lignes. Le problème apparaît rarement en test et souvent en production, le jour où le réseau hoquette.
- Piège : les effets de bord (envoi d'un e-mail, appel d'API de paiement) ne se rattrapent pas par un upsert. Mémorisez ce qui a déjà été envoyé avec une clé d'idempotence.
Voir aussi
- Ingestion par lots : complète ou incrémentaleCharger par lots, c'est copier la source à intervalle régulier : en entier si elle est petite, ou seulement les lignes modifiées depuis le dernier filigrane quand elle grossit.
- Capture des changements (CDC)La CDC lit le journal de transactions de la base source pour émettre chaque insertion, modification et suppression. On l'utilise pour répliquer en quasi temps réel sans requêter les tables.
- Airflow : DAG, tâches et backfillUn DAG Airflow décrit en Python des tâches et leurs dépendances, sans cycle, avec un planning. On l'utilise pour lancer un pipeline chaque jour, relancer les échecs et rattraper le passé.
- Qualité des données et contratsLes tests de qualité vérifient automatiquement qu'une table respecte ce qu'on attend d'elle : clés uniques, valeurs présentes, données fraîches, volume normal. On les lance à chaque chargement.