Cassandra Lightweight Transactions
A Lightweight Transaction (LWT) in Cassandra is a conditional read-modify-write operation that provides linearizable consistency — meaning you can safely check a condition and act on it atomically, even in a distributed environment. LWTs use the Paxos consensus algorithm and are significantly slower than regular reads and writes. Use them sparingly for situations where correctness is critical.
The Bank Account Analogy
Withdrawing money from a bank account requires checking the balance first, then deducting the amount — but only if the balance is still sufficient at the moment of deduction. Two simultaneous withdrawals could both read a $100 balance, both decide "$100 is enough for my $80 withdrawal," and both proceed — leaving the account at -$60. An LWT prevents this by locking the check-and-update into a single atomic step.
Without LWT (race condition):
Thread 1 reads balance: $100
Thread 2 reads balance: $100
Thread 1 deducts $80 → balance: $20
Thread 2 deducts $80 → balance: -$60 ← wrong!
With LWT:
Thread 1: UPDATE account SET balance = 20
WHERE account_id = X IF balance = 100; → applied ✓
Thread 2: UPDATE account SET balance = 20
WHERE account_id = X IF balance = 100; → NOT applied ✗
(balance is now 20)
LWT Syntax
INSERT IF NOT EXISTS
Inserts a row only when no row with the same primary key exists. Use this to prevent duplicate registrations.
INSERT INTO users (user_id, username, email) VALUES (uuid(), 'alice', 'alice@example.com') IF NOT EXISTS;
UPDATE IF EXISTS
Updates a row only when the row exists.
UPDATE users SET email = 'new@example.com' WHERE user_id = [uuid] IF EXISTS;
UPDATE IF condition
Updates a row only when the specified column currently holds the given value. This is compare-and-set (CAS).
-- Change status from 'pending' to 'processing' only if still pending: UPDATE orders SET status = 'processing' WHERE order_id = [uuid] IF status = 'pending';
DELETE IF EXISTS / IF condition
DELETE FROM sessions WHERE token = [uuid] IF EXISTS; DELETE FROM orders WHERE order_id = [uuid] IF status = 'cancelled';
The [applied] Response Column
Every LWT returns a boolean column named [applied]. When true, the operation succeeded. When false, the condition was not met and the operation was skipped. The response also includes the current values of the checked columns so you know the actual state.
-- Example: INSERT IF NOT EXISTS
INSERT INTO usernames (username, user_id)
VALUES ('alice', uuid())
IF NOT EXISTS;
Success (row did not exist):
[applied]
───────────
True
Failure (username already taken):
[applied] | user_id
───────────+──────────────────────
False | existing-user-uuid
How Paxos Works in Cassandra LWTs
LWTs use a four-phase Paxos protocol. This is why they cost roughly 4× the latency of a normal write.
Phase 1: Prepare Coordinator → Replica nodes: "I want to run a transaction on partition X" Replicas → Coordinator: "Granted (or denied if newer proposal exists)" Phase 2: Read (Promise) Coordinator reads the current value from a quorum of replicas → Determines the actual current state Phase 3: Propose Coordinator → Replicas: "Apply this conditional write" Replicas → Coordinator: "Accepted" Phase 4: Commit Coordinator → Replicas: "Commit the write" Replicas apply the write → ACK Result returned to client.
Serial Consistency Level
LWTs use a separate consistency level for the Paxos phases called serial consistency. The default is SERIAL (cross-datacenter). Use LOCAL_SERIAL to restrict Paxos rounds to the local data center, reducing latency in multi-DC setups.
-- In cqlsh:
SERIAL CONSISTENCY LOCAL_SERIAL;
INSERT INTO usernames (username, user_id)
VALUES ('bob', uuid())
IF NOT EXISTS;
// In Java driver:
SimpleStatement stmt = SimpleStatement.builder(
"INSERT INTO usernames (username, user_id) VALUES (?, ?) IF NOT EXISTS")
.setSerialConsistencyLevel(ConsistencyLevel.LOCAL_SERIAL)
.build();
LWT Performance Cost
Operation Approximate Cost ────────────────────────────────────────────────────────────── Normal write ~0.1–0.5 ms LWT write ~4–10 ms (4 Paxos phases + quorum reads)
LWTs require a quorum of replicas in every phase. In a high-availability setup, a node outage during Paxos causes LWT latency to spike dramatically. Never put LWTs in a high-throughput hot path.
Common LWT Use Cases
Use Case LWT Operation ────────────────────────────────────────────────────────────── User registration (unique name) INSERT IF NOT EXISTS Order state machine UPDATE SET status='X' IF status='Y' Account creation INSERT IF NOT EXISTS Seat reservation (tickets) UPDATE SET reserved=true IF reserved=false Token generation (unique) INSERT IF NOT EXISTS Idempotent job claiming UPDATE SET claimed=true IF claimed=false
LWT Pitfalls
Pitfall Consequence
──────────────────────────────────────────────────────────────
Using LWT for high-volume ops 4× latency degrades throughput
severely
Not checking [applied] response Application applies the change
even if the condition failed
Using SERIAL in multi-DC Cross-DC Paxos adds 100+ms latency
instead of LOCAL_SERIAL per operation
Batching LWTs Only one LWT per batch is allowed
on a single partition
Running LWT without quorum Paxos requires quorum; node failures
available cause LWT failures
Summary
Lightweight Transactions provide conditional, linearizable read-modify-write operations using Paxos consensus. Use INSERT IF NOT EXISTS for uniqueness constraints and UPDATE IF condition for compare-and-set state machines. Always check the [applied] response and handle the false case in your application. Use LOCAL_SERIAL consistency in multi-datacenter clusters. Reserve LWTs for low-volume, correctness-critical operations — their 4× cost makes them unsuitable for high-throughput workloads.
