Skip to main content
Articles/Databases/compare-and-swap-optimistic-locking
Scenario Article

Compare-and-Swap and Optimistic Locking: How Every Database Implements It

The 47-Second Window That Broke a Ride-Share Platform

Abstract Algorithmsβ€’Apr 17, 2026
XLinkedInReddit
πŸ’‘ Executive Summary

Compare-and-Swap (CAS) is the CPU-level atomic instruction that makes lock-free concurrency possible.

🎯 System Scenario & Requirements

System Goal

Compare-and-Swap (CAS) is the CPU-level atomic instruction that makes lock-free concurrency possible.

Actors & Components

  • Application Workers
  • Volatile Memory Tier
  • Persistent Disk Tier
  • Worker Process
  • In-Memory Cache
  • Primary Database

Functional Requirements

  • TLDR: Compare-and-Swap (CAS) is the CPU-level atomic instruction that makes lock-free concurrency possible.
  • Optimistic locking builds on it at the database layer: read freely, compute locally, write only if the record has not changed.
  • Every major database β€” PostgreSQL, MySQL, DynamoDB, Redis, MongoDB, CockroachDB, and Cassandra β€” implements CAS semantics differently.

Constraints & Assumptions

  • A CAS failure is not an error β€” it is a signal to retry.
  • But how you retry determines whether your system recovers gracefully or degrades into a retry storm.

⚑ Execution Stages & Decision Trace

1

Stage 1 β€” The CPU Instruction Behind Every Database Concurrent Write

Action / Decision:

Compare-and-Swap (CAS) is an atomic CPU instruction.

Component Behavior:

"Atomic" means it executes as a single, indivisible operation β€” no other CPU core can observe an intermediate state.

Observable Output: The semantics are simple: On x86-64 CPUs, this maps to the CMPXCHG instruction.
Read Full Stage in Deep Dive ↓
πŸ“–

Deep Dive

Detailed Architectural Analysis

Scenario context

Compare-and-Swap (CAS) is the CPU-level atomic instruction that makes lock-free concurrency possible. Optimistic locking builds on it at the database layer: read freely, compute locally, write only if the record has not changed. Every major database β€” PostgreSQL, MySQL, DynamoDB, Redis, MongoDB, CockroachDB, and Cassandra β€” implements CAS semantics differently. Choosing the wrong implementation, or retrying naΓ―vely,…

This walkthrough focuses on The 47-Second Window That Broke a Ride-Share Platform.

Before making a design choice, clarify:

  • At 08:14:32 UTC on a weekday morning, a ride-sharing platform's dispatch service and billing service both received an event for driver ID D-7741 .
  • The dispatch service read the driver record: status = AVAILABLE , version = 42 .
  • Simultaneously β€” 23 milliseconds later β€” the billing service read the same row: status = AVAILABLE , version = 42 .

Concepts in play: caching, transaction-isolation-distributed-acid, stack-and-queue-data-structures, consistency-replication

Stage 1 β€” The CPU Instruction Behind Every Database Concurrent Write

  • Compare-and-Swap (CAS) is an atomic CPU instruction.
  • "Atomic" means it executes as a single, indivisible operation β€” no other CPU core can observe an intermediate state.
  • The semantics are simple: On x86-64 CPUs, this maps to the CMPXCHG instruction.
  • On ARM processors, the equivalent is a LDREX / STREX pair (Load-Reserved / Store-Conditional).
CAS(memory_location, expected_value, new_value) β†’ boolean
  if *memory_location == expected_value:
    *memory_location = new_value
    return true
  else:
    return false   // nothing changed

Observable outcome: On ARM processors, the equivalent is a LDREX / STREX pair (Load-Reserved / Store-Conditional).

Stage 2 β€” How CAS Propagates from Hardware to Distributed Storage β€” The Abstraction Stack

  • CAS does not stay at the hardware level.
  • Every layer of software above the CPU builds on it, abstracting the raw memory instruction into higher-level constructs.
  • Understanding this stack tells you why database version columns and DynamoDB ConditionExpression s are not arbitrary design choices β€” they are CAS, two layers up.
  • Each layer in this diagram adds latency and abstraction, but preserves the same semantic: compare the current state to what you expect, and only write if they match .
graph TD
    A[x86 CMPXCHG / ARM LDREX+STREX] --> B[OS mutex and spinlock primitives]
    B --> C[Language atomics - Java AtomicInteger - Go sync/atomic]
    C --> D[Database version column + conditional UPDATE]
    D --> E[Distributed conditional write - DynamoDB ConditionExpression - Cassandra LWT]

Observable outcome: Each layer in this diagram adds latency and abstraction, but preserves the same semantic: compare the current state to what you expect, and only write if they match .

Stage 3 β€” How Optimistic Locking Builds on CAS β€” The Version Column Pattern

  • The version column pattern is the canonical database implementation of CAS.
  • The mechanics are straightforward once you see them as a CAS in disguise: 1.
  • Read: SELECT id, data, version FROM records WHERE id = ?
  • β€” record the version number alongside the data.
sequenceDiagram
    participant T1 as Transaction T1
    participant DB as Database
    participant T2 as Transaction T2

    T1->>DB: SELECT id, status, version WHERE id=D-7741
    DB-->>T1: status=AVAILABLE, version=5
    T2->>DB: SELECT id, status, version WHERE id=D-7741
    DB-->>T2: status=AVAILABLE, version=5

    T1->>DB: UPDATE drivers SET status=ASSIGNED, version=6 WHERE id=D-7741 AND version=5
    DB-->>T1: rows_affected=1 - success

    T2->>DB: UPDATE drivers SET billing_cycle=OPEN, version=6 WHERE id=D-7741 AND version=5
    DB-->>T2: rows_affected=0 - conflict detected

    Note over T2,DB: T2 retries - re-reads fresh state
    T2->>DB: SELECT id, status, version WHERE id=D-7741
    DB-->>T2: status=ASSIGNED, version=6
    T2->>DB: UPDATE drivers SET billing_cycle=OPEN, version=7 WHERE id=D-7741 AND version=6
    DB-->>T2: rows_affected=1 - success

Observable outcome: β€” record the version number alongside the data.

Stage 4 β€” How Each Database Implements CAS β€” Internals and Performance

  • Each database engine exposes CAS semantics through a different internal mechanism β€” some via SQL predicates, some via distributed consensus protocols, and some via atomic scripting.
  • This section covers the internal machinery of each and closes with a comparative latency analysis.
  • CAS Internals: How Each Database Engine Handles the Compare-and-Swap PostgreSQL β€” Explicit Version Columns and the Hidden xmin PostgreSQL gives you two distinct approaches to optimistic locking, and choosing between the…
  • Approach 1: Explicit version column.
-- Schema
ALTER TABLE drivers ADD COLUMN version INTEGER NOT NULL DEFAULT 1;

-- Read
SELECT id, status, billing_cycle, version
FROM drivers
WHERE id = 'D-7741';

-- Write (CAS attempt)
UPDATE drivers
SET    status       = 'ASSIGNED',
       ride_id      = 'R-9901',
       version      = version + 1
WHERE  id           = 'D-7741'
AND    version      = 5
RETURNING version;
-- If RETURNING returns a row: success (new version = 6)
-- If RETURNING returns no rows: conflict (version was not 5)

Observable outcome: Approach 1: Explicit version column.

Stage 5 β€” The Complete CAS Write Flow β€” From Client Request to Committed State

  • The diagram below shows the complete request lifecycle for an optimistic write, from the client HTTP call through the service layer to the database, with explicit success and retry paths.
  • This architecture shows three important design decisions.
  • First, the service layer performs all computation locally β€” the database only executes the CAS predicate, not business logic.
  • Second, the retry loop re-reads from the database before recomputing, ensuring each attempt operates on committed state.
graph TD
    A[Client - HTTP PATCH /drivers/D-7741] --> B[Service Layer]
    B --> C[Read driver record and version]
    C --> D[Database Read Replica or Primary]
    D --> E[Return record with version=N]
    E --> F[Compute new state locally]
    F --> G[Attempt CAS write - UPDATE WHERE version=N]
    G --> H{Database CAS result}
    H -->|rows_affected=1 - success| I[Return 200 OK with new version]
    H -->|rows_affected=0 - conflict| J[Re-read fresh state]
    J --> K{Retry count under limit?}
    K -->|Yes| L[Apply backoff and jitter]
    L --> F
    K -->|No| M[Return 409 Conflict to client]
    I --> N[Emit audit event and downstream notifications]

Observable outcome: Second, the retry loop re-reads from the database before recomputing, ensuring each attempt operates on committed state.

Stage 6 β€” How Stripe, GitHub, and Amazon Use CAS in Production

  • Stripe uses version columns on PaymentIntent records.
  • Every state transition (created β†’ processing β†’ succeeded β†’ captured) is guarded by a CAS write against the current state value.
  • If two webhook events try to advance the same intent simultaneously, only one succeeds.
  • Stripe's idempotency key mechanism ensures safe retry of the losing event.

Observable outcome: Stripe's idempotency key mechanism ensures safe retry of the losing event.

End-to-end outcome

After working through the stages, you should be able to explain:

  • Always re-read before recomputing on retry.
  • A retry that reuses stale read data is not a retry β€” it is a repeated mistake.
  • Compare-and-Swap is the hardware foundation of all lock-free concurrency.
  • The CMPXCHG instruction on x86 CPUs provides an atomic compare-and-swap in a single clock cycle; every database optimistic locking primitive is this instruction expressed at a higher layer of abstraction.

Alternatives considered

Optimistic vs. Pessimistic Locking β€” Choosing the Right Concurrency Strategy

Before going database-by-database, it is worth internalising the trade-off between the two dominant concurrency strategies, because choosing the wrong one is a performance cliff.

When Optimistic Locking Fails β€” The High-Contention Decision Guide

Optimistic locking excels when conflicts are rare.

Failure and degraded modes

Retry Strategy Patterns and Their Production Trade-offs

  • Detection: A CAS failure is not an error β€” it is a signal to retry.
  • User or operator impact: A CAS failure is not an error β€” it is a signal to retry.

Share this case study

Discuss architecture trade-offs and lessons with your team.

Share:XLinkedInReddit