Skip to content

Optimistic Concurrency Check

Concurrency-control protocol — instantiates Asynchronous Replica Convergence

Lets writers proceed without locks by stamping each record with a version and rejecting any write whose expected version no longer matches — catching the lost update instead of preventing it.

Optimistic Concurrency Check assumes conflicts are rare and lets every writer read and modify freely — but each write carries the version it expected to be updating, and the store accepts it only if the record still holds that version; otherwise the write is rejected and the writer must re-read and retry. Its defining move is detect, don't prevent: no lock is held during think-time, so throughput stays high, and the cost is paid only by the loser of an actual race, who is bounced rather than allowed to silently overwrite. It guards a single record's consistency by making a stale write fail loudly instead of quietly.

Example

Two support engineers open the same runbook article, version 12. Engineer A adds a troubleshooting step and saves; the store advances the article to version 13. Engineer B, still editing against version 12, tries to save — but her write announces "expected version 12," and the store now holds 13, so the save is rejected with an edit-conflict notice showing what changed underneath her. Nothing A wrote was silently clobbered. B re-reads version 13, re-applies her change on top, and saves as version 14. The check did not stop the two from editing at once — that is the point — it stopped the second save from erasing the first.

How it works

The distinguishing method is a version compared at write time, not a lock held at read time:

  • Read returns a version token. Every read hands back the value plus the record's current version.
  • Writes declare their expectation. The update carries the version it read.
  • Compare and set, atomically. The store applies the write only if the stored version equals the expected one, bumping the version in the same atomic step.
  • Reject on mismatch. If the versions differ, the write is refused and the conflict signalled, so the writer re-reads and retries rather than overwrites.

Tuning parameters

  • Version representation — monotonic counter, content hash / ETag, or timestamp; counters and hashes are safe across machines, while timestamps invite clock-skew misordering.
  • Conflict scope — whole-record versus per-field versioning; finer scope lets two edits to different fields both succeed, cutting false conflicts at the cost of more version metadata.
  • Retry policy — on rejection, auto-merge-and-retry, or bounce the conflict to the caller; automation smooths the common case but can loop under contention.
  • Contention posture — how much concurrency is tolerated before switching to pessimistic locking; optimism thrashes when writes to one record are frequent.

When it helps, and when it misleads

Its strength is high concurrency with no lock contention, and it renders the lost update anomaly impossible to miss rather than silent.[1] Its failure modes are the mirror of its assumption: under genuine high contention writers keep losing the race and retrying, wasting work in a livelock-like churn; timestamp versions misorder under clock skew; and — a hard boundary — it guards only a single record, so it cannot enforce an invariant spanning several. The classic misuse is treating the check as if it resolved conflicts when it only detects them, or picking wall-clock timestamps as versions and trusting them across machines. The discipline is to keep versions logical rather than clock-based, cap retries so contention degrades gracefully, and route cross-record invariants and actual merges elsewhere.

How it implements the components

  • version_token — the per-record version returned on read and checked on write; the artifact that makes a concurrent modification detectable at all.
  • state_consistency_guard — the compare-and-set gate that rejects any write built on a stale version, so the lost-update inconsistency cannot land for that record.

It does not mint the operation_identity_key that makes a retried request safe — that is Idempotency Keys; it detects a conflict but does not resolve it — the conflict_resolution_rule and the merge belong to Data Diff and Merge Tool, with human escalation to Exception Queue Review; and it does not carry causal_context_or_version_frontier across replicas — that is Version-Vector or Dotted-Context Exchange.

Notes

Its version_token names a record's version — "which revision of this data am I updating?" — which is a different question from the operation_identity_key of Idempotency Keys, which names an operation — "is this the same request I already saw?". The two tokens are easy to conflate and solve opposite problems: one detects a concurrent write, the other a duplicate request.

References

[1] The lost update problem — two transactions read the same value, both modify it, and the second write silently erases the first. Optimistic concurrency control converts that silent loss into an explicit, retryable rejection.