An idempotent replay returns 409 rather than the original response, and keys never expire
- Dominant language
- C#
- Stars
- 6
- Forks
- 7
- Avg merge
- 4h 42m
- Merged PRs (30d)
- 307
Description
Writing #548's documentation established what inbound idempotency actually does. Two of those behaviours are worth revisiting before clients build against them, and one of them grows a table forever.
## A replay does not return the original response
A second request with a used key gets a fixed 409 and the message `Request with this Idempotency-Key already processed.`
The case idempotency exists for is a client that sent a request, the server committed it, and the connection dropped before the response arrived. That client retries with the same key precisely because it does not know what happened. Under the current behaviour it learns the write succeeded and never learns the id that was created.
So the key prevents the duplicate and does not complete the recovery. The client still has to go and search for what it made, which is the thing it was trying to avoid.
Most implementations return the original response instead, with the original status, so a retry is transparent. That is what a client integrating against this will assume, and the docs now say otherwise, which is the least we owe them.
Storing the response body to make that possible is not free: it is another place a response is kept, and #607 is an open question about exactly that kind of storage. Worth deciding together rather than separately.
## A completed key never expires, and the record is never removed
Only an in-progress claim is reclaimed, after ten minutes, for the case where the requester's process died mid-request. A key that completed is a 409 for all time.
Two consequences.
A client using a deterministic key, an order id or a document number, can never legitimately retry that operation again, even a year later against a different intent. That pushes clients toward random keys, which then cannot be recomputed after a crash, which is the situation deterministic keys exist to solve.
And `IdempotencyRecord` grows without bound. Nothing prunes it. Every write that ever carried a key is a row kept forever.
Most implementations expire keys after a day, which bounds the table and matches how long a client could plausibly still be retrying.
## What to decide
Whether a replay returns the original response, and how long a key is honoured. They interact: keeping responses is much cheaper if they expire.
`WorkflowRunRetentionService` already exists as the pattern for pruning on a policy.
## Done when
Both are decided, implemented, and `docs/idempotency.md` says what is true, rather than the current behaviour being inherited by default because nobody looked at it.
Found while documenting the existing behaviour for #548.
Contributor guide
Research direction
Read docs/idempotency.md to understand the documented inbound behavior, then inspect IdempotencyRecord and WorkflowRunRetentionService for the current persistence and pruning patterns. Decide how replay responses and completed-key retention should work together, implement both behaviors, and update docs/idempotency.md so it describes the resulting behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- backend-api-design, databases, documentation
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100