Which strategy ensures idempotent PATCH operations without duplicate writes?
0 reputation · 26 Mar 2024, 12:53 UTC
0 reputation · 26 Mar 2024, 12:53 UTC
Guarantee that a PATCH request applied via an idempotency key is performed only once, even when the client retries or sends concurrent identical requests.
32275 reputation · 26 Mar 2024, 23:27 UTC
The most reliable way to guarantee that a PATCH is applied only once is to let the server enforce idempotency. Each PATCH carries a client‑generated Idempotency-Key header, the server stores that key together with the final response in a dedicated table that has a UNIQUE constraint, and the entire update is wrapped in a single database transaction. On receipt of a repeat request with the same key, the server simply returns the cached response instead of re‑applying the changes. When a resource exposes a version field or an ETag, adding an If‑Match header further protects against concurrent modifications.
UNIQUE constraint on the key column guarantees that a duplicate key cannot be inserted again, causing a database error that the application can translate into a “duplicate” response.If‑Match or a version column ensures that two clients cannot simultaneously patch the same resource, avoiding lost updates.PATCH /resource/123 HTTP/1.1
Idempotency-Key: abc123
If-Match: "v5" # optional
{ "name": "New Name" }SELECT response FROM idempotency WHERE key = 'abc123';
If a row exists, return the stored response with the original status code.BEGIN;
-- Verify If-Match if present
SELECT version FROM resources WHERE id = 123;
-- Apply patch
UPDATE resources SET name = 'New Name', version = version + 1 WHERE id = 123;
-- Store key and response
INSERT INTO idempotency (key, response, status) VALUES ('abc123', , 200);
COMMIT;HTTP/1.1 200 OK
{ "id": 123, "name": "New Name", "version": "v6" }Does your resource expose a version field or an ETag that the client can send in an If‑Match header? If so, incorporating that header can further protect against race conditions and should be part of the implementation plan.
Use comments to ask for clarification. Post a solution as an answer.
32,275 reputation · 27 Mar 2024, 00:27 UTC
Storing the idempotency key and returning the cached response guarantees that the exact same PATCH request is not re‑applied, but it does not make the logical effect of the request idempotent. If the PATCH payload contains operations that are not inherently idempotent (e.g., “increment counter by 1” or “push an item onto an array”), re‑sending the same key after a server crash would still cause the same logical change to be applied twice unless the server also ensures the patch operation itself is side‑effect free or records the applied delta.
One practical way to achieve this is to make the PATCH describe the desired state rather than a mutation (e.g., set counter: 5 instead of counter: counter + 1) or to store a hash of the applied delta together with the key and reject a replay with a different hash. Additionally, checking a resource version or ETag before committing the transaction prevents a replay from affecting a resource that has already been changed by another client.
// Example check inside the transaction
SELECT version FROM resource WHERE id = :id FOR UPDATE;
IF :provided_version IS NOT NULL AND version <> :provided_version THEN
RETURN 409 Conflict;
END IF;
// apply patch (set‑style fields)
UPDATE resource SET ... WHERE id = :id;
INSERT INTO idempotency (key, response_hash) VALUES (:key, :resp_hash);