Course outline

GraphQL / Advanced: bounded and distributed execution / Lesson 14

Idempotent writes, conflicts and transactions

Protect business effects across retries with a deliberate idempotency and revision contract.

About 4 min readContent reviewed
On this page

Lesson goals

  • Separate request identity, object identity and optimistic revision.
  • Reason about duplicate requests and atomic domain boundaries.

Design around a lost response after commit

A mutation can commit successfully while its response is lost. A blind retry may then repeat an external effect or return a conflict that hides the first success. An idempotency contract associates a stable request key with a principal, operation and canonical input fingerprint, then returns the same recorded outcome for a legitimate replay. Object ID and operation name alone are insufficient because one object can receive many distinct edits. Key scope, retention, concurrent duplicate handling and recovery must be explicit in the domain implementation.

Keep idempotency separate from stale-write detection

An expectedRevision detects that another accepted edit changed the object after the client read it. A requestKey identifies this particular logical attempt across transport retries. They solve different problems and may appear together. The input below is a schema design example, not an implementation of atomic storage. A reused key with different input should be rejected under a stable conflict policy. Validate ownership and permitted state transitions before the write, and avoid returning another principal’s recorded outcome if an attacker guesses a request key.

graphql Distinct write identities
input PublishCourseInput {
  id: ID!
  expectedRevision: Int!
  requestKey: String!
}
type PublishCoursePayload {
  course: Course
  problems: [Problem!]!
}

expectedRevision protects a read-modify-write precondition; requestKey supports replay recognition. The domain service must implement both atomically where required.

Record the effect and replay outcome coherently

Checking for a request key and then writing in separate unprotected steps allows concurrent duplicates to pass the check. The durable change and idempotency outcome need a coherent storage protocol, commonly a transaction with uniqueness constraints appropriate to the chosen source. External effects cannot automatically join that transaction. A transactional outbox can record intended delivery alongside the change, while delivery and consumers still require retry and deduplication rules. GraphQL supplies a mutation entry point; it does not make these distributed consistency guarantees on behalf of the application.

Model replay identity without a storage claim

This synchronous in-memory model increments a synthetic revision once and returns the same result for an identical replay. Reusing the key with a different fingerprint raises a conflict. The input fingerprint is supplied as a literal for clarity; production canonicalization needs an explicit format and safe handling of sensitive input. The model has no parallelism, persistence or crash recovery, so it does not establish a production idempotency protocol. Its purpose is to make the distinction between a new business edit and retransmission visible in a small test.

javascript Offline replay model
const seen = new Map();
let revision = 4;
function publish(key, fingerprint) {
  if (seen.has(key)) {
    const old = seen.get(key);
    if (old.fingerprint !== fingerprint) throw new Error("KEY_REUSED");
    return old.result;
  }
  const result = {revision: ++revision};
  seen.set(key, {fingerprint, result});
  return result;
}
console.assert(publish("r1", "c1@4").revision === 5);
console.assert(publish("r1", "c1@4").revision === 5);
console.assert(revision === 5);

The identical replay does not increment again in this single-process fixture. Concurrent and crash-safe behavior remains unimplemented.

Test the uncertain and conflicting paths

A write test matrix should include first success, identical replay, key reused with different input, concurrent duplicate, stale revision and response loss after commit. Verify that expected conflicts produce a stable domain outcome and no additional effect. Define how long keys remain replayable and what a client should do after that retention expires. Store only necessary outcome information under appropriate privacy controls. Reconciliation should read the authoritative operation or object state rather than inventing success from a client’s optimistic cache.

Common errors and fixes

A retry publishes twice

Why it happens
Transport retries had no stable business request identity.
How to fix it
Implement scoped replay recognition and reconcile the original result.
Prevent it next time
Test response loss after committed success.

Two duplicates both pass the initial lookup

Why it happens
Check and write were not atomically coordinated.
How to fix it
Use a durable uniqueness/transaction protocol for the selected store.
Prevent it next time
Review concurrent duplicate and crash boundaries.

A stale revision is confused with a replay

Why it happens
Object concurrency and request identity shared one field.
How to fix it
Define expectedRevision and requestKey separately.
Prevent it next time
Test a new edit and a retransmission independently.

Check your understanding

1. Why should the same request key with changed input fail?

Show worked solution

The key represents one logical request and its recorded outcome. Accepting changed input under that identity makes replay semantics ambiguous and can return an unrelated result or repeat a new effect invisibly. Bind the key to a canonical input fingerprint, principal and operation scope, and return a stable safe conflict when that binding does not match.

2. Does serial top-level mutation execution solve concurrent duplicate HTTP requests?

Show worked solution

No. The serial guarantee applies to top-level fields within an executed mutation operation, not a global lock across separate requests. Two requests may race in the domain service. Use the storage protocol’s atomicity and uniqueness mechanisms, then test duplicate races and uncertain response recovery in an isolated environment authorized for that integration.

Key takeaways

  • Idempotency keys and revisions protect different write concerns.
  • GraphQL mutation ordering does not implement durable replay safety.

Official references