Idempotency keys for exactly-once processing
by defly on 12/1/2025, 12:07:33 PM
https://www.morling.dev/blog/on-idempotency-keys/
Comments
by: imron
I like to use uuid5 for this. It produces unique keys in a given namespace (defined by a uuid) but also takes an input key and produces identical output ID for the same input key.<p>This has a number of nice properties:<p>1. You don’t need to store keys in any special way. Just make them a unique column of your db and the db will detect duplicates for you (and you can provide logic to handle as required, eg ignoring if other input fields are the same, raising an error if a message has the same idempotent key but different fields).<p>2. You can reliably generate new downstream keys from an incoming key without the need for coordination between consumers, getting an identical output key for a given input key regardless of consumer.<p>3. In the event of a replayed message it’s fine to republish downstream events because the system is now deterministic for a given input, so you’ll get identical output (including generated messages) for identical input, and generating duplicate outputs is not an issue because this will be detected and ignored by downstream consumers.<p>4. This parallelises well because consumers are deterministic and don’t require any coordination except by db transaction.
12/5/2025, 10:53:56 PM
by: bokohut
This was my exact solution in the late 1990's that I formulated using a uid algorithm I created when confronted with a growing payment processing load issue that centralized hardware at the time could not handle. MsSQL could not process the ever increasing load yet the firehose of real-time payments transaction volume could not be turned off so an interim parallel solution involving microservices to walk everything over to Oracle was devised using this technique. Everything old is new again as the patterns and cycles ebb and flow.
12/5/2025, 9:49:07 PM
by: pyrolistical
This article glosses over the hardest bit and bike sheds too much over keys.<p>> Critically, these two things must happen atomically, typically by wrapping them in a database transaction. Either the message gets processed and its idempotency key gets persisted. Or, the transaction gets rolled back and no changes are applied at all.<p>How do you do that when the processing isn’t persisted to the same database? IE. what if the side effect is outside the transaction?<p>You can’t atomically rollback the transaction and external side effects.<p>If you could use a distributed database transaction already, then you don’t need idempotent keys at all. The transaction itself is the guarantee
12/5/2025, 11:52:37 PM
by: hinkley
Failure resistant systems end up having a bespoke implementation of a project management workflow built into them and then treating each task like a project to be managed from start to finish, with milestones along the way.
12/5/2025, 9:28:56 PM
by: jackfranklyn
The messier version of this problem: banks themselves don't give stable unique identifiers. Transaction references get reused, amounts change during settlement, descriptions morph between API calls. In practice you end up building composite keys from fuzzy matching, not clean UUIDs. Real payment data is far noisier than these theoretical discussions assume.
12/5/2025, 11:16:00 PM
by: otterley
This is some useful reading that's in the same vein: <a href="https://docs.aws.amazon.com/wellarchitected/latest/reliability-pillar/rel_prevent_interaction_failure_idempotent.html" rel="nofollow">https://docs.aws.amazon.com/wellarchitected/latest/reliabili...</a>
12/5/2025, 11:23:24 PM
by: Groxx
Why call this "exactly once" when it's very clearly "at most once"?
12/5/2025, 11:25:05 PM
by: eximius
These strategies only really work for stream processing. You also want idempotent APIs which won't really work with these. You'd probably go for the strategy they pass over which is having it be an arbitrary string key and just writing it down with some TTL.
12/5/2025, 10:35:27 PM
by: amarant
Huh. Interesting solution! I've always thought the only way to make an API idempotent was to not expose "adding" endpoints. That is, instead of exposing a endpoint "addvalue(n)" you would have setvalue(n)". Any adding that might be needed is then left as an exercise for the client.<p>Which obviously has it's own set of tradeoffs.
12/6/2025, 1:03:17 AM
by: ekjhgkejhgk
Here's what I don't understand about distributed systems: TCP works amazing, so why not use the same ideas? Every message increments a counter, so the receiver can tell the ordering and whether some message is missing. Why is this complicated?
12/5/2025, 9:31:42 PM
by: manoDev
> The more messages you need to process overall, the more attractive a solution centered around monotonically increasing sequences becomes, as it allows for space-efficient duplicate detection and exclusion, no matter how many messages you have.<p>It should be the opposite: with more messages you want to scale with independent consumers, and a monotonic counter is a disaster for that.<p>You also don’t need to worry about dropping old messages if you implement your processing to respect the commutative property.
12/5/2025, 9:46:00 PM
by: zmj
I like the uuid v7 approach - being able to reject messages that have aged past the idempotency key retention period is a nice safeguard.
12/5/2025, 10:39:36 PM
by: attila-lendvai
does OP mean simply the <i>identity</i> of the message?<p>idempotency means something else to me.
12/5/2025, 11:31:30 PM