Insert the event ID and apply the effect in one transaction
A redelivery’s insert returns 0 rows, so the handler skips the debit and acks. Ack only after the commit.
Dedupe on which ID?
Bad: The broker’s message ID
SQS IDs change on redrive; receipt handles change on every receive.
Good: The producer’s event ID
The outbox row’s UUID, the same on every copy.
Two consumers hold m7 at once. Both check for the key, find nothing, and debit.
The fix
Insert the key first, under a unique key. B’s insert waits for A’s commit, then inserts 0 rows.
The inbox: store first, ack at once, process later
A thin receiver inserts each message and acks in milliseconds. A processor applies effects from the table, with its own retries and order.
65 GB
of dedupe keys, 15 days at 500/s
SQS keeps messages up to 14 days, so a DLQ redrive can arrive two weeks late. Remember keys longer than any retry.
Dedupe in Redis?
Bad: Redis decides
Claim the key, crash before the commit, and every redelivery skips the payment.
Good: The database decides
Redis only caches “done” after the commit; misses fall through to the unique key.
Effects outside your database
- Send a key derived from the event
- Stripe replays the first response, 500s included
- Its keys last 24 hours; your DLQ, 14 days
- So record the outcome and check it first
Remember
Delivered many times, applied once. Insert a stable event ID with the effect, in one transaction, then ack.