Scylla Batches
Use Scylla batches for grouped CQL writes through the Scylla driver.
Scylla batches are used to group multiple CQL statements into a single batch request.
They are supported by the Scylla driver and are specific to Scylla/CQL workloads.
Batches are different from SQL transactions.
They do not behave like MySQL or PostgreSQL transactions with BEGIN, COMMIT, and ROLLBACK.
They also do not replace lightweight transactions.
Use batches when your Scylla data model requires grouped writes.
Basic Idea
The Scylla driver exposes batch helpers through the connection.
Common batch types include:
- logged batch
- unlogged batch
- counter batch
A typical flow is:
- create a batch
- add CQL statements
- execute the batch
batch := conn.NewLoggedBatch(ctx)
err := batch.AddSQL(`
INSERT INTO users_by_id (id, email, name, active, created_at)
VALUES (?, ?, ?, ?, ?)
`, id, email, name, active, createdAt)
if err != nil {
return err
}
err = batch.Execute()
if err != nil {
return err
}Batch Types
| Batch type | Purpose |
|---|---|
| Logged batch | Group writes that should be coordinated by Scylla |
| Unlogged batch | Group writes without logged batch coordination |
| Counter batch | Group counter updates |
The exact behavior depends on Scylla and CQL semantics.
Use batches carefully and only when they match your data model.
Complete Example
This example writes the same user into two query tables.
package main
import (
"context"
"time"
"github.com/gocql/gocql"
"github.com/netlifeguru/db"
"github.com/netlifeguru/db-scylla"
)
const insertUserByIDQuery = `
INSERT INTO users_by_id (id, email, name, active, created_at)
VALUES (?, ?, ?, ?, ?)
`
const insertUserByEmailQuery = `
INSERT INTO users_by_email (email, id, name, active, created_at)
VALUES (?, ?, ?, ?, ?)
`
func InsertUserWithBatch(
ctx context.Context,
conn scylla.BatchConn,
name string,
email string,
active bool,
) (string, error) {
id := gocql.TimeUUID()
createdAt := time.Now().UTC()
batch := conn.NewLoggedBatch(ctx)
if err := batch.AddSQL(insertUserByIDQuery, id, email, name, active, createdAt); err != nil {
return "", err
}
if err := batch.AddSQL(insertUserByEmailQuery, email, id, name, active, createdAt); err != nil {
return "", err
}
if err := batch.Execute(); err != nil {
return "", err
}
return id.String(), nil
}Using db.Query
You can also add a prepared db.Query to a batch.
q, err := db.Raw(insertUserByIDQuery, id, email, name, active, createdAt)
if err != nil {
return err
}
batch := conn.NewLoggedBatch(ctx)
if err := batch.Add(q); err != nil {
return err
}
if err := batch.Execute(); err != nil {
return err
}This is useful when your queries are selected or prepared before the batch is created.
Low-Level Batch Example
The low-level pattern uses db.Raw and batch.Add.
package main
import (
"context"
"time"
"github.com/gocql/gocql"
"github.com/netlifeguru/db"
"github.com/netlifeguru/db-scylla"
)
func InsertUserWithLowLevelBatch(
ctx context.Context,
conn scylla.BatchConn,
name string,
email string,
active bool,
) (string, error) {
id := gocql.TimeUUID()
createdAt := time.Now().UTC()
q1, err := db.Raw(insertUserByIDQuery, id, email, name, active, createdAt)
if err != nil {
return "", err
}
q2, err := db.Raw(insertUserByEmailQuery, email, id, name, active, createdAt)
if err != nil {
return "", err
}
batch := conn.NewLoggedBatch(ctx)
if err := batch.Add(q1); err != nil {
return "", err
}
if err := batch.Add(q2); err != nil {
return "", err
}
if err := batch.Execute(); err != nil {
return "", err
}
return id.String(), nil
}Accessing Batch Methods
The shared db.Conn interface is intentionally database-agnostic.
Scylla batch methods are Scylla-specific, so use the Scylla batch interface when you need them.
batchConn, ok := conn.(scylla.BatchConn)
if !ok {
return errors.New("connection does not support Scylla batches")
}Then create a batch:
batch := batchConn.NewLoggedBatch(ctx)This keeps the shared db.Conn interface clean while still allowing Scylla-specific behavior when needed.
Logged Batch
Use NewLoggedBatch when you need Scylla logged batch behavior.
batch := conn.NewLoggedBatch(ctx)Logged batches coordinate batch writes through Scylla’s batch log.
Use them only when the data model requires that behavior.
Unlogged Batch
Use NewUnloggedBatch for unlogged batch writes.
batch := conn.NewUnloggedBatch(ctx)Unlogged batches avoid the batch log overhead, but they do not provide the same coordination behavior as logged batches.
Counter Batch
Use NewCounterBatch for counter updates.
batch := conn.NewCounterBatch(ctx)Counter batches are intended for CQL counter operations.
Batches vs Lightweight Transactions
Batches and lightweight transactions solve different problems.
| Feature | Batch | Lightweight transaction |
|---|---|---|
| Purpose | Group multiple statements | Apply one conditional statement |
| Condition check | no | yes |
Uses [applied] | no | yes |
| Common CQL | BEGIN BATCH ... APPLY BATCH behavior through driver | IF NOT EXISTS, IF column = value |
| Best for | grouped writes | compare-and-set style writes |
Use lightweight transactions when you need a conditional write.
Use batches when you need grouped CQL statements.
Batches vs SQL Transactions
Scylla batches are not SQL transactions.
They do not provide the same behavior as:
BEGIN;
COMMIT;
ROLLBACK;If you come from MySQL or PostgreSQL, do not treat Scylla batches as a direct transaction replacement.
Design your Scylla schema and write path according to Scylla data modeling rules.
When to Use Batches
Use batches when:
- your Scylla data model requires grouped writes
- you write the same logical entity into multiple query tables
- you need logged, unlogged, or counter batch behavior
- you understand the performance and modeling implications
When Not to Use Batches
Avoid batches when:
- you are trying to make Scylla behave like a relational database
- the statements are unrelated
- a normal single-table write is enough
- you want a replacement for SQL transactions
- you are using batches for every write without a modeling reason
Related Examples
Standalone examples are available in the examples repository: