Chapter 16 · Lightweight Transactions, Paxos, CAS, and Linearizable Conditional Updates

IF NOT EXISTS and IF Conditions: Compare-and-Set Semantics in CQL

Use CQL IF NOT EXISTS and IF conditions as observable compare-and-set operations, then prove winner/loser behavior under concurrent contention.

Intermediate → Advanced105–145 minutesCAS + concurrent contender labApache Cassandra 5.0.9 · Java 17 · cqlsh/nodetool · Java Driver 4.19.3 optional · RF=3 dc1 · UCSLast reviewed: September 2026

Learning outcomes

AtlasMart must guarantee that only one account can claim a promotional code and that an inventory reservation is accepted only when the expected state still holds. A normal last-write-wins update can overwrite a concurrent decision. This lesson introduces Cassandra's narrow tool for that problem: a Paxos-backed conditional mutation that reports whether the condition was actually applied.

01

Explain IF NOT EXISTS and IF conditions as compare-and-set, not as ordinary upserts with extra syntax.

02

Interpret the [applied] result and current-value columns returned by failed conditions.

03

Separate serial consistency for the Paxos phase from regular consistency for the learned mutation.

04

Run concurrent claim attempts and prove that one logical winner is chosen for the protected key.

05

Recognize boundaries: LWT is not a cross-partition relational transaction and custom client timestamps are not allowed for conditional updates.

Chapter 16 lab baseline

The mandatory labs continue the disposable AtlasMart course cluster: Apache Cassandra 5.0.9 in the pinned cassandra:5.0.9 image, Java 17 inside the image, cluster atlasmart-course, Docker network atlasmart-cassandra, nodes atlasmart-cass-1..3, datacenter dc1, racks rack1..rack3, 16 virtual nodes per node, NetworkTopologyStrategy with replication factor (RF) 3, and regular consistency level (CL) LOCAL_QUORUM unless an experiment says otherwise. New tables use UnifiedCompactionStrategy (UCS), gc_grace_seconds = 864000 unless explicitly isolated for an exercise, and no default TTL. Authentication, client TLS, internode TLS, and remote JMX are disabled only inside this isolated local learning network. The optional application examples use Apache Cassandra Java Driver 4.19.3. Verify your actual runtime with nodetool version, cqlsh --version, and java -version. This lesson uses only one DC, so SERIAL and LOCAL_SERIAL have similar replica scope here; Lesson 3 adds the multi-DC distinction.

Execution and safety note

Run commands only against the disposable Apache Cassandra course lab or another explicitly approved non-production environment. Confirm node, keyspace, table, container, volume, path, and datacenter targets before destructive, failure-injection, cleanup, repair, restore, security, or topology operations. Capture current state and expected rollback/recovery evidence first; output and timings can differ by host, operating system, Java runtime, Docker/runtime, driver, and Cassandra configuration.

Core terms for this chapter

A lightweight transaction (LWT) is Cassandra's conditional mutation mechanism. It uses the Paxos consensus protocol so competing operations on the same logical Paxos scope can agree on one ordered outcome. Compare-and-set (CAS) means “apply this mutation only if the current value satisfies the condition.” A conditional mutation is CQL such as INSERT ... IF NOT EXISTS or UPDATE ... IF column = value. Linearizable means successful operations appear to occur in a single real-time-compatible order within the guarantee's documented scope; it is stronger than ordinary eventual consistency.

A coordinator is the Cassandra node handling one request. A replica is a node that stores the partition according to the keyspace replication strategy. A partition groups rows by partition key and maps through the partitioner to a token; the token determines natural replicas. RF is replication factor. A regular CL controls the data/learn phase of the operation. SERIAL and LOCAL_SERIAL are serial consistency levels that control the Paxos phase. A ballot is the proposal identity/order used by Paxos rounds. Contention occurs when concurrent conditional operations compete for the same Paxos state. A hot partition is a partition receiving disproportionately high request volume. A retry repeats an operation after an error or timeout; for LWT, an ambiguous outcome must be reconciled before blind retry. SSTables are immutable on-disk table files, while compaction rewrites SSTables. repair is Cassandra's anti-entropy process and is separate from Paxos agreement. Storage-Attached Indexing (SAI) and vector search can help locate rows but do not create uniqueness or cross-row transactional invariants.

1. What [applied] actually means

INSERT ... IF NOT EXISTS asks Cassandra to install the row only if the target primary key is absent at the linearization point. UPDATE ... IF ... compares current values and applies the mutation only if the predicate is satisfied. Cassandra returns a special result whose [applied] column is True for the winner and False for a rejected condition. On rejection, Cassandra can return current values needed to understand why the compare failed. This is not a SQL transaction block: the guarantee is attached to the conditional operation and its Paxos scope.

Operation Condition Successful result Rejected result
INSERT ... IF NOT EXISTS target primary key absent [applied]=True and row created [applied]=False; existing row remains
UPDATE ... IF revision=7 current revision exactly 7 new values become chosen mutation condition false; no requested mutation
normal INSERT/UPDATE none last-write-wins mutation accepted by normal write path no CAS winner/loser decision

2. Reproduce a unique claim and a compare-and-set update

bash · verify the disposable AtlasMart cluster
docker exec atlasmart-cass-1 nodetool versiondocker exec atlasmart-cass-1 nodetool statusdocker exec atlasmart-cass-1 java -versiondocker exec atlasmart-cass-1 cqlsh -e "SELECT cluster_name, data_center, rack, release_version FROM system.local;"docker exec atlasmart-cass-1 cqlsh -e "SELECT peer, data_center, rack, release_version FROM system.peers_v2;"# Continue only when all three nodes are UN in dc1.# If the course cluster is absent, recreate it with the same Chapter 01 conventions# and pinned cassandra:5.0.9 image before running this chapter.
CQL · create the Chapter 16 invariant fixtures
CREATE KEYSPACE IF NOT EXISTS atlasmart_lwtWITH replication = {'class':'NetworkTopologyStrategy','dc1':3};CREATE TABLE IF NOT EXISTS atlasmart_lwt.inventory_guard (    sku text PRIMARY KEY,    available int,    reservation_owner text,    revision int) WITH compaction = {'class':'UnifiedCompactionStrategy'};CREATE TABLE IF NOT EXISTS atlasmart_lwt.unique_claim (    claim_type text,    claim_value text,    owner_id text,    created_at timestamp,    PRIMARY KEY ((claim_type, claim_value))) WITH compaction = {'class':'UnifiedCompactionStrategy'};CREATE TABLE IF NOT EXISTS atlasmart_lwt.order_projection (    order_id text PRIMARY KEY,    customer_id text,    state text,    updated_at timestamp) WITH compaction = {'class':'UnifiedCompactionStrategy'};CONSISTENCY LOCAL_QUORUM;SERIAL CONSISTENCY LOCAL_SERIAL;INSERT INTO atlasmart_lwt.inventory_guard(sku, available, reservation_owner, revision)VALUES ('sku-42', 10, 'NONE', 0);
CQL · one winner, then a rejected duplicate claim
CONSISTENCY LOCAL_QUORUM;SERIAL CONSISTENCY LOCAL_SERIAL;DELETE FROM atlasmart_lwt.unique_claim WHERE claim_type='promo' AND claim_value='WELCOME-42';INSERT INTO atlasmart_lwt.unique_claim(claim_type, claim_value, owner_id, created_at)VALUES ('promo','WELCOME-42','customer-a',toTimestamp(now()))IF NOT EXISTS;-- Re-run with a different owner. The second contender should not overwrite the winner.INSERT INTO atlasmart_lwt.unique_claim(claim_type, claim_value, owner_id, created_at)VALUES ('promo','WELCOME-42','customer-b',toTimestamp(now()))IF NOT EXISTS;SELECT * FROM atlasmart_lwt.unique_claimWHERE claim_type='promo' AND claim_value='WELCOME-42';
CQL · compare expected revision before reserving inventory
UPDATE atlasmart_lwt.inventory_guardSET available=9, reservation_owner='order-9001', revision=1WHERE sku='sku-42'IF available=10 AND revision=0;-- This stale compare must be rejected instead of silently overwriting order-9001.UPDATE atlasmart_lwt.inventory_guardSET available=9, reservation_owner='order-9002', revision=1WHERE sku='sku-42'IF available=10 AND revision=0;SELECT * FROM atlasmart_lwt.inventory_guard WHERE sku='sku-42';
Expected evidence, not a fabricated transcript

On an untouched fixture, the first condition should report [applied]=True and the stale duplicate should report False. The exact columns printed on a rejected condition, trace events, host addresses, and timing depend on Cassandra/cqlsh version and current state. Capture your own output and verify the final row.

3. Concurrent CAS contenders

Sequential replays prove condition semantics but not contention. The following local PowerShell exercise starts several independent cqlsh clients that all compete to change one row from UNCLAIMED. Only the operation whose condition is chosen while the expected state is still current should be applied. Job scheduling can change which worker wins; winner identity is intentionally nondeterministic.

CQL · create the contention target
CREATE TABLE IF NOT EXISTS atlasmart_lwt.claim_once (    resource_id text PRIMARY KEY,    owner text) WITH compaction = {'class':'UnifiedCompactionStrategy'};INSERT INTO atlasmart_lwt.claim_once (resource_id, owner)VALUES ('flash-sale-slot','UNCLAIMED');
PowerShell · launch eight concurrent conditional contenders
$jobs = 1..8 | ForEach-Object {  $n = $_  Start-Job -ArgumentList $n -ScriptBlock {    param($n)    docker exec atlasmart-cass-1 cqlsh -e "CONSISTENCY LOCAL_QUORUM; SERIAL CONSISTENCY LOCAL_SERIAL; UPDATE atlasmart_lwt.claim_once SET owner='worker-$n' WHERE resource_id='flash-sale-slot' IF owner='UNCLAIMED';"  }}$jobs | Wait-Job | Receive-Job$jobs | Remove-Jobdocker exec atlasmart-cass-1 cqlsh -e "SELECT * FROM atlasmart_lwt.claim_once WHERE resource_id='flash-sale-slot';"

4. Wrong approach: “make every write an LWT”

If AtlasMart wraps ordinary telemetry inserts, order projections, cache-like session updates, and append-only events in IF conditions “for safety,” every write pays Paxos coordination even when there is no invariant to arbitrate. That increases network round trips, coordinator work, tail latency, and contention exposure. The repair is architectural: reserve LWT for keys where two valid-looking concurrent decisions must be serialized, and keep idempotent/normal writes on the regular path.

Another boundary: no custom timestamps on LWT.

Conditional updates reject CQL USING TIMESTAMP. Through the Java driver, client-generated timestamps are ignored for LWT and the server assigns the timestamp. Do not design CAS correctness around an application-supplied microsecond timestamp.

CQL · verify/reset the lesson fixtures
SELECT * FROM atlasmart_lwt.unique_claim WHERE claim_type='promo';SELECT * FROM atlasmart_lwt.inventory_guard WHERE sku='sku-42';SELECT * FROM atlasmart_lwt.claim_once WHERE resource_id='flash-sale-slot';-- Reset only the disposable Chapter 16 fixtures when you want to repeat the lab.DELETE FROM atlasmart_lwt.unique_claim WHERE claim_type='promo' AND claim_value='WELCOME-42';UPDATE atlasmart_lwt.inventory_guard SET available=10, reservation_owner='NONE', revision=0 WHERE sku='sku-42';UPDATE atlasmart_lwt.claim_once SET owner='UNCLAIMED' WHERE resource_id='flash-sale-slot';

5. Verification checklist

  • Record regular CL and serial CL separately.
  • Capture at least one [applied]=True and one [applied]=False result.
  • Run concurrent contenders and verify only one final owner exists.
  • Explain why a rejected condition is a normal business outcome, not automatically an infrastructure error.
  • Explain why the same mechanism does not make two unrelated partitions one relational transaction.

Check your understanding

  1. What does [applied]=False mean?
  2. Why are regular CL and serial CL separate?
  3. Does IF NOT EXISTS make an arbitrary query result globally unique?
  4. Can a client force USING TIMESTAMP on a conditional update?
  5. Why is “LWT for every write” usually a poor default?
Review the answers

1. The condition did not hold at the linearizable compare point, so Cassandra did not apply the requested conditional mutation; it is often an expected contention/business result.

2. Serial CL scopes the Paxos phase; regular CL controls the learned/data mutation visibility requirements after consensus.

3. No. It protects the conditional primary-key scope you model; a search/index query is not a uniqueness lock.

4. No. CQL rejects custom timestamps for conditional updates, and driver timestamp generators are ignored for LWT.

5. It pays consensus coordination and contention costs even when no true invariant requires arbitration.

Production judgment

LWT is an invariant tool, not a “strong consistency” checkbox for every write. Before using it, define the exact invariant, its partition/key scope, contender cardinality, expected concurrency, RF and datacenter placement, regular and serial CL, failure behavior, acceptable p95/p99 latency, retry/reconciliation rules, and how a timed-out outcome will be discovered. Measure CASRead/CASWrite latency, timeouts, failures, unavailables, condition-not-met counts, and contention histograms alongside ordinary request latency, CPU, JVM garbage collection, network, disk, compaction, repair state, and hot-key distribution.

Do not infer a universal LWT throughput number from this laptop lab. SAI/vector indexes do not make a search result safe as a uniqueness lock; security/tenant boundaries require authorization and data-model controls, not LOCAL_SERIAL. Managed Cassandra services can constrain JMX, Paxos variants, or topology settings, so translate the same invariant and evidence model to the service's supported telemetry. Migration and rollback must account for application semantics: replacing a normal write with LWT can change latency and availability, while removing LWT can silently weaken an invariant. Lesson 2 opens the Paxos mechanism and compares its round trips and CAS latency evidence with the regular write path.

Summary and next bridge

Cassandra conditional statements are compare-and-set operations with explicit winner/loser evidence. They solve a narrow concurrency problem; they do not create a general relational transaction manager. Next, follow the Paxos phases and measure why LWT needs more coordination than a regular write.

Authoritative references

Use these current official sources as the version-sensitive source of truth. Re-check them when regenerating this chapter because Paxos variants, driver behavior, metrics, and operational recommendations can evolve.

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.