Fulmenflux

Blog / Streaming

Exactly-once delivery doesn't exist. Idempotency is how you fake it safely.

Retries, replays and redeliveries are normal in event pipelines. Here is the dedup-key pattern that keeps them from corrupting your numbers.

Cristian Curteanu 2 min read

Every broker and every HTTP client retries. A webhook times out, the sender tries again, and your pipeline sees the same event twice. If the event is a payment, a usage record or an inventory change, “twice” is a bug in someone’s invoice.

Kafka’s exactly-once semantics cover one hop: producer to broker to a consumer that writes back to Kafka. The moment your consumer writes to Postgres, calls an API or updates a cache, you are outside that guarantee. What you can build is at-least-once delivery with idempotent effects, and that gives you the same result.

The pattern: a dedup key and one transaction

Give every event a key that identifies the fact, not the delivery. A UUID generated by the producer at the moment the thing happened works. A hash of the delivery attempt does not.

Then make “have I seen this?” and “apply the effect” a single atomic step:

BEGIN;

INSERT INTO processed_events (event_id, processed_at)
VALUES ($1, now())
ON CONFLICT (event_id) DO NOTHING;

-- only continue if a row was inserted
UPDATE usage_totals
SET units = units + $2
WHERE customer_id = $3;

COMMIT;

If the insert conflicts, skip the update and commit. A replay of last Tuesday’s topic then changes nothing.

Three ways this goes wrong

  • The key is generated by the consumer. Each retry produces a new key, so nothing is ever deduplicated.
  • The check and the effect are separate transactions. A crash between them leaves you either double-applying or never applying.
  • The dedup table grows forever. Keep keys for longer than your maximum replay window, then expire them by partition.

Replay becomes a feature

Once effects are idempotent, you can rewind a consumer group to fix a bug in your transform and let it run. No one-off scripts, no “did we already fix that day?” spreadsheets. This is the same pattern that keeps the event pipeline in ABAXUS, our usage-based billing engine, accurate under retries.

If your pipeline can’t survive a replay today, that is the first thing we would look at in an architecture review.

Tags

More from the blog