13 - Consommation robuste avec Kafka
La robustesse d’un système event-driven dépend largement de la manière dont les consumers traitent les events. Un producer fiable ne suffit pas: si la consommation est fragile, l’architecture devient instable. En banque, la consommation est un composant critique: un simple doublon peut créer une incohérence comptable, un consumer lent peut provoquer un backlog qui bloque les opérations.
Cet article détaille les mécanismes fondamentaux de consommation, les pièges classiques, et les stratégies avancées pour garantir une consommation robuste et prévisible, même sous forte charge ou en période d’incident.
Consumer groups et rebalancing
Kafka répartit les partitions d’un topic entre les membres d’un consumer group. Chaque partition est traitée par un seul consumer du groupe, ce qui garantit l’ordre par partition.
Le rebalance survient lorsqu’un consumer rejoint ou quitte le groupe, ou lorsque Kafka détecte une absence de heartbeat. Les partitions sont réassignées et les consumers doivent redémarrer leur lecture. Un rebalance mal géré peut créer des boucles d’instabilité et dégrader la latence.
Paramètres critiques
session.timeout.ms: temps d’inactivité avant exclusion.heartbeat.interval.ms: fréquence des heartbeats.max.poll.interval.ms: durée maximale entre deux polls; si trop longue, le consumer est considéré comme bloqué.
En banque, un traitement lourd (ex: scoring AML) peut dépasser max.poll. Il
faut alors découper le traitement ou augmenter le paramètre, au risque de
ralentir la détection des consumers morts.
Cooperative rebalancing
Le mode cooperative limite les perturbations en réassignant progressivement les partitions. Cela réduit les “stop-the-world” et améliore la stabilité opérationnelle, particulièrement utile sur des flux critiques.
Gestion des offsets
Les offsets représentent la position de lecture dans le log. Ils sont la clé de la reprise et du replay.
- Auto-commit: simple, mais risque de perte en cas d’erreur.
- Manual commit: plus sûr, commit après traitement réussi.
En banque, on privilégie le commit manuel pour garantir que l’événement a été traité correctement avant de le marquer comme consommé.
Patterns de commit
- Commit après traitement: garantit l’intégrité, mais augmente la latence.
- Commit par batch: améliore le débit, mais peut générer des doublons.
- Commit transactionnel: avec Kafka transactions, on peut consommer et produire dans la même transaction (exactly-once dans le pipeline).
Sémantiques de livraison
Kafka fournit at-least-once par défaut. Cela implique des doublons possibles. Les consumers doivent donc être idempotents.
| Sémantique | Avantages | Risques | Usage bancaire |
|---|---|---|---|
| At-most-once | Latence faible | Perte possible | Rarement acceptable |
| At-least-once | Fiable | Doublons possibles | Standard si idempotence |
| Exactly-once | Très fiable | Complexité | Pipelines critiques |
Dans un ledger bancaire, l’idempotence est obligatoire. Un event dupliqué ne
peut pas créer une double écriture. On utilise des mécanismes de déduplication
par event_id ou des tables de contrôle d’idempotence.
Idempotence côté consumer
Un consumer robuste doit pouvoir traiter un event plusieurs fois sans changer le résultat. Stratégies classiques:
- Déduplication par
event_id: table dédiée. - Upsert sur un read model par clé métier.
- Outbox inversée: log de traitements déjà appliqués.
Exemple bancaire: un consumer Ledger applique une écriture double-entry. Si le
même event est reçu deux fois, il doit reconnaître le event_id et ignorer
le doublon, faute de quoi le solde devient faux.
Consume-transform-produce et transactions
Beaucoup de consumers ne font pas que consommer: ils transforment et republient un nouvel event. Dans Kafka, ce pattern consume-transform-produce peut bénéficier des transactions pour offrir une sémantique proche de “exactly-once” dans le pipeline.
Le principe:
- Lire un batch d’events.
- Produire les events dérivés dans une transaction.
- Commiter les offsets dans la même transaction (sendOffsetsToTransaction).
Ainsi, soit tout est validé (events + offsets), soit tout est annulé. En banque, ce mécanisme est très utile pour des pipelines critiques (ledger, scoring AML), car il évite qu’un event soit committé sans que l’event dérivé soit publié.
Ce modèle n’est pas gratuit: il augmente la latence et la complexité, et il
nécessite isolation.level=read_committed côté consumers downstream. Mais il
réduit fortement les incohérences dans les chaînes de traitement.
Gestion des effets externes
Un consumer peut déclencher des effets externes: notifications, appels à un service de change, mises à jour dans un système legacy. Ces effets ne sont pas transactionnels avec Kafka. Deux stratégies s’imposent:
1) Idempotence externe: inclure un identifiant unique dans l’appel externe et dédupliquer côté service cible. 2) Outbox côté consumer: écrire l’effet externe dans une table locale, puis le publier/traiter séparément.
Dans un flux bancaire, envoyer un SMS de notification peut être idempotent, mais débiter un compte externe ne l’est pas. On doit donc isoler ces actions et leur donner leur propre mécanisme de compensation ou de déduplication.
Validation métier et qualité de données
Un consumer robuste ne se contente pas de parser un event. Il doit valider que
le payload respecte les règles attendues. Dans un environnement bancaire, cela
inclut:
- validation de la devise (ISO 4217),
- cohérence des montants (unités mineures),
- présence des identifiants critiques (transfer_id, account_id).
Les violations doivent être envoyées vers une DLQ ou un topic de quarantaine. Cela évite que des événements corrompus polluent les projections et permet une analyse post-mortem contrôlée.
Stratégies de shutdown et rebalance
Un shutdown brutal est une source majeure d’instabilité. Les consumers doivent implémenter une fermeture propre: - pause de la consommation, - traitement des events en cours, - commit des offsets, - fermeture des ressources externes.
Kafka permet d’utiliser des rebalance listeners pour gérer proprement les assignations. En banque, où les flux sont critiques, cette discipline réduit les rebalances inutiles et améliore la stabilité globale.
Backpressure et contrôle du débit
Un consumer lent peut accumuler du lag. Kafka ne ralentit pas le producer, il faut donc gérer la pression côté consumer.
Outils disponibles:
- pause/resume sur les partitions.
- Ajuster max.poll.records et fetch.min.bytes.
- Ajuster le niveau de parallélisme (plus de consumers).
En banque, on choisit souvent de ralentir plutôt que de perdre des events. Un backlog est acceptable s’il reste sous contrôle et si l’équipe est alertée.
Concurrence et ordering
Kafka garantit l’ordre par partition, pas globalement. Pour augmenter le débit, on ajoute des partitions et des consumers, mais l’ordre est alors garanti uniquement pour une clé donnée.
Bonnes pratiques: - Paralléliser au niveau partition. - Garder un traitement séquentiel par clé métier. - Éviter la concurrence interne qui casse l’ordre (threads multiples par partition sans coordination).
Erreurs, retries et DLQ
Un consumer peut échouer pour des erreurs transitoires ou permanentes. Il faut les distinguer pour choisir la bonne stratégie.
Retries contrôlés
Les erreurs transitoires (timeout réseau, service externe indisponible) peuvent être retentées avec backoff. Les erreurs permanentes (schéma invalide, données corrompues) doivent être envoyées en DLQ.
Dead Letter Queue
La DLQ permet d’isoler les messages non traitables sans bloquer le flux. En banque, on préfère traiter un event invalide séparément plutôt que de bloquer l’ensemble du pipeline.
Bonnes pratiques de base
- Ajouter un topic de retry dédié pour les erreurs transitoires.
prod.payments.transfer.retry.v1
- Ajouter un topic de DLQ pour les erreurs permanentes.
prod.payments.transfer.dlq.v1
- Documenter le processus de reprocessing (qui, quand, comment).
Reprocessing et replay
Le replay est une force majeure de Kafka, mais il doit être contrôlé.
Scénarios fréquents: - recalcul d’une projection après un bug, - ajout d’une nouvelle projection, - incident nécessitant une reconstruction d’état.
En banque, un replay doit être déterministe: les effets externes sont isolés et les appels externes sont journalisés. Un replay qui déclenche des notifications ou des mouvements comptables réels est catastrophique.
Stratégies de replay
- Offset reset: repositionner l’offset du consumer.
- Consumer dédié: créer un consumer isolé pour le replay.
- Topic de reprocessing: republier les events dans un flux séparé.
Le choix dépend du risque opérationnel et de la sensibilité du flux.
Observabilité et SLA
La robustesse se mesure. Les métriques clés: - Lag par consumer group. - Processing time par message. - Commit latency. - Error rate. - Rebalance frequency.
En banque, ces métriques doivent être corrélées à des SLA métiers: un virement instantané ne tolère pas plusieurs minutes de lag. L’observabilité doit être associée à des alertes et des runbooks.
Rattrapage et reprise après incident
Lorsqu’un consumer accumule un retard important, il faut une stratégie claire
de rattrapage. Les options sont:
- Scale-out: ajouter des instances pour augmenter le parallélisme.
- Batching contrôlé: augmenter max.poll.records temporairement.
- Replay ciblé: rejouer uniquement les partitions touchées.
En banque, le rattrapage doit être maîtrisé pour ne pas surcharger les systèmes aval (fraude, reporting, notifications). On peut introduire une fenêtre de débit limitée ou un mode “degraded” qui ignore les traitements non critiques jusqu’à résorption du backlog.
Une reprise réussie dépend aussi des runbooks: qui déclenche le replay, quelles partitions sont concernées, comment valider la cohérence post-replay. Sans ces procédures, un incident de consommation se transforme en incident d’audit.
Exemple bancaire: pipeline de virement
1) TransferInitiated arrive dans le consumer Ledger.
2) Le consumer écrit dans le ledger et publie TransferSettled.
3) Il commit l’offset uniquement après succès.
4) En cas d’erreur, il retente ou publie en DLQ.
Si le consumer crash après l’écriture mais avant le commit, l’event sera rejoué. Grâce à l’idempotence, la deuxième consommation n’a pas d’effet.
Configuration type pour consumer critique
enable.auto.commit=false
max.poll.records=200
max.poll.interval.ms=300000
session.timeout.ms=15000
heartbeat.interval.ms=3000
fetch.min.bytes=1024
fetch.max.wait.ms=500
isolation.level=read_committed
Ce profil privilégie la fiabilité et réduit le risque de rebalances fréquents.
read_committed garantit que les consumers lisent uniquement les events
committés en transaction.
Sécurité et conformité côté consumer
Les consumers manipulent souvent des données sensibles. Dans un environnement bancaire, ils doivent appliquer des règles strictes: - ACLs limitant l’accès aux topics. - Masquage des données sensibles dans les logs. - Chiffrement des snapshots et caches locaux.
Un consumer qui écrit une projection dans une base analytique doit respecter les politiques de rétention et d’anonymisation. Il est fréquent de séparer les projections opérationnelles (temps réel) des projections de reporting, afin de limiter l’exposition des données personnelles.
Anti-pièges courants
- Auto-commit activé sur un flux critique.
- Traitement trop long sans adaptation de
max.poll.interval. - Absence d’idempotence.
- DLQ utilisée comme flux normal.
- Reprocessing non documenté.
Conclusion
La consommation robuste est le véritable facteur de fiabilité d’une architecture event-driven. Dans un environnement bancaire, elle doit être conçue comme un composant critique: idempotence, gestion des offsets, retries contrôlés et observabilité ne sont pas optionnels. Avec ces mécanismes, Kafka peut supporter des volumes élevés tout en garantissant cohérence et audit.
À mesure que l’architecture grandit, la discipline de consommation devient un avantage compétitif: elle réduit les incidents, accélère les replays et renforce la confiance des métiers dans la plateforme. C’est un investissement qui paie durablement.