GH GambleHub

Exactly-once vs At-least-once

1) Perché parlare di semantici

La semantica di consegna determina la frequenza con cui il destinatario vedrà il messaggio in caso di guasti e retrai:
  • At-just-once - senza ripetizioni, ma è possibile perdere (raramente accettabile).
  • At-least-once - Non perdiamo, ma sono possibili duplicati (default della maggior parte dei broker/code).
  • Exactly-once - Ogni messaggio viene elaborato esattamente una volta in termini di effetto osservato.

La verità chiave è che in un mondo distribuito, senza transazioni globali e coerenza sincrona, il puro end-to-end exactly-once non è raggiungibile. Stiamo costruendo in modo efficiente exactly-once, consentendo ripetizioni nei trasporti, ma rendendo idipotente il trattamento in modo che l'effetto osservato sia «come una volta sola».


2) Modello di guasto e dove si verificano i duplicati

Le ripetizioni vengono visualizzate a causa di:
  • Perdita ack/commit (produttore/broker/consumatore «non sentito» conferma).
  • Rielezioni leader/repliche, ripristino dopo interruzioni di rete.
  • Timeout/retrai in qualsiasi zona (kliyent→broker→konsyumer→sink).

La conseguenza è che non si può contare sull'unicità del trasporto. Gestiamo gli effetti: registrazione nel database, prelievo di denaro, invio di email, ecc.


3) Exactly-once nei fornitori e che cosa è veramente

3. 1 Kafka

Dà mattoni:
  • Idempotent Producer (`enable. idempotence = true ') - impedisce le riprese sul lato produttore durante i retrai.
  • Transazioni - I messaggi vengono pubblicati in modo atomico in più partenze e committono offset di consumo (pattern read-process-write senza «omissioni»).
  • Compattion - memorizza l'ultimo valore della chiave.

Ma «fine catena» (BB/pagamento/posta elettronica) richiede comunque idempotenza. Altrimenti la ripresa del processore farà scattare l'effetto.

3. 2 NATS / Rabbit / SQS

Il valore predefinito è at-least-once con ack/redelivery. Exactly-once è raggiunto a livello di applicazione: chiavi, dashboard, upsert.

Output: Exactly-once con l'effetto exactly-onc. L'ultima è nel gestore.


4) Come costruire efficacemente exactly-once sopra at-least-once

4. 1 Chiave Idempotente (idempotency key)

Ogni comando/evento porta la chiave naturale: 'payment _ id', 'order _ id # step', 'saga _ id # n'. Processore:
  • Controlla «l'hai già visto?» - Dedup Store (Redis/BD) con TTL/retensh.
  • Se visto, ripete il risultato calcolato in precedenza o fa no-op.
Redis-sketch:
lua
-- SET key if not exists; expires in 24h local ok = redis.call("SET", KEYS[1], ARGV[1], "NX", "EX", 86400)
if ok then return "PROCESS" else return "SKIP" end

4. 2 Upsert in base (sink idipotente)

I record vengono effettuati tramite UPSERT/ON CONFLICT con verifica versione/importo.

PostgreSQL:
sql
INSERT INTO payments(id, status, amount, updated_at)
VALUES ($1, $2, $3, now())
ON CONFLICT (id) DO UPDATE
SET status = EXCLUDED.status,
updated_at = now()
WHERE payments.status <> EXCLUDED.status;

4. 3 Transazione Outbox/Inbox

Outbox: la transazione aziendale e la scrittura «eventi di pubblicazione» avvengono in una singola transazione database. Il pubblicatore di background legge outbox e invia al broker non ci sono differenze tra lo stato e l'evento.
Inbox: per i comandi in ingresso salviamo «messaggino _ id» e il risultato prima dell'esecuzione; La rielaborazione vede la registrazione e non ripete gli effetti collaterali.

4. 4 Gestore di catene consistenti (read→process→write)

La transazione «ha letto l'offset» ha registrato i risultati del commit in un unico blocco atomico.
Senza transazioni: «Prima scrivi il risultato/Inbox, poi ack»; con il crash, il duplicato vedrà Inbox e termina no-op.

4. 5 SAGA/compensi

Quando l'idempotenza non è possibile (il provider esterno ha prelevato il denaro), usiamo le operazioni di compensazione (refund/void) e le API esterne idipotenti (ripetuto «POST» con «Idempotency-Key» dà lo stesso risultato).


5) Quando basta at-least-once

Aggiornamenti della cache/rappresentazioni materializzate con la combinazione della chiave.
Contatori/metriche in cui la ripetizione è accettabile (o memorizziamo i delta con la versione).
Notifiche in cui la lettera secondaria non è critica (meglio mettere comunque la chiave).

Regola: a meno che la ripresa non cambi il significato del business o rileviamo facilmente le → at-least-once + protezione parziale.


6) Prestazioni e costi

Exactly-once (anche «efficiente») costa di più: scrittura (Inbox/Outbox), memorizzazione delle chiavi, transazioni, più difficile da diagnosticare.
At-least-once è più economico/semplice, meglio throughput/p99.
Valuta il prezzo della ripresa x probabilità di ripresa vs costo di protezione.


7) Esempi di configurazione e codice

7. 1 produttore Kafka (Idempotence + transazioni)

properties enable.idempotence=true acks=all retries=INT_MAX max.in.flight.requests.per.connection=5 transactional.id=orders-writer-1
java producer.initTransactions();
producer.beginTransaction();
producer.send(recordA);
producer.send(recordB);
// также можно atomically commit consumer offsets producer.commitTransaction();

7. 2 Consolle con Inbox (pseudocode)

pseudo if (inbox.exists(msg.id)) return inbox.result(msg.id)
begin tx if!inbox.insert(msg.id) then return inbox.result(msg.id)
result = handle(msg)
sink.upsert(result)     # идемпотентный синк inbox.set_result(msg.id, result)
commit ack(msg)

7. 3 HTTP Idempotency-Key (API esterne)


POST /payments
Idempotency-Key: 7f1c-42-...
Body: { "payment_id": "p-123", "amount": 10.00 }

Un POST ripetuto con la stessa chiave indica lo stesso risultato/stato.


8) Osservabilità e metriche

«duplicato _ attemps _ total» - quante volte è stata catturata (Inbox/Redis).
«idempotency _ hit _ rate» è la percentuale di ripetizioni «salvate» dall'idipotenza.
'txn _ abort _ rate' (Kafka/BD) è una percentuale di rimborsi.
«outbox _ backlog» è un ritardo di pubblicazione.
'exactly _ once _ path _ latency {p95, p99}' v'at _ least _ once _ path _ latency '- Spese generali.
Controllo logi: collegamento messaggio _ id, idempotency _ key, saga _ id, attempt.


9) Test playbook (Game Days)

I retrai del produttore per i timeout artificiali.
Crash tra «sink e ack»: assicurarsi che Inbox/Upsert impedisca la ripresa.
Pere-consegna: aumentare redelivery nel broker; Controllare il dedotto.
Idampotenza delle API esterne: POST ripetuti con la stessa chiave è la stessa risposta.
Cambia leader/interruzione di rete - Controlla le transazioni Kafka/comportamento dei consumatori.


10) Anti-pattern

«Abbiamo Kafka con exactly-once, quindi senza chiavi».
No-op ack prima di scrivere: ackout, ma il sink è caduto.
Assenza di DLQ/retrai con jitter: ripetizioni senza fine e tempesta.
UUID casuali invece delle chiavi naturali: niente da deduplicare.
Combinazione di Inbox/Outbox con tabelle prod senza indici: blocchi hot e code p99.
Operazioni aziendali senza API idipotente nei provider esterni.


11) Assegno-foglio di selezione

1. Prezzo della presa (denaro/legale/UX) vs prezzo di protezione (latitanza/complessità/costo).
2. C'è una chiave naturale evento/operazione? Se non lo è, createne uno stabile.
3. Sink supporta Upsert/versioning? Altrimenti, Inbox + risarcimenti.
4. Sono necessarie transazioni globali? In caso contrario, segmentare su SAGA.
5. Hai bisogno di repliche/ripetute lunghe? Kafka + Outbox. Hai bisogno di RPC veloce/ritardo basso? NATS + Idempotency-Key.
6. Multi-tenenza e quote: isolamento chiavi/spazi.
7. Osservabilità: le metriche idempotency e backlog sono incluse.


12) FAQ

Q: È possibile raggiungere un exactly-once matematico end-to-end?
A: Solo in casi ristretti con un unico storage e transazioni consistenti su tutto il percorso. In generale, no; Utilizzare efficacemente exactly-once attraverso l'idempotenza.

Cosa c'è di più veloce?
A: At-least-once. Exactly-once aggiunge transazioni/memorizzazione delle chiavi sopra p99 e il costo.

Dove conservare le chiavi di idempotenza?
A: Store rapido (Redis) con TTL o tabella Inbox (PK = messaggistica _ id). Per i pagamenti è più lungo (giorni/settimane).

Q: Come scegliere TTL chiavi di deducibilità?
A: Minimo = tempo massimo di recapito + scorte operative (generalmente 24-72 ore). Per la finanza, di più.

Ho bisogno di una chiave se ho una compattion su Kafka?
A: Sì. La Compattion riduce lo storage, ma non rende il sink idipotente.


13) Riepilogo

At-least-once è una semantica di trasporto di base e affidabile.
Exactly-once come effetto aziendale si ottiene a livello processore: Idempotency-Key, Inbox/Outbox, Upsert/versioni, SAGA/compensi.
La scelta è un compromesso costo, rischio di ripresa e facilità d'uso. Progettate le chiavi naturali, rendete idipotenti i sink, aggiungete osservazione e fate regolarmente game days - in questo modo i vostri pipline saranno prevedibili e sicuri anche in caso di tempeste di retroscena e guasti.

Contact

Mettiti in contatto

Scrivici per qualsiasi domanda o richiesta di supporto.Siamo sempre pronti ad aiutarti!

Avvia integrazione

L’Email è obbligatoria. Telegram o WhatsApp — opzionali.

Il tuo nome opzionale
Email opzionale
Oggetto opzionale
Messaggio opzionale
Telegram opzionale
@
Se indichi Telegram — ti risponderemo anche lì, oltre che via Email.
WhatsApp opzionale
Formato: +prefisso internazionale e numero (ad es. +39XXXXXXXXX).

Cliccando sul pulsante, acconsenti al trattamento dei dati.