Cassandra Batch Statements
A BATCH in Cassandra groups multiple INSERT, UPDATE, or DELETE statements into a single operation. Batches reduce network round trips by sending all statements in one request. Understanding when to use each type of batch — and when not to use batches at all — is essential for writing correct, performant Cassandra code.
BATCH Types
Type Atomicity Use When
──────────────────────────────────────────────────────────────
LOGGED BATCH Yes (eventually) Writing to multiple tables
that must stay in sync
UNLOGGED BATCH No Writing multiple rows to
the same partition only
COUNTER BATCH No (special) Batching counter updates only
LOGGED BATCH
A LOGGED BATCH provides a form of atomicity: if the coordinator crashes mid-batch, the batch log (stored on replica nodes) ensures all statements eventually apply. No statement in the batch is permanently skipped. This is not the same as ACID transaction atomicity — there is no rollback, and partial states can briefly be visible during recovery.
BEGIN BATCH
INSERT INTO orders_by_customer
(customer_id, order_date, order_id, total, status)
VALUES ([uuid], toTimestamp(now()), uuid(), 149.99, 'pending');
INSERT INTO orders_by_status
(status, order_date, order_id, customer_id, total)
VALUES ('pending', toTimestamp(now()), [order-uuid], [uuid], 149.99);
INSERT INTO orders_by_id
(order_id, customer_id, total, status)
VALUES ([order-uuid], [uuid], 149.99, 'pending');
APPLY BATCH;
How LOGGED BATCH Works Internally
Step 1: Coordinator writes the entire batch to a batch log
table on 2 replica nodes (for safety)
Step 2: Coordinator executes each statement in the batch
Step 3: Coordinator removes the batch log entry on success
Step 4: If coordinator crashes at step 2, another node reads
the batch log and retries all statements
The batch log adds overhead — roughly 2× the write cost. Use LOGGED BATCH only when multi-table consistency matters (like keeping denormalized tables in sync).
UNLOGGED BATCH
An UNLOGGED BATCH sends multiple statements in one network request with no atomicity guarantee and no batch log. It is significantly faster than a LOGGED BATCH but provides no recovery if the coordinator fails mid-execution.
BEGIN UNLOGGED BATCH
INSERT INTO products (product_id, name, price)
VALUES (uuid(), 'USB Cable', 9.99);
INSERT INTO products (product_id, name, price)
VALUES (uuid(), 'HDMI Cable', 14.99);
INSERT INTO products (product_id, name, price)
VALUES (uuid(), 'Power Bank', 39.99);
APPLY BATCH;
UNLOGGED BATCH — Same Partition Only
UNLOGGED BATCH provides the most benefit when all statements target the same partition key. In that case, all writes go to the same set of replica nodes in one message — the minimum possible network overhead.
Good: all rows in the same partition
BEGIN UNLOGGED BATCH
INSERT INTO sensor_readings (sensor_id, day, read_time, value)
VALUES ('S1', '2024-06-15', '2024-06-15 10:00:00', 22.5);
INSERT INTO sensor_readings (sensor_id, day, read_time, value)
VALUES ('S1', '2024-06-15', '2024-06-15 10:01:00', 22.6);
INSERT INTO sensor_readings (sensor_id, day, read_time, value)
VALUES ('S1', '2024-06-15', '2024-06-15 10:02:00', 22.4);
APPLY BATCH;
All rows have the same partition key (sensor_id='S1', day='2024-06-15')
→ coordinator sends one message to the right nodes → minimal overhead ✓
UNLOGGED BATCH — Multiple Partitions (Avoid)
When an UNLOGGED BATCH spans many different partitions across the cluster, the coordinator must send sub-batches to many different nodes. This adds more coordinator load and network traffic than sending the statements individually would. This is a common Cassandra anti-pattern known as the "multi-partition unlogged batch" problem.
Bad: rows in different partitions scattered across the cluster BEGIN UNLOGGED BATCH INSERT INTO products (product_id, ...) VALUES (uuid-1, ...); → Node A INSERT INTO products (product_id, ...) VALUES (uuid-2, ...); → Node C INSERT INTO products (product_id, ...) VALUES (uuid-3, ...); → Node B ... INSERT INTO products (product_id, ...) VALUES (uuid-50, ...); → Node D APPLY BATCH; Coordinator must fan out to 4+ nodes → MORE overhead than 50 individual writes
COUNTER BATCH
Counter columns must use a dedicated COUNTER BATCH (or a regular BATCH with only counter updates). You cannot mix counter and non-counter statements in the same batch.
BEGIN COUNTER BATCH UPDATE page_views SET views = views + 1 WHERE page_id = '/home'; UPDATE page_views SET views = views + 1 WHERE page_id = '/products'; UPDATE product_stats SET purchases = purchases + 1 WHERE product_id = [uuid]; APPLY BATCH;
Timestamps in Batches
All statements in a BATCH share the same client-side timestamp by default. This ensures that when denormalized copies of the same data are written, no copy is considered "older" due to millisecond timing differences.
BEGIN BATCH USING TIMESTAMP 1710000000000000 INSERT INTO orders_by_customer ...; INSERT INTO orders_by_status ...; APPLY BATCH;
Batch Anti-Patterns
Anti-Pattern Why It Is Wrong
──────────────────────────────────────────────────────────────
Large unlogged batch across Coordinator fan-out adds more
many partitions overhead than individual writes
Batching for performance "always" Only same-partition batches help
Using logged batch for every write Batch log overhead on every write
Batching thousands of statements Large batches cause coordinator
memory spikes and GC pressure
Using batch as a transaction Batch is not an ACID transaction
— no rollback on failure
Batch Size Limits
Cassandra logs a warning when a batch exceeds 5 KB and fails the batch when it exceeds 50 KB (configurable in cassandra.yaml). Large batches cause coordinator memory pressure.
# cassandra.yaml batch_size_warn_threshold_in_kb: 5 # warn above 5 KB batch_size_fail_threshold_in_kb: 50 # fail above 50 KB
Decision Guide
Question Use ────────────────────────────────────────────────────────────── Need all writes to eventually apply? LOGGED BATCH All writes target same partition? UNLOGGED BATCH Writing to denormalized tables? LOGGED BATCH Updating counters only? COUNTER BATCH Writing to many different partitions? Individual writes (no batch) Writing hundreds of different rows? Individual async writes
Summary
Use LOGGED BATCH when writing to multiple denormalized tables that must stay in sync — its batch log provides eventual atomicity. Use UNLOGGED BATCH for multiple inserts to the same partition to save network round trips. Use COUNTER BATCH for counter-only updates. Avoid large multi-partition unlogged batches — they increase coordinator load rather than reducing it. Batches are a consistency tool, not a performance shortcut for arbitrary write groups.
