Riprovare una richiesta senza doppio effetto
L'idempotenza permette di riprovare una richiesta POST senza temere un doppio effetto. Un client che perde la risposta per timeout può riprovare con la stessa chiave di idempotenza, e l'API restituisce la risposta memorizzata invece di eseguire nuovamente l'handler. CaptainDNS segue la convenzione Stripe, con una finestra di validità di 24 ore.
Perché usare l'idempotenza
Le reti non sono affidabili. Un client può:
- Inviare una richiesta che ha successo lato server ma la cui risposta si perde per strada.
- Andare in crash nel bel mezzo di un retry automatico.
- Vedere un timeout dal load balancer mentre l'API ha già elaborato la richiesta.
Senza idempotenza, devi scegliere tra:
- Non riprovare, con il rischio di perdere operazioni legittime.
- Riprovare alla cieca, con il rischio di eseguire più volte la stessa operazione.
L'idempotenza offre una terza via: riprovare in sicurezza.
Come usarla
Aggiungi un header Idempotency-Key alla tua richiesta POST:
POST /public/v1/deliverability/score HTTP/1.1
Host: api.captaindns.com
Authorization: Bearer cdns_live_a3f2XK7mN9QrVtZ4yP1sH6eL8cF2dB5aR3gW7kJxM
Content-Type: application/json
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
{"email":"contact@captaindns.com"}
La chiave deve essere una stringa opaca da 1 a 255 caratteri ASCII stampabili senza spazi. Gli UUID v4 o ULID restano raccomandati come buona pratica (entropia sufficiente, collisioni statisticamente impossibili), ma qualsiasi identificatore univoco lato client va bene.
Cosa succede lato server
Alla prima richiesta:
- CaptainDNS calcola lo SHA-256 del body della richiesta.
- Dopo il successo, memorizza l'associazione tra la chiave di idempotenza e l'hash del body.
- Cattura lo status e il body della risposta prima di restituirla al client.
Alla seconda richiesta con la stessa chiave:
- CaptainDNS trova un record esistente.
- Se l'hash del nuovo body corrisponde a quello memorizzato: replay. La risposta memorizzata viene restituita tale e quale, con l'header
X-Idempotent-Replay: true. - Se l'hash differisce: conflitto. L'API restituisce
409 IDEMPOTENCY_CONFLICTcon un messaggio esplicito.
Il replay non attiva l'elaborazione sottostante e non scrive una seconda riga di utilizzo: la risposta memorizzata viene restituita tale e quale. Attraversa però tutte le tappe che lo precedono. Il controllo dei crediti e il token bucket per chiave si applicano prima che l'idempotenza entri in gioco: un replay consuma quindi un token di rate limit e un credito, esattamente come una chiamata ordinaria. L'idempotenza ti protegge dalla doppia elaborazione, non dalla doppia fatturazione. Dimensiona i tuoi retry di conseguenza.
Finestra di validità
I record di idempotenza hanno una durata di 24 ore. Trascorso questo periodo, vengono eliminati e una chiave riutilizzata attiva una nuova elaborazione normalmente.
Questa finestra è un compromesso: troppo corta, e perdi il vantaggio dei retry differiti; troppo lunga, e accumuli un volume crescente di risposte vecchie. 24 ore coprono la maggior parte dei pattern di retry: il retry immediato dopo un errore di rete, quello nei minuti successivi alla ripresa di un job, e quello programmato di notte dopo un fallimento rilevato dal monitoraggio.
Metodi coinvolti
L'idempotenza si applica solo ai metodi che modificano lo stato o che sono costosi. In pratica, tutti gli endpoint pubblici CaptainDNS sono POST, quindi tutti sono idonei. Le richieste GET non passano dal meccanismo di idempotenza: sono per natura idempotenti a livello HTTP.
L'header Idempotency-Key è opzionale. Se non lo invii, la richiesta viene elaborata normalmente, senza garanzia di replay.
Conflitti
Un conflitto 409 IDEMPOTENCY_CONFLICT si verifica quando:
- Riutilizzi una chiave di idempotenza per una richiesta il cui body differisce, anche di un solo carattere.
- Questo può capitare se il tuo client rigenera la richiesta tra due tentativi (timestamp, UUID incorporati, ordine delle chiavi JSON).
Per evitare i conflitti:
- Calcola la chiave di idempotenza prima di serializzare il body, non dopo.
- Non mescolare chiamate diverse sotto la stessa chiave.
- Se il tuo framework riordina le chiavi JSON, fissa l'ordine a mano.
In caso di conflitto legittimo (per esempio, vuoi davvero riprovare un'operazione diversa), genera una nuova chiave.
Esempi d'uso
Retry dopo timeout di rete
idempotencyKey = uuid()
for attempt in 1..3:
try:
response = post(url, body, headers={"Idempotency-Key": idempotencyKey})
return response
except TimeoutError:
sleep(2 * attempt)
raise
Ogni retry usa la stessa chiave. Se una delle chiamate ha già avuto successo lato server, le successive restituiranno la risposta memorizzata.
Deduplica in una coda di job
job = fetch_next_job()
idempotencyKey = hash(job.id)
response = post(url, job.payload, headers={"Idempotency-Key": idempotencyKey})
mark_job_done(job.id, response)
Se la coda ridistribuisce un job dopo un crash, la seconda esecuzione dello stesso job id riutilizza la stessa chiave e recupera la risposta memorizzata, invece di addebitare due volte al cliente.
Batch con deduplica lato server
for domain in domains:
idempotencyKey = "batch-" + run_id + "-" + domain
post(url, {"domain": domain}, headers={"Idempotency-Key": idempotencyKey})
Se il batch viene rilanciato dopo un errore, ogni dominio già elaborato passa direttamente al replay, e solo quelli non ancora trattati consumano crediti.
Limitazioni
- Storage limitato a 24 h: oltre questo termine, il replay non è più possibile. I job molto differiti devono conservare le risposte lato client.
- Ambito legato alla chiave API: una stessa chiave di idempotenza usata da due chiavi API diverse non collide. È voluto: ogni chiave ha il proprio spazio dei nomi indipendente.
- Nessuna ripresa tra server diversi: se usi più backend distinti, condividono la stessa API ma devono coordinare tra loro le chiavi di idempotenza.
- Identità stretta del body: uno spazio in più rompe l'hash e genera un conflitto. Serializza in modo deterministico.
Prossimi passi
Prosegui con i codici di errore per capire come reagire a 409 IDEMPOTENCY_CONFLICT, oppure consulta il riferimento OpenAPI per vedere l'header documentato per ogni endpoint pubblico.