Si un consommateur Kafka déclenche un effet secondaire, supposez que des doublons sont possibles tant que vous n'avez pas prouvé le contraire.
C'est le défaut le plus sûr.
Kafka possède des fonctionnalités d'exactement une fois, mais les ingénieurs ont souvent tendance à surinterpréter ce que ces garanties couvrent. Elles aident beaucoup dans le modèle de traitement propre à Kafka. Elles ne rendent pas magiquement chaque écriture dans une base de données en aval ou chaque appel d'API externe exempt de doublons.
Le problème du consommateur
Le modèle risqué ressemble à ceci :
- consommer le message
- mettre à jour la base de données
- accuser réception du progrès
Si le service se bloque entre les étapes, une relecture peut se produire.
Ce n'est pas Kafka qui est cassé. C'est un comportement normal des systèmes distribués en cas de défaillance.
Utilisez une clé d'idempotence
Chaque événement qui peut déclencher un effet secondaire doit porter un identifiant stable :
{
"eventId": "evt_01HS8T0X8Q5Q8X4X8H2K2YJ6FD",
"type": "PaymentCharged",
"walletId": "wal_123"
}
Ensuite, rendez l'effet secondaire conditionnel à savoir si cet événement a déjà été appliqué.
Laissez la base de données l'appliquer
Une approche courante et fiable est une contrainte d'unicité :
CREATE TABLE processed_events (
event_id text PRIMARY KEY,
processed_at timestamptz NOT NULL DEFAULT now()
);
Traitez ensuite cela dans une seule transaction :
BEGIN;
INSERT INTO processed_events (event_id)
VALUES ($1);
UPDATE wallets
SET balance = balance - $2
WHERE id = $3;
COMMIT;
Si le même événement est rejoué, l'insertion échoue sur la contrainte d'unicité et le consommateur peut le traiter comme déjà traité.
Cela est souvent plus simple et plus fiable que la déduplication en mémoire ad hoc.
Le modèle mental utile
Ne visez pas à ce que "les doublons ne se produisent jamais."
Visez plutôt à ce que "les doublons ne changent pas l'état final."
C'est ce que l'idempotence vous apporte.
Lectures complémentaires