Master BATCH Operations
Complete guide to batch operations in Cassandra - logged vs unlogged batches, atomicity, performance optimization, and when NOT to use batches!
🎯 BATCH in Cassandra
CRITICAL: BATCH is NOT for Performance!
BIGGEST MISCONCEPTION: Many developers think BATCH makes writes faster. IT DOESN'T! In fact, BATCH can make writes SLOWER!
⚠️ BATCH is ONLY for:
- Atomicity: Ensuring multiple writes succeed or fail together
- Same Partition: Multiple operations to THE SAME partition key
Using BATCH for "bulk inserts" or "performance" is an ANTI-PATTERN!
📚 Beginner's Guide to BATCH
What is BATCH?
Simple Explanation: BATCH lets you group multiple INSERT, UPDATE, or DELETE statements together.
🏦 Bank Transfer Analogy:
Imagine transferring money between two bank accounts:
- Without BATCH:
- Step 1: Deduct $100 from Account A → Success ✓
- Step 2: Add $100 to Account B → FAILS ✗
- Problem: Money vanished! Account A lost $100, Account B didn't receive it!
- With BATCH:
- Both operations happen together as ONE unit
- Either BOTH succeed, or BOTH fail
- Safe: No money lost!
🚨 Common Misunderstanding:
Wrong Thinking: "I have 10,000 rows to insert. Let me put them in a BATCH to make it faster!"
Reality: This will make it MUCH SLOWER because:
- BATCH adds overhead (logging, coordination)
- Coordinator node becomes bottleneck
- All writes wait for slowest one to complete
💡 For bulk inserts: Send individual writes in parallel, NOT in a BATCH!
📖 BATCH Syntax
Basic BATCH Syntax
[USING TIMESTAMP timestamp]
INSERT INTO table1 (...) VALUES (...);
UPDATE table2 SET ... WHERE ...;
DELETE FROM table3 WHERE ...;
APPLY BATCH;
-- Logged BATCH (default - ensures atomicity)
BEGIN BATCH
INSERT INTO users (user_id, name) VALUES (?, ?);
INSERT INTO user_emails (email, user_id) VALUES (?, ?);
APPLY BATCH;
-- Unlogged BATCH (no atomicity guarantee)
BEGIN UNLOGGED BATCH
INSERT INTO logs (log_id, message) VALUES (?, ?);
INSERT INTO logs (log_id, message) VALUES (?, ?);
APPLY BATCH;
BATCH Rules
- Can contain INSERT, UPDATE, DELETE statements
- All statements share same timestamp (if specified)
- Cannot contain SELECT statements
- Cannot contain other BATCH statements (no nesting)
- Recommended: Keep batches small (5-10 statements)
📝 Logged BATCH (Default)
Understanding Logged BATCH
What "Logged" Means: Cassandra writes the entire batch to a special log (the batchlog) before executing it.
📝 Package Delivery Analogy:
Think of sending a package with "signature required":
- Step 1: Delivery company writes down "Package for Alice: Address XYZ" in their permanent log
- Step 2: Attempts delivery to Alice
- Step 3a (Success): Alice signs, log updated "DELIVERED"
- Step 3b (Failure): Alice not home, log says "RETRY TOMORROW"
The log ensures the package will EVENTUALLY be delivered, even if there are temporary failures!
✅ Logged BATCH Guarantees:
- Atomicity: All statements succeed together, or all fail together
- Durability: Written to batchlog first (survives failures)
- Eventual completion: Will retry automatically until all succeed
Use Case: When you NEED all operations to succeed (like bank transfer)
⚠️ Performance Cost:
Logged BATCH is SLOW because:
- Extra write to batchlog (3 replicas)
- Coordinator must wait for all operations
- Additional read to check batchlog status
Typically 2-5x slower than individual writes!
Logged BATCH Example
BEGIN BATCH
-- Create user record
INSERT INTO users (user_id, username, email, created_at)
VALUES (uuid(), 'alice', '[email protected]', toTimestamp(now()));
-- Create email-to-user lookup (for login)
INSERT INTO users_by_email (email, user_id, username)
VALUES ('[email protected]', uuid(), 'alice');
-- Initialize user preferences
INSERT INTO user_preferences (user_id, theme, language)
VALUES (uuid(), 'dark', 'en');
APPLY BATCH;
// ✅ All 3 tables updated together atomically
// ✅ If any fails, none are written
⚡ Unlogged BATCH
Understanding Unlogged BATCH
What "Unlogged" Means: Skips the batchlog - just groups statements together and sends them to nodes.
📬 Regular Mail Analogy:
Think of sending multiple letters together:
- You put 5 letters in one envelope
- Mail carrier delivers them as a group
- BUT no tracking, no signature required
- If delivery fails, some letters might be delivered, some might not
Faster delivery, but less guarantee!
✅ Unlogged BATCH Benefits:
- Faster: No batchlog overhead
- Groups requests: Single network call to coordinator
- Same partition optimization: All writes to same partition sent together
Use Case: Multiple writes to SAME partition where atomicity isn't critical
❌ NO Atomicity Guarantee:
If coordinator fails mid-batch:
- Some statements might succeed
- Some statements might fail
- No automatic retry
- Application must handle partial failures
Only use when you don't care about some statements failing!
Unlogged BATCH Example
BEGIN UNLOGGED BATCH
-- All logs for same application (same partition)
INSERT INTO app_logs (app_id, log_time, message)
VALUES ('app-001', now(), 'User login successful');
INSERT INTO app_logs (app_id, log_time, message)
VALUES ('app-001', now(), 'Page view: /dashboard');
INSERT INTO app_logs (app_id, log_time, message)
VALUES ('app-001', now(), 'API call: GET /users');
APPLY BATCH;
// ✅ Efficient: All to same partition
// ✅ If one log fails, others still written (acceptable)
⚖️ Logged vs Unlogged BATCH
📝 Logged BATCH
- ✓ Atomic (all-or-nothing)
- ✓ Durable (batchlog survives failures)
- ✓ Automatic retries
- ✗ 2-5x slower
- ✗ Extra writes (batchlog)
- ✗ Coordinator bottleneck
Use for: Critical operations requiring atomicity
⚡ Unlogged BATCH
- ✓ Faster (no batchlog)
- ✓ Less overhead
- ✓ Groups statements efficiently
- ✗ NOT atomic
- ✗ Partial failures possible
- ✗ No automatic retry
Use for: Same-partition writes where failures OK
| Feature | Logged BATCH | Unlogged BATCH |
|---|---|---|
| Atomicity | ✓ Guaranteed | ✗ Not guaranteed |
| Performance | Slow (2-5x overhead) | Faster |
| Batchlog | Yes (written to 3 replicas) | No |
| Retries | Automatic | None |
| Use Case | Critical operations | Same-partition writes |
✅ When to Use BATCH
Valid BATCH Use Cases
1. Denormalized Data (Logged BATCH)
Scenario: You store same data in multiple tables for different query patterns
Example: User registration creates entries in users, users_by_email, and user_preferences
✓ Use logged BATCH to ensure all tables updated together
2. Same Partition Multiple Writes (Unlogged BATCH)
Scenario: Many INSERTs/UPDATEs to same partition key
Example: Writing multiple log entries for same application
✓ Use unlogged BATCH to group writes efficiently
3. Maintaining Consistency (Logged BATCH)
Scenario: Related writes that MUST succeed together
Example: Inventory deduction + order creation
✓ Use logged BATCH for critical consistency
❌ BATCH Anti-Patterns
DON'T Use BATCH For These!
❌ Anti-Pattern #1: Bulk Inserts
BEGIN BATCH
INSERT INTO users ... -- 10,000 statements!
INSERT INTO users ...
-- ... 9,998 more ...
APPLY BATCH;
// ❌ Coordinator out of memory!
// ❌ Cluster instability!
// ❌ Timeout errors!
✓ CORRECT: Send individual writes in parallel using async/await!
❌ Anti-Pattern #2: Cross-Partition without Need
BEGIN BATCH
INSERT INTO users WHERE user_id = 'user1' ...
INSERT INTO users WHERE user_id = 'user2' ...
INSERT INTO users WHERE user_id = 'user3' ...
APPLY BATCH;
// ❌ Coordinator coordinates across multiple nodes
// ❌ Slower than individual writes!
❌ Anti-Pattern #3: Performance Optimization
Myth: "BATCH makes writes faster!"
Reality: BATCH adds overhead and makes writes SLOWER!
Only use BATCH for correctness (atomicity), never for performance!
💡 Real-World Examples
Example 1: User Registration
BEGIN BATCH
-- Main user record
INSERT INTO users (user_id, username, email, created_at)
VALUES (?, ?, ?, toTimestamp(now()));
-- Email lookup table (for login by email)
INSERT INTO users_by_email (email, user_id)
VALUES (?, ?);
-- Username lookup table (for uniqueness)
INSERT INTO users_by_username (username, user_id)
VALUES (?, ?);
APPLY BATCH;
// ✅ All 3 tables updated atomically
// ✅ Can't have orphaned records
Example 2: Sensor Data (Same Partition)
BEGIN UNLOGGED BATCH
INSERT INTO sensor_data (sensor_id, timestamp, temperature)
VALUES ('sensor-123', now(), 72.5);
INSERT INTO sensor_data (sensor_id, timestamp, temperature)
VALUES ('sensor-123', now(), 72.6);
INSERT INTO sensor_data (sensor_id, timestamp, temperature)
VALUES ('sensor-123', now(), 72.7);
APPLY BATCH;
// ✅ Same partition = efficient
// ✅ Unlogged = faster
// ✅ Missing one reading isn't critical
✅ BATCH Best Practices
Golden Rules
- Use BATCH for correctness, not performance
- Keep batches small (5-10 statements max)
- Same partition = unlogged BATCH
- Cross-partition = logged BATCH (if atomicity needed)
- Never batch for bulk inserts
- Monitor batch sizes in production
| Scenario | Recommendation |
|---|---|
| Denormalized writes (must sync) | Logged BATCH |
| Same partition multiple writes | Unlogged BATCH |
| Bulk inserts (1000s of rows) | Individual writes (async) |
| Performance optimization | Don't use BATCH! |
| Cross-partition (no atomicity need) | Individual writes |
Google AdSense - Responsive Ad Unit