EventRelay
A payment and its event are stored in one transaction and published to Kafka through an outbox. The consumer remembers processed events, so a repeated delivery changes nothing.
A mail room that sends once
The payment and its event envelope drop into the box in one move. A carriage sends the envelope down a tube, and the receiver keeps a spike of numbers: a repeated envelope falls into the “seen” tray.
The problem
A process can stop after Kafka has acknowledged an event but before the database records it as published. Then the event goes out again. So delivery is at least once, and a repeat must be harmless.
The solution
The payment and its outbox row commit to PostgreSQL in one transaction. Several publishers claim rows with FOR UPDATE SKIP LOCKED and send to Kafka with acks=all; a failure retries with a backoff from one second to five minutes, and after eight attempts the row goes to an operator. The consumer records the event id together with its projection update, so a repeated delivery changes nothing.
Under the hood
- Transactional outbox: the payment and event in one transaction.
- Parallel publishers that never claim the same row.
- Exponential retries and a DEAD state after eight attempts.
- An idempotent consumer: processed events stored with the projection.
- Malformed records go to a dead-letter topic after three tries.