Stratégies de répétition et idempotence
1) Pourquoi est-ce nécessaire
Sur les réseaux, les pannes sont la norme : délais, erreurs de transition, flappings réseau, surcharge. Les retraits n'améliorent la fiabilité que si :1. la répétition est sûre (idempotente),
2. les extraits entre les répétitions sont respectés,
3. les limites/quotas et la « santé » des dépendances sont respectés.
L'objectif est le comportement effectively-once au niveau des opérations commerciales sans faux prises et courses.
2) Taxonomie de la sémantique de livraison
At-most-once : pas de répétitions, risque de perte (loging, fire-and-forget).
At-least-once : des doublons sont possibles → l'idempotence du consommateur est nécessaire (la plupart des files d'attente, webhooks).
Effectively-once : les doublons sont possibles, mais correctement dédupliqués (clés, transactions, outbox).
3) Quand rétracter, et quand pas
La rétractation a un sens : « 408 », « 429 » (en respectant « Retry-After »), « 425 » (Too Early), « 499 » (closed client sur le périmètre), « 5xx », « 504 », temps de réseau/discontinuité, « 502 » à la passerelle, « connexion reset ».
Non rétroactif sans modification de la requête : '400/ 401/403/404/422'.
Cas controversés : '409 Conflict' (généralement pas rétrograde ; d'abord, nous lisons le statut de l'opération/réaffirmons l'intention).
4) Taymouts, backoff et jitter
4. 1 Règles
D'abord la temporisation, puis la rétroaction : chaque demande doit avoir un « deadline ».
Backoff exponentiel : 'delay _ n = base 2 ^ n', limitons' max _ delay '.
Jitter est obligatoire : ajoutez le hasard pour découpler les « ondes synchrones stupides ».
4. 2 Modèles de gitter
Full jitter : 'sleep = rand (0, base2 ^ n)' est le meilleur choix général.
Jitter decorrelated : 'sleep = min (max_delay, rand (base, sleep_prev3))' - pour les dialogues longs.
Equal jitter : 'sleep = base2 ^ n/2 + rand (0, base2 ^ n/2)' est une variation douce.
4. 3 Retry-budget
Limitez la proportion de retraits :- `retry_budget_per_min = max(α success_rps, floor β)`; habituellement 'α = 0. 1–0. 2`.
- Lorsque le budget est épuisé - nous passons à fail-fast/circuit breaker « open ».
5) Interaction avec rate limiting et Circuit Breaker
Respectez 'Retry-After', 'RateLimit-Reset' et considérez cela comme un back-off.
En cas de « 5xx »/timauts élevés, abaissez la fréquence des retraits et le parallélisme général.
- Half-open : permet un échantillon limité.
- Open : rejette instantanément (économise la ressource).
- Closed : travail normal.
- Sur les opérations de write, il est préférable de retourner 409/503 avec un indice clair que de tourner des retraits agressifs.
6) Idempotence des opérations write
6. 1 Idée générale
Les mêmes intentions → un seul résultat. La base est la clé d'idempotence et le stockage des enregistrements d'exécution.
6. 2 contrat HTTP
Le client envoie un en-tête :
Idempotency-Key: 7a6b7f9e-2a46-4d0b-9c3a-2b30e1c3c9e3
Idempotency-Key-Expiry: 24h # optional
Serveur :
- lors de la première exécution réussie, stocker (clé → résultat, état, hachage du corps) ;
- à la répétition, renvoie l'ancienne réponse et le titre 'Idempotency-Replay : true' ;
- en cas de conflit de corps (même clé mais autre payload) : '409 Conflict'.
6. 3 Stockage et TTL
Table/clé-valeur : 'idempotency _ key', 'request _ hash', 'result', 'status', 'expiry _ at'.
TTL = fenêtre des répétitions possibles et des livraisons tardives (habituellement 24-72 h pour les paiements).
Index par 'idempotency _ key' ; pour une charge élevée - Chardonnez par le hachage.
6. 4 Exemple de schéma (SQL)
sql
CREATE TABLE idempo_store (
key UUID PRIMARY KEY,
req_hash BYTEA NOT NULL,
status INT NOT NULL,
response JSONB NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
expiry_at TIMESTAMPTZ NOT NULL
);
6. 5 Pseudo-code du gestionnaire
pseudo handle_write(req):
k = req. headers["Idempotency-Key"]
h = hash(req. body)
rec = idempo_store. get(k)
if rec and rec. req_hash == h:
return rec. status, rec. response, {"Idempotency-Replay": "true"}
if rec and rec. req_hash!= h:
return 409, problem("IDEMPOTENT_CONFLICT")
begin tx result = apply_business_mutation (req) # change status upsert once (idempo_store, key = k, req_hash=h, status = 201, response = result, expiry = now () + 2d)
commit
return 201, result
7) Les modèles « effectively-once »
Transactional Outbox : enregistre un événement professionnel et envoie un message à partir de la même transaction OBD via un relais d'arrière-plan ; le consommateur est idempotente.
Inbox/Table de traitement chez le consommateur : nous gardons 'event _ id'pour ignorer les prises.
Exactly-once sur Kafka ≠ exactly-once dans les affaires : même avec EOS producteur/consumer, la logique appliquée doit encore être idempotente.
Transactions compensatoires (Saga) : si les étapes sont rétrogrades et provoquent des effets secondaires, nous renvoyons le système à l'invariant.
8) Cas privés : paiements et transactions financières
Fort idempotency : la clé est liée à la logique de l'opération (par exemple, "external _ payment _ id').
Déduplication sur PSP : Stockez 'merchant _ reference' → si la PSP est répétée, le résultat sera le même.
Retrai « du client » : n'autoriser que sous 'Idempotency-Key', sinon risque de double débit.
Compétitivité : blocage « par compte/outil/contrat » pendant la durée de l'exécution ; à répétition, retourner 409/423.
Observabilité : métriques 'idempo _ replay _ total', 'idempo _ conflict _ total'.
9) Webhooks et défis externes
Signatures HMAC et fenêtre temporelle ; d'abord la vérification, puis le traitement.
Retraits de l'expéditeur : exponentielle backoff + jitter, 'max _ attempts' et DLQ.
Le consommateur est idempotent : 'event _ id' → table/in-memory cache ; l'ordre « propre » n'est pas garanti.
Codes : 2xx = succès, 4xx = ne pas répéter, 5xx/délai = répéter.
10) Files d'attente et tâches de fond
At-least-once par défaut, les doublons → sont inévitables.
Stockez 'task _ id '/' event _ id' et l'état d'exécution ; dans le doublage, la voie courte est « replay ».
DLQ et poison-messages : compteur de tentatives, quarantaine, analyse manuelle.
Limites de concurrence (sémaphores) et workers idempotent.
11) Versioning et clés « naturelles »
Les clés naturelles (numéro de compte + date + numéro de document) améliorent la résistance aux répétitions.
Si vous changez de schéma ou de version, incluez la clé de version dans Idempotency-Key ou dans le hachage de requête.
12) en-têtes HTTP et conseils au client
'Idempotency-Key ',' Idempotency-Replay ',' Retry-After ',' Prefer : wait = <sec> ',' If-Match '/' ETag '(verrous optimistes).
409 en cas de conflit de clé, 425/429/503 avec « Retry-After ».
Pour les opérations « longues », la réception de l'état asynchrone ('202 Accepted' +' Location 'sur la ressource d'état).
13) Tests et scénarios de chaos
Tests negative : double envoi, répétition avec un autre corps, dissynchrone de l'horloge.
Trouble de l'ordre : 't2' arrive avant 't1'.
Injection de temporisation/' RST '/' EOF ', demandes en demi-teinte (slow-POST).
L'idempotency est tombé → le comportement fail-closed (mieux vaut une panne qu'un double débit).
14) Métriques et alertes
`retries_total{reason}`, `retry_budget_used{route}`, `backoff_seconds_bucket`.
`idempo_replay_total`, `idempo_conflict_total`, `duplicate_detected_total`.
Part 409/425/429/5xx par itinéraire ; p95/p99 « temps avant le succès » avec des retraits.
Alert : burn-rate du budget des rétrograves, flambée des conflits d'idempotence, croissance du DLQ.
15) Anti-modèles
Recréer toutes les erreurs consécutives.
L'absence de jitter → les ondes synchrones des rétroactions.
Clés de longue durée sans TTL et nettoyage.
Conservez le résultat après la communauté de l'effet secondaire (violation de l'outbox).
Les logs sans 'trace _ id '/' idempotency _ key' → ne peuvent pas être forensés.
Retraits parallèles agressifs sur les opérations write.
16) Chèque-liste prod-prêt
- Une politique unique : ce qui est rétrograde, ce qui ne l'est pas ; codes et indices pour le client.
- Exponentielle backoff + full jitter ; spécifié par 'retry _ budget'.
- Contrat 'Idempotency-Key' + stockage des résultats avec TTL.
- Outbox/Inbox pour les événements ; DLQ; limites de concurrence.
- Intégration avec circuit breaker, respect 'Retry-After'.
- Métriques/alertes sur les retraits/doublons/conflits.
- Ensemble de tests de chaos et émulation des pannes de réseau.
- Documentation pour les clients : exemples de back-offs et statuts.
17) TL; DR
Les retraits ne sont utiles qu'avec l'idempotence. Entrez 'Idempotency-Key' et le référentiel de résultats, appliquez un backoff exponentiel avec gitter et retry-budget, respectez 'Retry-After', intégrez-vous au circuit breaker. Pour les événements - outbox/inbox ; pour les paiements - déduplication stricte et verrouillage. Mesurez les retraits et les conflits, testez les doublons et les délais.