Idempotentialité et clés
Qu'est-ce que l'idempotence
Idempotence est une propriété d'opération dans laquelle la répétition avec le même ID ne modifie pas l'effet final. Dans les systèmes distribués, c'est la principale façon de rendre le résultat équivalent à « exactement un traitement », malgré les retraits, les messages dupliqués et les délais.
L'idée clé est que chaque opération potentiellement répétable doit être marquée avec une clé par laquelle le système reconnaît « c'est déjà fait » et applique le résultat au plus une fois.
Où c'est important
Paiements et soldes : débits/créditeurs par 'opération _ id'.
Réservations/quotas/limites : même slot/ressource.
Webhooks/notifications : la refonte ne doit pas faire double emploi avec l'effet.
Importer/migrer : réessayer les fichiers/paquets.
Stream-processing : prises du courtier/CDC.
Vues des clés et leur portée
1. Clé d'opération - ID d'une tentative d'activité spécifique
Exemples : 'idempotency _ key' (HTTP), 'operation _ id' (RPC).
Domaine : service/agrégat ; stocké dans la table de déduplication.
2. Clé d'événement - ID d'événement/message unique
Exemples : 'event _ id' (UUID), '(producer_id, sequence)'.
Domaine : consommateur/groupe de consommateurs ; protège les projections.
3. La clé des affaires est la clé naturelle du domaine
Exemples : 'payment _ id', 'invoice _ number', '(user_id, day)'.
Zone : agrégat ; s'applique aux contrôles d'unicité/version.
Politique de TTL et de stockage
TTL des clés ≥ une fenêtre de répétition possible : Rétention du journal + retards réseau/processeur.
Pour les domaines critiques (paiements) TTL - jours/semaines ; pour la télémétrie, l'horloge.
Nettoyer les tables de dedup par les jobs de background ; pour l'audit - archiver.
Stockage de clés (déduplication)
OBD transactionnelle (recommandé) : upsert/index unique fiable, transaction conjointe avec effet.
KV/Redis : rapide, pratique pour un court TTL, mais sans transaction conjointe avec OLTP - prudent.
State store stream processeur : local + chainjlog dans le courtier ; bon à Flink/KStreams.
- idempotency_keys
`consumer_id` (или `service`), `op_id` (PK на пару), `applied_at`, `ttl_expires_at`, `result_hash`/`response_status` (опц.) .
Index : '(consumer_id, op_id)' est unique.
Techniques d'implémentation de base
1) Transaction « effet + progrès »
Enregistrer le résultat et enregistrer la progression de la lecture/position - dans une transaction.
pseudo begin tx if not exists(select 1 from idempotency_keys where consumer=:c and op_id=:id) then
-- apply effect atomically (upsert/merge/increment)
apply_effect(...)
insert into idempotency_keys(consumer, op_id, applied_at)
values(:c,:id, now)
end if
-- record reading progress (offset/position)
upsert offsets set pos=:pos where consumer=:c commit
2) Concurrence optimiste (version de l'unité)
Protège contre le double effet lors des courses :sql update account set balance = balance +:delta,
version = version + 1 where id=:account_id and version=:expected_version;
-- if 0 rows are updated → retry/conflict
3) Selles idempotentes (upsert/merge)
Opération « facturer une fois » :sql insert into bonuses(user_id, op_id, amount)
values(:u,:op,:amt)
on conflict (user_id, op_id) do nothing;
Idempotence dans les protocoles
HTTP/REST
Titre 'Idempotency-Key : <uuid'hash>'.
Le serveur stocke l'enregistrement de la clé et retourne la même réponse (ou le code '409 '/' 422' en cas de conflit d'invariants).
Pour les « dangereux » POST - obligatoire 'Idempotency-Key' + la politique de temporisation/rétrospective durable.
gRPC/RPC
Métadonnées 'idempotency _ key', 'request _ id' + deadline.
Implémentation serveur - Comme dans REST : Table de déduplication dans une transaction.
Courtiers/streaming (Kafka/NATS/Pulsar)
Producteur : stable 'event _ id '/producteur idempotent (où soutenu).
Consumer : dedup par '(consumer_id, event_id)' et/ou par version commerciale de l'agrégat.
DLQ séparé pour les messages non idempotent/corrompus.
Webhooks et partenaires externes
Exiger 'Idempotency-Key '/' event _ id' dans le contrat ; la réadmission doit être sûre.
Stocker 'notification _ id' et les états d'envoi ; Avec le retrait - ne doublez pas.
Conception de clés
Déterminisme : les retraits doivent envoyer la même clé (générer à l'avance sur le client/orchestrateur).
Zone de visibilité : formez 'op _ id' comme 'service : aggregate : id : purpose'.
Conflits : utilisez le UUIDv7/ULID ou le hachage des paramètres métiers (avec du sel si nécessaire).
Hiérarchie : le 'opération _ id'générique sur le front de → est diffusé dans toutes les sous-opérations (chaîne idempotent).
Aspects UX et produits
Une requête répétée par clé doit renvoyer le même résultat (y compris le corps/état) ou un « déjà » explicite.
Affichez les statuts « opération en cours/terminée » au lieu de réessayer « chance ».
Pour les opérations longues, le poling par clé ('GET/operations/{ op _ id}').
Observabilité
Loger 'op _ id', 'event _ id', 'trace _ id', résultat : 'APPLIED '/' ALREADY _ APPLIED'.
Métriques : proportion de répétitions, taille des tables de déduplication, temps de transaction, conflits de version, taux DLQ.
Trace : la clé doit passer par la commande → l'événement → la projection → l'appel externe.
Sécurité et conformité
Ne pas stocker le PII dans les clés ; la clé est un ID, pas un payload.
Chiffrez les champs sensibles dans les enregistrements de déduplication lorsque la TTL est longue.
Politique de conservation : TTL et archives ; le droit à l'oubli - à travers la crypto-effacement des réponses/métadonnées (si elles contiennent des IPI).
Tests
1. Doublons : lancer un message/requête 2 à 5 fois - l'effet est exactement un.
2. Chute entre les étapes : avant/après l'enregistrement de l'effet, avant/après la fixation de l'offset.
3. Restart/rebalance des consommateurs : pas de double application.
4. Concurrence : demandes parallèles avec un 'op _ id' → un effet, l'autre est 'ALREADY _ APPLIED/409'.
5. Clés à longue durée de vie : vérification de l'expiration de la TTL et des répétitions après la restauration.
Anti-modèles
Une nouvelle clé aléatoire pour chaque rétraction : le système ne reconnaît pas les répétitions.
Deux commits distincts : d'abord l'effet, puis l'offset - la chute entre les deux double l'effet.
Confiance du seul courtier : pas de dédoupe dans le bleu/agrégat.
Absence de version de l'agrégat : un événement répété change d'état une deuxième fois.
Fat keys : la clé inclut les champs métiers/PII → les fuites et les indices complexes.
Absence de réponses répétables : le client ne peut pas rétracter en toute sécurité.
Exemples
Paiement POST
Client : 'POST/payments' + 'Idempotency-Key : k-789'.
Serveur : transaction : crée 'payment' et écrit dans 'idempotency _ keys'.
Répétition : renvoie le même '201 '/corps ; en cas de conflit, l'invariant est « 409 ».
Bonus (sink)
sql insert into credits(user_id, op_id, amount, created_at)
values(:u,:op,:amt, now)
on conflict (user_id, op_id) do nothing;
Projection à partir d'événements
Consumer stocke 'seen (event_id)' et 'version' de l'agrégat ; la répétition est ignoré/idempotent upsert.
L'avancement de la lecture est enregistré dans la même transaction que la mise à jour de la projection.
Chèque-liste de production
- Une clé idempotente et sa zone de visibilité ont été définies pour toutes les opérations non sécurisées.
- Il existe des tables de déduplication avec TTL et index uniques.
- L'effet et le progrès de la lecture communiqueront atomiquement.
- Le modèle write inclut une concurrence optimiste (version/sequence).
- Les contrats API fixent 'Idempotency-Key '/' operation _ id' et le comportement de répétition.
- Les métriques et les logs contiennent 'op _ id '/' event _ id '/' trace _ id'.
- Essais de doublons, chutes et courses - en CI.
- La politique TTL/Archives et la sécurité PII ont été respectées.
FAQ
En quoi 'Idempotency-Key' diffère-t-il de 'Request-Id' ?
"Request-Id' : Trace ; il peut changer sur les retraits. 'Idempotency-Key'est l'identifiant sémantique de l'opération, obligatoirement le même lors des répétitions.
L'idempotence peut-elle être faite sans OBD ?
Pour une fenêtre courte, oui (Redis/cache intra-process), mais sans transaction conjointe, le risque de prise augmente. Dans les domaines critiques, c'est mieux dans une seule transaction OBD.
Que faire des partenaires externes ?
Négociez les clés et les réponses répétées. Si votre partenaire ne prend pas en charge - enveloppez l'appel dans votre couche idempotent et stockez « déjà appliqué ».
Comment choisir TTL ?
Résumer les délais maximaux : rétention du journal + cas worst/rebalance + tampon. Ajouter le stock (× 2).
Résultat
L'idempotence est une discipline de clés, de transactions et de versions. Les identificateurs d'opérations stables + fixation atomique de l'effet et des progrès de lecture + les selles/projections idempotentes donnent « exactement un effet » sans magie de niveau de transport. Rendre les clés déterministes, les TTL réalistes et les tests malveillants. Alors les retraits et les doublons deviendront une routine, pas des incidents.