Idempotent Consumers in Event-Driven Systems
Contexte
Dans un système basé sur Kafka, un message peut être livré plusieurs fois au consommateur.
Kafka garantit par défaut une livraison at-least-once, ce qui signifie qu’un message peut être traité plusieurs fois.
Plusieurs situations peuvent provoquer une redélivrance :
- retry du consumer
- redémarrage du service
- crash du consumer
- rééquilibrage des partitions
- replay d’événements
- erreurs de commit d’offset
Sans protection spécifique, un consumer peut appliquer plusieurs fois la même opération.
Cela peut provoquer :
- des doublons
- des incohérences métier
- des effets de bord indésirables
Pour éviter ces problèmes, les consommateurs doivent être idempotents.
Définition
Une opération est idempotente si son exécution plusieurs fois produit le même résultat que son exécution une seule fois.
Exemple :
traiter(eventId=123)
traiter(eventId=123)
traiter(eventId=123)
Le résultat final doit rester identique.
Pourquoi l'idempotence est essentielle
Dans un système distribué, plusieurs mécanismes peuvent provoquer des redélivrances :
- retry automatique
- réexécution d'événements
- duplication réseau
- reprise après crash
L'idempotence permet de garantir la cohérence du système malgré ces situations.
Stratégies d’implémentation
Plusieurs approches peuvent être utilisées.
1 — Table des événements traités
Une stratégie consiste à enregistrer les identifiants des événements déjà traités.
Exemple de table :
processed_events
event_id
processed_at
Flux de traitement :
- vérifier si
event_idexiste - si oui → ignorer l’événement
- sinon → traiter et enregistrer l’événement
Cette approche est simple et fiable.
2 — Vérification de l’état métier
Dans certains cas, le modèle métier permet déjà de détecter les doublons.
Exemple :
- vérifier si une entité existe déjà
- vérifier la version d’un agrégat
- vérifier l’état actuel avant modification
Exemple :
if (sejour.exists(id)) {
ignore event
}
3 — Versionnement des agrégats
Dans une architecture orientée événements, chaque agrégat peut posséder une version.
Un événement contient :
aggregate_id
version
Si la version reçue est déjà appliquée, l’événement est ignoré.
Interaction avec Retry et DLT
L'idempotence fonctionne en complément du mécanisme Retry + Dead Letter Topic.
Flux typique :
- un message est consommé
- une erreur se produit
- retry du message
- le message est traité plusieurs fois
- l'idempotence empêche les effets de bord
Sans idempotence, les retries peuvent provoquer des incohérences.
Bonnes pratiques
Pour construire des consumers robustes :
- inclure un
eventIdunique dans les événements - éviter les effets de bord non contrôlés
- rendre les opérations métier idempotentes
- combiner idempotence + retry + DLT
- surveiller les événements du DLT
Exemple simplifié
Pseudo-code :
if processedEvents.contains(eventId):
return
process(event)
processedEvents.save(eventId)
Conclusion
Dans les architectures event-driven, les redélivrances sont normales.
Les consumers doivent être conçus pour gérer ces situations.
L’idempotence est un principe clé pour garantir la cohérence des systèmes distribués.