Advanced Topics

Lightweight Transactions

Master Cassandra's LWT using Paxos consensus for compare-and-set operations with ACID-like guarantees!

⚖️ What are Lightweight Transactions (LWT)?

The Bank Account Problem 💰

Imagine two people trying to withdraw from the same bank account simultaneously:

  • 📱 Person A: Checks balance ($100), tries to withdraw $80
  • 📱 Person B: Checks balance ($100), tries to withdraw $80
  • 💥 Problem: Both see $100, both withdraw $80 = $160 withdrawn from $100!
  • 🏦 Result: Account overdrawn, bank loses money!

LWT solves this! It ensures only ONE person can update based on a condition, using distributed consensus (Paxos).

LWT in Simple Terms

Lightweight Transactions = Compare-and-Set operations with linearizable consistency in Cassandra. Think of it as "only update if current value matches my expectation."

Key Concepts:
  • IF Clause: Conditional updates/inserts
  • Paxos Protocol: Distributed consensus algorithm
  • SERIAL Consistency: Linearizable reads/writes
  • Atomic: All-or-nothing, no partial updates
  • Slow: 4-round trips vs 1-round for normal writes

Normal Write vs LWT Write

⚡

Normal Write

  • Speed: 1 round-trip
  • Consistency: Eventual
  • Guarantee: Last-write-wins
  • Use: 99% of operations
  • Performance: ~1ms latency
UPDATE accounts SET balance = 50 WHERE user_id = 'alice';
⚖️

LWT Write

  • Speed: 4 round-trips (Paxos)
  • Consistency: Linearizable
  • Guarantee: Compare-and-set
  • Use: Critical operations only
  • Performance: ~10ms+ latency
UPDATE accounts SET balance = 50 WHERE user_id = 'alice' IF balance = 100;

❓ Why Do We Need LWT?

Cassandra's Normal Behavior

Remember: Cassandra is eventually consistent by default. This means:

  • ⏰ Last-Write-Wins: Timestamp determines which value survives
  • 🔄 No Read-Before-Write: Updates don't check current value
  • 💥 Race Conditions: Concurrent writes can overwrite each other
  • 📊 No Transactions: No ACID guarantees across operations

This works great for: Social media feeds, analytics, logs, metrics
This breaks for: Bank accounts, inventory, user registration, seat booking

Real-World Scenarios Requiring LWT

🎫 Seat Booking

Problem: Two users book same seat simultaneously

-- Book seat only if not taken INSERT INTO bookings ( show_id, seat_number, user_id ) VALUES ( 'movie-123', 'A15', 'alice' ) IF NOT EXISTS;

Result: Only first person gets seat, second fails gracefully

👤 User Registration

Problem: Ensure username uniqueness

-- Create user only if doesn't exist INSERT INTO users ( username, email, created_at ) VALUES ( 'alice', 'alice@example.com', toTimestamp(now()) ) IF NOT EXISTS;

Result: First registration succeeds, duplicates rejected

📦 Inventory

Problem: Don't oversell stock

-- Decrease stock only if available UPDATE inventory SET quantity = quantity - 1 WHERE product_id = 'laptop-123' IF quantity > 0;

Result: Can't sell what you don't have

🔒 Distributed Lock

Problem: Only one process should run task

-- Acquire lock atomically INSERT INTO locks ( resource, owner, expires_at ) VALUES ( 'batch-job-1', 'server-a', now() + 3600 ) IF NOT EXISTS;

Result: Only one server gets the lock

🤝 How Paxos Consensus Works

Paxos in Simple Terms

Paxos is a distributed consensus algorithm that ensures all nodes agree on a single value, even with failures and network partitions. It's the "democratic voting" system that makes LWT work.

The 4-Phase Paxos Dance

-- Phase 1: PREPARE Coordinator: "I propose ballot #5 for this update. Anyone object?" Replicas: Check if they've seen a higher ballot number Replicas: "No objection" OR "I've seen ballot #6 already" -- Phase 2: READ Coordinator: "What's the current value you have?" Replicas: Send current values with timestamps Coordinator: Picks the most recent value -- Phase 3: PROPOSE Coordinator: "I propose updating to new value with ballot #5" Replicas: Check ballot number still valid Replicas: "Accepted" OR "Rejected (seen higher ballot)" -- Phase 4: COMMIT Coordinator: "Majority accepted! Commit the change" Replicas: Write value to memtable & commit log Success: Return [applied]=true to client

What Paxos Guarantees

  • ✅ Linearizability: Operations appear to happen atomically
  • ✅ Consensus: All nodes agree on the final value
  • ✅ Fault Tolerance: Works even if nodes fail mid-operation
  • ✅ No Lost Updates: Compare-and-set is truly atomic
  • ✅ Idempotent: Retries don't cause duplicate operations

Why 4 Phases = Slower Performance

Normal Write: 1 Round-Trip

1. Client → Coordinator 2. Coordinator → Replicas 3. Replicas ACK → Coordinator 4. Coordinator → Client Total: ~1ms local, ~10ms cross-DC

LWT Write: 4 Round-Trips

1. PREPARE phase 2. READ phase 3. PROPOSE phase 4. COMMIT phase Total: ~10ms local, ~100ms+ cross-DC 10x slower minimum!

💻 LWT CQL Syntax

INSERT IF NOT EXISTS

Create Only If Row Doesn't Exist

Perfect for unique username, preventing duplicate IDs, first-come-first-served scenarios.

-- Syntax INSERT INTO table_name (columns...) VALUES (values...) IF NOT EXISTS; -- Example: User Registration INSERT INTO users (username, email, created_at) VALUES ('alice', 'alice@example.com', toTimestamp(now())) IF NOT EXISTS; -- Returns: -- [applied] | username -- true | alice ← Success! User created -- false | alice ← Failed! Username taken

UPDATE IF [condition]

Update Only If Current Value Meets Condition

Perfect for inventory management, balance checks, status transitions.

-- Syntax UPDATE table_name SET column = value WHERE primary_key = value IF column_condition; -- Example 1: Inventory Check UPDATE inventory SET quantity = quantity - 1 WHERE product_id = 'laptop-123' IF quantity >= 1; -- Example 2: Bank Account Withdrawal UPDATE accounts SET balance = balance - 50 WHERE user_id = 'alice' IF balance >= 50; -- Example 3: Status Transition UPDATE orders SET status = 'shipped' WHERE order_id = 'order-456' IF status = 'paid'; -- Only ship if paid!

DELETE IF [condition]

Delete Only If Current Value Meets Condition

Perfect for conditional cleanup, lock release, state validation.

-- Syntax DELETE FROM table_name WHERE primary_key = value IF column_condition; -- Example: Release Lock Only If You Own It DELETE FROM locks WHERE resource = 'batch-job-1' IF owner = 'server-a';

Complex Conditions

-- Multiple conditions with AND UPDATE products SET quantity = quantity - 5, last_updated = toTimestamp(now()) WHERE product_id = 'widget-789' IF quantity >= 5 AND status = 'active'; -- Check column exists (IS NOT NULL) UPDATE sessions SET expires_at = toTimestamp(now()) + 3600 WHERE session_id = 'sess-123' IF user_id IS NOT NULL; -- Check column doesn't exist (IS NULL) UPDATE tasks SET assigned_to = 'alice', started_at = toTimestamp(now()) WHERE task_id = 'task-456' IF assigned_to IS NULL;

📊 SERIAL Consistency Levels

Special Consistency Levels for LWT

LWT operations use special consistency levels that work with Paxos:

SERIAL

  • Scope: Cluster-wide
  • Quorum: Across ALL datacenters
  • Use: Single DC only
  • Guarantee: Linearizable
-- Python example session.execute( query, consistency_level= ConsistencyLevel.SERIAL )

LOCAL_SERIAL

  • Scope: Local datacenter only
  • Quorum: Within local DC
  • Use: Multi-DC production
  • Guarantee: Linearizable per DC
-- Python example session.execute( query, consistency_level= ConsistencyLevel.LOCAL_SERIAL )

Important Notes

  • ⚠️ Don't mix: Use SERIAL with SERIAL, LOCAL_SERIAL with LOCAL_SERIAL
  • ⚠️ Multi-DC: ALWAYS use LOCAL_SERIAL in multi-datacenter deployments
  • ⚠️ Read consistency: Also needs SERIAL/LOCAL_SERIAL to see uncommitted LWT
  • ⚠️ Performance: Both are ~10x slower than QUORUM

⚡ Performance Characteristics

Performance Impact

LWT is SLOW! Here are the numbers:

Normal Write

  • Latency: 1-2ms
  • Throughput: 10,000/sec/node
  • Network: 1 round-trip
  • CPU: Low
  • Use: 99% of operations

LWT Write

  • Latency: 10-50ms+
  • Throughput: 1,000/sec/node
  • Network: 4 round-trips
  • CPU: 10x higher
  • Use: <1% of operations

Why Is LWT So Slow?

  • 🔄 4 Network Round-Trips: PREPARE, READ, PROPOSE, COMMIT phases
  • 💾 Disk I/O: Must persist Paxos state to disk
  • 🔒 Coordination: Nodes must reach consensus (voting)
  • ⏰ Contention: Concurrent LWTs on same partition serialize
  • 🌐 Cross-DC: SERIAL goes cross-datacenter (use LOCAL_SERIAL!)

Optimization Tips

How to Minimize LWT Impact

  • ✅ Use Sparingly: Only for operations requiring atomicity
  • ✅ LOCAL_SERIAL: Use LOCAL_SERIAL in multi-DC
  • ✅ Batch Size: Keep batches small (< 10 statements)
  • ✅ Different Partitions: LWT on different partitions can run parallel
  • ✅ Async: Don't block user requests waiting for LWT
  • ✅ Monitor: Track LWT latency and contention

🎯 Real-World Use Cases

Use Case 1: E-Commerce Product Inventory

-- Table Design CREATE TABLE inventory ( product_id text PRIMARY KEY, name text, quantity int, reserved int, last_updated timestamp ); -- Reserve items only if available UPDATE inventory SET quantity = quantity - 2, reserved = reserved + 2, last_updated = toTimestamp(now()) WHERE product_id = 'laptop-123' IF quantity >= 2; -- Check result in application if (result.was_applied()): # Success! Items reserved create_order() else: # Failed! Not enough stock show_error("Out of stock")

Use Case 2: User Registration & Username Uniqueness

-- Table Design CREATE TABLE users ( username text PRIMARY KEY, user_id uuid, email text, password_hash text, created_at timestamp ); -- Register user only if username available INSERT INTO users ( username, user_id, email, password_hash, created_at ) VALUES ( 'alice', uuid(), 'alice@example.com', '$2b$12$...', toTimestamp(now()) ) IF NOT EXISTS;

Use Case 3: Distributed Locking

-- Table Design CREATE TABLE locks ( resource text PRIMARY KEY, owner text, acquired_at timestamp, expires_at timestamp ); -- Acquire lock INSERT INTO locks (resource, owner, acquired_at, expires_at) VALUES ( 'batch-job-daily-report', 'server-a', toTimestamp(now()), toTimestamp(now()) + 3600 -- 1 hour TTL ) IF NOT EXISTS USING TTL 3600; -- Release lock (only if you own it!) DELETE FROM locks WHERE resource = 'batch-job-daily-report' IF owner = 'server-a';

Use Case 4: Seat Booking System

-- Table Design CREATE TABLE seat_bookings ( show_id text, seat_number text, user_id uuid, booked_at timestamp, PRIMARY KEY ((show_id), seat_number) ); -- Book seat atomically INSERT INTO seat_bookings ( show_id, seat_number, user_id, booked_at ) VALUES ( 'movie-avengers-7pm', 'A15', uuid(), toTimestamp(now()) ) IF NOT EXISTS;

✅ Best Practices & Guidelines

✅ DO These

  • Use LWT only when atomicity required
  • Use LOCAL_SERIAL in multi-DC
  • Design to minimize LWT usage
  • Handle [applied]=false gracefully
  • Monitor LWT latency/contention
  • Use application-level caching
  • Consider external locks (Redis)

❌ DON'T Do These

  • Use for all writes
  • Use SERIAL in multi-DC
  • LWT on high-traffic partitions
  • Large batches with LWT
  • Retry blindly on failure
  • Expect same perf as normal write
  • Use for analytics/logging

🎯 LWT Summary

You now understand Cassandra's Lightweight Transactions!

📚 Key Takeaways:

  • ⚖️ LWT = Compare-and-set with Paxos consensus
  • 🐌 10x slower than normal writes (4 round-trips)
  • 🎯 Use ONLY for critical atomic operations
  • 🌐 LOCAL_SERIAL for multi-DC deployments
  • 📝 Syntax: IF NOT EXISTS, IF condition
  • 💡 Perfect for: inventory, booking, registration, locks

Remember: LWT is your "nuclear option" - powerful but expensive! ⚖️

Advertisement

Responsive Ad