Strategie di ripetizione e idipotenza
1) Perché è necessario
Le reti di guasti sono la norma: timeout, transit, flapping di rete, sovraccarico. I retrai migliorano l'affidabilità solo se:1. il ripetizione è sicuro (idipotente),
2. vengono rispettate le estensioni tra le ricorrenze,
3. sono rispettati i limiti/quote e la «salute» delle dipendenze.
L'obiettivo è un comportamento effettively-once a livello di business senza false riprese e corse.
2) Tassonomia semantica di consegna
At-not-once: senza ripetizione, rischio di perdita (logica, fire-and-forget).
At-least-once - È possibile duplicare il → richiede l'idipotenza del consumatore (la maggior parte delle code, webhoop).
Effectively-once: i duplicati sono possibili ma deduplicati correttamente (chiavi, transazioni, outbox).
3) Quando ritrarre e quando no
La retrazione ha senso: «408», «429» (rispettando «Retry-After»), «425» (Too Early), «499» (client closed sul perimetro), «5xx», «504», timeout di rete, «502» sul gateway, «connection reset».
Non ritraiamo senza modificare la richiesta «400/ 401/403/404/422».
Valigette controverse: '409 Conflict' (di solito non retraiamo; Prima leggiamo lo stato dell'operazione/ricalcoliamo l'intenzione).
4) Timeout, backoff e jitter
4. 1 Regole
Prima il timeout, poi i ritrasmi, ogni richiesta deve avere deadline.
Exponential backoff: «delay _ n = base 2 ^ n», limitiamo «max _ delay».
Jitter è obbligatorio: aggiungi casualità alla fine delle onde sincroni stupide.
4. 2 Modelli di jitter
Full jitter: 'sleep = rand (0, base2 ^ n)' è la scelta condivisa migliore.
Decorated jitter: 'sleep = min (max _ delay, rand (base, sleep _ prev3))' - per interazioni lunghe.
Equal jitter: 'sleep = base2 ^ n/2 + rand (0, base2 ^ n/2)' è una variazione morbida.
4. 3 Retry-budget
Limitate la quota di retrai:- `retry_budget_per_min = max(α success_rps, floor β)`; Solitamente, '© = 0. 1–0. 2`.
- Quando il budget è esaurito, passiamo a fail-fast/circuito breaker «open».
5) Interazione con rate limiting e Circuito Breaker
Rispettate Retry-After, RateLimit-Reset e consideratelo un back-off.
Con gli alti «5xx »/timeout - ridurre la frequenza dei retrai e il parallelismo generale.
- Half-open: consente un campione limitato.
- Open: rifiuta istantaneamente (risparmia la risorsa).
- Closed è un lavoro normale.
- Nelle operazioni write è preferibile restituire 409/503 con un suggerimento chiaro piuttosto che girare i retrai aggressivi.
6) Idampotenza delle operazioni write
6. 1 Idea generale
Le stesse intenzioni → un solo risultato. La base è la chiave di idempotenza e l'archivio delle registrazioni di esecuzione.
6. 2 contratto HTTP
Il cliente invia il titolo:
Idempotency-Key: 7a6b7f9e-2a46-4d0b-9c3a-2b30e1c3c9e3
Idempotency-Key-Expiry: 24h # optional
Server:
- Al primo successo, salva (chiave, risultato, stato, hash del corpo).
- Quando viene ripetuta, restituisce la risposta precedente e il titolo «Idempotency-Replay: true»;
- quando il corpo è in conflitto (stessa chiave, ma altra payload) - '409 Conflict'.
6. 3 Storage e TTL
Tabella/chiave-valore: «idempotency _ key», «sollest _ hash», «result», «status», «expiry _ at».
TTL = una finestra di possibili ripetizioni e consegne tardive (generalmente 24-72 ore per i pagamenti).
Indici dì idempotency _ key "; per l'alto carico di lavoro, charding su hashtag.
6. 4 Schema di esempio (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 Pseudocode processore
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) Pattern «effectively-once»
Transactional Outbox: registrazione dell'evento aziendale e invio di un messaggio dalla stessa transazione database tramite relayer di sfondo il consumatore è idoneo.
Inbox/Processed-Table del consumatore: salviamo «event _ id» per ignorare le riprese.
Exactly-once su Kafka per exactly-once nel business: anche con EOS produttore/concertatore, la logica applicativa deve essere comunque idemotante.
Transazioni compensative (Saga) - Se i passi vengono ritracciati e causano effetti collaterali, restituiamo il sistema all'invariante.
8) Casi privati: pagamenti e transazioni finanziarie
Strong idempotency - La chiave è collegata alla logica dell'operazione (ad esempio, 'external _ payment _ id').
La deduplicazione su PSP memorizza'merchant _ reference ', che restituisce il risultato precedente.
Retrai «da cliente»: autorizza solo con «Idempotency-Key», altrimenti il rischio di doppia cancellazione.
Concorrenza: blocchi su account/strumento/contratto per il periodo di esecuzione; se ripetuto, restituisci 409/423.
Osservabilità: metriche «idempo _ replay _ total», «idempo _ conflict _ total».
9) Webhook e chiamate esterne
Firme HMAC e finestra temporale Prima il controllo, poi l'elaborazione.
Retrai del mittente: backoff esponenziale + jitter, "max _ attemps'e DLQ.
Il consumatore è idepotente: 'event _ id', tabella/in-memory cache; l'ordine «attento» non è garantito.
Codici: 2xx = riuscito, 4xx = non ripetere, 5xx/timeout = ripetere.
10) Code e attività di sfondo
At-least-once predefinito duplicati sono inevitabili.
Memorizza «task _ id »/« event _ id» e lo stato di esecuzione; durante la ripresa, un breve percorso «replay».
DLQ e poison-messaggistica: conteggio tentativi, quarantena, analisi manuale.
Limiti competitivi (semafori) e worker idempotenti.
11) Versioning e chiavi «naturali»
Le chiavi naturali (numero di conto + data + numero di documento) aumentano la resistenza alle ricorrenze.
Quando cambi schema/versione, inserisci la chiave di versione in Idempotency-Key o nell'hash della query.
12) Intestazioni HTTP e suggerimenti al client
«Idempotency-Key», «Idempotency-Replay», «Retry-After», «Preferer: wait = <sec>», «If-Match »/« ETag» (blocchi ottimistici).
409 in caso di conflitto di chiavi, 425/429/503 con il valico «Retry-After».
Per le operazioni «lunghe», accetta lo stato asincrono ('202 Accetted' + Location ', per risorsa di stato).
13) Test e script di caos
Test negativi: doppio invio, ripetizione con altro corpo, rassincrone ore.
"t2" arriva prima dì t1 ".
Iniezione di timeout/« RST »/« EOF », richieste a metà (slow-POST).
Un deposito idempotency decaduto → il comportamento fail-closed (meglio un guasto che una doppia cancellazione).
14) Metriche e alerti
`retries_total{reason}`, `retry_budget_used{route}`, `backoff_seconds_bucket`.
`idempo_replay_total`, `idempo_conflict_total`, `duplicate_detected_total`.
Quota 409/425/429/5xx lungo le rotte; p95/p99 «tempo prima del successo» con i retrai.
Alert: bilancio burn-rate dei retrai, aumento dei conflitti di idempotenza, crescita del DLQ.
15) Antipattern
Ritrattare tutti gli errori di fila.
L'assenza di un jitter ha → le onde sincronizzate dei retrai.
Chiavi a lunga vita senza TTL e pulizia.
Salva il risultato dopo l'effetto collaterale (violazione outbox).
I loghi senza «trace _ id »/« idempotency _ key» non possono essere forensi.
Retrai paralleli aggressivi su operazioni write.
16) Assegno-foglio prod-pronto
- Politica unificata: cosa ritraiamo, cosa no; codici e suggerimenti al cliente.
- Backoff esponenziale + full jitter; impostato'retry _ budget get '.
- Contratto «Idempotency-Key» + Memorizzazione risultati con TTL.
- Outbox/Inbox per gli eventi; DLQ; limiti di competitività.
- Integrazione con il circuito breaker, respect 'Retry-After'.
- Metriche/alert per retrai/duplicati/conflitti.
- Serie di test di caos e emulazione di guasti di rete.
- Documentazione per i clienti: esempi di back-off e di stato.
17) TL; DR
I retrai sono utili solo con l'idipotenza. Immettere «Idempotency-Key» e l'archivio risultati, applicare backoff esponenziale con jitter e retry-budget, rispettare «Retry-After», integrarsi con il circuito breaker. Per gli eventi - outbox/inbox; per i pagamenti: deduplicazione e blocchi rigorosi. Misurare retrai e conflitti, testare duplicati e timeout.