Use versions and compare-and-set to reject stale writers, then distinguish local conditional mutation from consensus-backed lightweight transactions and general transactions.

Optimistic Concurrency, Compare-and-Set, Lightweight Transactions, and Conditional Mutations

Optimistic concurrency turns silent lost updates into explicit conflicts. AtlasMart uses versioned CAS to make the precondition observable and then examines the extra coordination cost of distributed conditional decisions.

Advanced105–135 minutesCAS and conditional-mutation labPython 3.13+ · standard libraryVendor-neutral · free/local mandatory pathLast reviewed: August 2026
01

Explain the read-modify-write race and lost update anomaly.

02

Use version-based compare-and-set (CAS) to reject stale writers.

03

Distinguish local conditional mutation from consensus-backed lightweight transactions and general multi-record transactions.

04

Reason about latency, contention, retry, and starvation costs under optimistic concurrency.

1. The problem: two correct clients can still erase each other

AtlasMart has a catalog price at version 7. Two administrators read it concurrently. One intends to change the price to 90; the other, based on the same old version, changes it to 80. A plain read-modify-write sequence has a lost update risk: both clients are individually correct, yet the later write silently erases the earlier one.

Optimistic concurrency control assumes conflicts are uncommon enough that clients may proceed without locking, then validate at commit time. Compare-and-set (CAS) means “apply this mutation only if the current version/value still equals what I observed.” A failed CAS is not a system error; it is evidence that the client’s premise became stale.

2. Versions make the precondition explicit

A version number, entity tag, generation, or monotonic revision can be used as the precondition. The critical point is that the comparison and mutation must be atomic at the authoritative owner. Reading version 7 and later issuing an unconditional write is still unsafe. A valid conditional mutation is conceptually UPDATE ... IF version = 7, not “check in application code, then write later.”

Mechanism Scope Conflict behavior Typical cost
Local CAS/version check One authoritative key/record Reject stale writer Extra retries under contention
Consensus-backed conditional / LWT Replicated conditional decision Linearizable compare-and-set semantics under stated assumptions Additional coordination/latency
General transaction Potentially multiple records/resources Atomic commit over larger boundary Locks/validation/logging/coordination depending on system

3. AtlasMart lab: stale writer versus CAS

Mandatory lab environment

Python 3.13+ standard library only. The generated lab was verified with Python 3.13.5. No database server, Docker, cloud account, paid feature, credential, firewall change, clock manipulation, or destructive failure injection is required. All failures are deterministic in-memory simulations.

The lab first demonstrates the lost update, then repeats the same history with a version precondition. A final conceptual replica vote illustrates why a distributed conditional mutation has a coordination cost; it is deliberately not presented as a vendor Paxos packet trace.

python · AtlasMart deterministic simulation
state = {"price": 100, "version": 7}

print("NAIVE READ-MODIFY-WRITE")
a_read = dict(state)
b_read = dict(state)
state["price"] = 90  # client A writes
state["price"] = 80  # stale client B overwrites A
print("final price:", state["price"], "lost A update:", state["price"] != 90)

print("\nVERSIONED COMPARE-AND-SET")
state = {"price": 100, "version": 7}
a_read = dict(state)
b_read = dict(state)

def cas_price(expected_version, new_price):
    if state["version"] != expected_version:
        return False
    state["price"] = new_price
    state["version"] += 1
    return True

print("A CAS applied:", cas_price(a_read["version"], 90), state)
print("B stale CAS rejected:", not cas_price(b_read["version"], 80), state)

print("\nCONSENSUS-BACKED CONDITIONAL (CONCEPTUAL)")
replicas = {"r1": {"version": 8, "price": 90}, "r2": {"version": 8, "price": 90}, "r3": {"version": 8, "price": 90}}
quorum = 2
expected = 8
proposal = 85
votes = [name for name, value in replicas.items() if value["version"] == expected]
print("matching-version votes:", votes, "quorum reached:", len(votes) >= quorum)
if len(votes) >= quorum:
    for name in votes:
        replicas[name] = {"version": 9, "price": proposal}
print("replica state:", replicas)
print("note: this models the decision contract, not a vendor Paxos message trace")
Expected evidence

The naive path ends at price 80 and loses client A’s update. CAS applies A’s version-7 mutation and rejects B’s stale version-7 mutation after the record advances to version 8. The conceptual replica step reaches a quorum on version 8 and advances participating replicas to version 9. It demonstrates the decision contract, not implementation-specific message counts.

4. Lightweight transactions are not “free transactions”

Some distributed databases expose conditional writes backed by consensus. Cassandra, for example, documents IF/IF NOT EXISTS conditions as lightweight transactions implemented with Paxos and warns of non-negligible performance cost. The important abstraction is not the product name: stronger conditional semantics require additional coordination among replicas and can reduce throughput or increase tail latency under contention.

Do not conflate a single-partition conditional mutation with an arbitrary multi-record ACID transaction. The former protects a precondition over a narrow ownership boundary; the latter may need a larger commit protocol.

5. Deliberately wrong approach: retry CAS forever

A stale CAS should trigger a re-read and business decision, not an unbounded blind retry loop. Under hot-key contention, immediate retries can create a retry storm and starve useful work. Use bounded retries, jitter/backoff where appropriate, clear conflict responses to callers, and metrics such as conflict rate and retry depth. If conflicts are routine rather than exceptional, the model may need a queue, single-writer owner, partitioning change, or reservation design.

6. Production judgment and bridge

Use optimistic/CAS-style control when conflicts are relatively rare, losing an update is unacceptable, and one authority can evaluate the precondition. Test contention, failover, timeout ambiguity, retry idempotency, and stale clients. Record which version source is authoritative and whether version reuse can occur after restore or migration. The next lesson increases the scope again: when multiple participants must all commit or all abort, two-phase commit coordinates an atomic decision but introduces prepared state and blocking failure modes.

Check your understanding

  1. Why is an application-side check followed by an unconditional write unsafe?
  2. What does a failed CAS mean?
  3. Why can consensus-backed conditionals be slower?
  4. Is a lightweight transaction automatically a general distributed transaction?
  5. What operational signal suggests optimistic concurrency is a poor fit?
Review the answers

1. Another writer can change the record between the check and the write; the comparison and mutation must be one atomic operation at the authority.

2. The observed precondition is stale; it is a concurrency outcome that requires re-read or business resolution.

3. Replicas must coordinate on the conditional decision rather than accepting an ordinary local mutation path.

4. No. Its scope and guarantees are product-defined and often much narrower than arbitrary multi-resource atomic commit.

5. Persistently high conflict/retry rates or hot-key contention that makes retries dominate useful work.

References

Foundational claims are vendor-neutral. Product documentation is used only as a current implementation example and is not required for the mandatory labs.

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only Ethereum or ERC-20 compatible assets to this address.