Concurrency Comparisons

Side-by-side trade-offs where neither column wins. The workload, the runtime and how much correctness risk you can carry decide — and each comparison ends with the verdict that follows from that, not from a preference.

Optimistic vs pessimistic concurrency control

Two answers to "two clients want to change the same thing". Lock it up front, or let both proceed and detect the collision at write time. The conflict rate decides, and almost nobody measures it before choosing.

DimensionOptimisticPessimistic
AssumptionConflicts are rareConflicts are common, or the cost of one is high
MechanismVersion or timestamp checked at write; retry on mismatchAcquire a lock before reading, hold it until commit
Cost with no conflictAlmost nothing — one extra compareFull lock acquisition, plus everyone else waiting
Cost with conflictWasted work plus a retry, possibly repeatedWaiting, but the work is done once
Deadlock riskNone — no locks are heldReal, and grows with the number of locks
Behaviour under high contentionDegrades badly — retries multiply and can livelockDegrades predictably into a queue
User-visible failure"Someone else changed this — reload and retry"A slow request, or a lock timeout
Fits a stateless APINaturally — the version travels in the requestPoorly — holding a lock across a user think-time is a trap
Use Optimistic when
  • Measured conflict rate is low — a few percent of writes.
  • Retrying is cheap and safe: the work is idempotent or recomputable.
  • The client can present a sane "changed underneath you" outcome.
Use Pessimistic when
  • Contention on the same row is genuinely high.
  • Redoing the work is expensive, or has side effects you cannot repeat.
  • You need a queue rather than a retry storm at peak.
Verdict

Start optimistic and measure the conflict rate. Switch to pessimistic when retries stop being rare — the crossover is where wasted work exceeds waiting time, and it arrives sooner than intuition suggests on hot rows.