Implementing Cassandra Lightweight Transactions for Single-Row Linearizable Updates
A practical guide to using IF NOT EXISTS and conditional updates in Apache Cassandra to prevent race conditions, with version caveats, serial consistency configuration, and contention diagnostics.
12 May 2026, 15:04 UTC

The Problem: Concurrent Overwrites Without Coordination
Standard Apache Cassandra writes are asynchronous and eventually consistent. When two clients update the same row simultaneously, the database accepts both writes and resolves the conflict using timestamps, producing a last-write-wins outcome. For workflows such as account registration or inventory reservation, this can silently discard one update. Lightweight Transactions (LWTs) add compare-and-set (CAS) semantics so a write succeeds only if the row matches an expected condition.
The useful takeaway: LWTs are a correctness tool, not a throughput tool. They replace the normal write path with a Paxos consensus sequence that adds network round-trips, so they belong at specific synchronization points rather than on every write.
Scope and Prerequisites
LWTs provide linearizable consistency for a single partition. The operation appears to occur atomically at one point in time, but only for that partition. They do not provide multi-partition atomicity or cross-table transactions.
- Version support: LWT support was introduced in Cassandra 2.0. Exact behavior, driver defaults, and exception types have evolved across major releases; verify against your specific cluster and client library.
- Partition scope: The primary key in your
WHEREclause defines the atomic boundary. You cannot conditionally update rows across multiple partitions in one LWT. - Performance budget: LWTs run a multi-round-trip Paxos sequence instead of a standard hinted-handoff write path. Use them only where correctness outweighs added latency.
Writing Conditional CQL
An LWT is any INSERT or UPDATE statement that includes an IF clause. The condition is evaluated against the current state of the row before the mutation is applied.
Preventing Duplicate Inserts
Use IF NOT EXISTS to ensure a row is created only once. This is useful for idempotent registration flows where two clients might claim the same identifier at the same millisecond.
Run the following in cqlsh or through a driver. The executing role requires MODIFY permission on the target table.
INSERT INTO users (username, email, created_at)
VALUES ('example_user', '[contact removed]', toTimestamp(now()))
IF NOT EXISTS;
Expected check: The driver returns a result set containing a boolean column named [applied]. If true, the row was inserted. If false, the row already existed and no write occurred.
Guarding State Transitions
Use a column equality check to allow a transition only from a known state. This prevents two workers from advancing the same order or job twice.
UPDATE orders
SET status = 'SHIPPED', shipped_at = toTimestamp(now())
WHERE order_id = 12345
IF status = 'PENDING';
Expected check: If [applied] is false, the result set includes the current values of the columns referenced in the IF clause, so your application can decide whether to retry or abort.
Configuring Serial Consistency
LWTs use a separate consistency setting called serial_consistency to govern the Paxos phase. This is distinct from the regular write consistency level applied to the mutation itself.
| Setting | Scope | Behavior |
|---|---|---|
SERIAL |
All datacenters | Requires a quorum of replicas in each datacenter before the condition is considered agreed. |
LOCAL_SERIAL |
Local datacenter | Requires a quorum only within the coordinator's local datacenter. Lower latency, but does not coordinate the condition across datacenters. |
Do not assume a universal default. Some drivers default to SERIAL, others to LOCAL_SERIAL, and some require explicit configuration. Check your driver documentation and set the serial consistency level explicitly when your topology requires a specific behavior.
Verifying the Outcome
Because an LWT returns a result set rather than a simple acknowledgment, your application must inspect the [applied] column.
- applied = true: The condition matched and the mutation was committed.
- applied = false: The condition failed. The database state is unchanged. The result set also returns the current values of any columns referenced in the
IFclause, allowing your application to resolve the conflict.
Practical check: Run the INSERT ... IF NOT EXISTS example twice from two separate sessions. The first should return applied = true; the second should return applied = false along with the existing row values.
Diagnosing Timeouts and Contention
A WriteTimeoutException during an LWT does not automatically mean Paxos contention. It can also indicate replica unavailability, network partition, or overloaded nodes. Before attributing the failure to contention:
- Check replica health and gossip status by running
nodetool statuson a Cassandra node. This requires appropriate operating-system permissions on that node. - Review client-side timeout settings against your replication factor and chosen serial consistency level.
- If many concurrent LWTs target the same partition key, contention is likely. Reduce write frequency on that row, or redesign the access pattern to avoid a hot partition.
Limitations and Recovery
LWTs are not a general-purpose ACID transaction system. They are scoped to one partition, add latency, and can become a bottleneck under high contention on a single row.
- No multi-partition rollback: If
applied = false, nothing changed. Ifapplied = true, the write is committed. To revert a committed LWT, issue a new conditional or standard write that restores the prior state; there is no automatic transaction rollback. - Version sensitivity: Exact defaults, exception types, and driver APIs vary across Cassandra releases and client libraries. Validate behavior in a staging environment that matches your production topology.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.