Challenge
Core services published billing and usage events to the message broker through in-process calls. That coupled the write path to broker availability and, on failure, risked both lost and duplicated events.
When events meter money and usage, neither is acceptable.
Approach
- Change-data capture instead of in-process publishing. Re-architected the flow to DynamoDB Streams → a Lambda publisher → the event broker. Services no longer talk to the broker directly, and every committed state change is published downstream.
- Idempotency end to end. Enforced idempotency with Redis
SET NXand added consumer-side deduplication to absorb the duplicates that at-least-once delivery produces. - A hardened broker client layer. Migrated to a modern Kafka client, fixed asynchronous sends that were stalling consumer lag, corrected commit semantics, and resolved IAM / authorisation issues around idempotent writes.
- No more race conditions on deletes. Eliminated a quota-deletion race with a soft-delete + TTL + secondary-index design.
Outcome
Publishing is now driven by committed data rather than by in-process calls, so services deploy and run independently of the broker. Duplicates from at-least-once delivery are absorbed on both the producer and consumer side, and the quota-deletion race is designed out rather than worked around.
Want to go deeper on the idempotency side? We wrote about it in Idempotency in payment systems.


