Batch operations
For the complete documentation index see: llms.txt
All documentation pages available in markdown.
Applies to
- Aerospike Developer SDK (Java 21+ and Python 3.11+)
- Aerospike Database 6.0 or later unless a section states otherwise
Learn how to perform multiple database operations efficiently in a single network request. Batch operations reduce latency and improve throughput when working with multiple records.
Except where noted, snippets on this page use the imports below. A snippet lists additional import lines only when it needs a type not shown here. When this page includes a Complete example section, that block is fully self-contained with every import required to run it.
import com.aerospike.client.sdk.DataSet;import com.aerospike.client.sdk.Record;import com.aerospike.client.sdk.RecordResult;import com.aerospike.client.sdk.RecordStream;import com.aerospike.client.sdk.ErrorStrategy;
import java.util.List;from aerospike_sdk import DataSetWhy use batch operations?
| Approach | 100 Records | Network Roundtrips |
|---|---|---|
| Individual operations | ~100ms+ | 100 |
| Batch operation | ~5-10ms | 1 |
Batch operations send multiple requests in a single network call, as well as sending requests to multiple servers concurrently, dramatically reducing latency.
Two kinds of batch
Batch commands come in two shapes, and which one you need depends on whether every key gets the same thing.
One operation applied to many keys. Pass a key list to the ordinary command and it runs once against every key in the list. The operation and its arguments are identical for all of them. This covers reads, deletes, existence checks, touches, UDF calls, and writes where every record gets the same bin values.
| Command | What it does to every key in the list |
|---|---|
query(keys) | Reads the records |
exists(keys) | Reports whether each record exists |
delete(keys) | Deletes the records |
touch(keys) | Resets the time to expiration |
insert(keys) / update(keys) / upsert(keys) / replace(keys) | Applies the same bin values to each record |
executeUdf(keys) | Runs the same function with the same arguments |
A different payload per key. When each record needs its own values, name the data set instead of a key list and supply one row per key. This is the batch-write shape, and it is what the legacy clients called a batch write.
DataSet users = DataSet.of("test", "users");
// One operation, many keys: every record gets tier="gold".session.upsert(users.ids("user-1", "user-2", "user-3")) .bin("tier").setTo("gold") .execute();
// A different payload per key, still one request.session.insert(users) .bins("name", "score") .id("user-1").values("Alice", 10) .id("user-2").values("Bob", 20) .id("user-3").values("Carol", 30) .execute();users = DataSet.of("test", "users")
# One operation, many keys: every record gets tier="gold".await session.upsert(users.ids("user-1", "user-2", "user-3")).put({"tier": "gold"}).execute()Batch read
Read multiple records in one request:
DataSet users = DataSet.of("test", "users");
RecordStream result = session.query(users.ids("user-1", "user-2", "user-3")) .execute();
// Get all recordsList<Record> records = new java.util.ArrayList<>();result.forEach(rr -> { if (rr.isOk()) { records.add(rr.recordOrThrow()); }});for (Record record : records) { System.out.println("Name: " + record.getString("name"));}
// Or access by indexRecord first = records.get(0); // user-1Record second = records.get(1); // user-2📖 API reference:
DataSet.of(...)|DataSet.ids(...)|Session.query(List)|ChainableQueryBuilder.execute()|RecordStream.forEach(...)|RecordResult.isOk()|RecordResult.recordOrThrow()|Record.getString(...)
users = DataSet.of("test", "users")
stream = await session.query(users.ids("user-1", "user-2", "user-3")).execute()
# Get all recordsrecords = []async for rr in stream: if rr.is_ok: records.append(rr.record_or_raise())stream.close()
for record in records: print(f"Name: {record.bins['name']}")
# Or access by indexfirst = records[0] # user-1second = records[1] # user-2📖 API reference:
DataSet.of()|DataSet.ids()|Session.query()|QueryBuilder.execute()|RecordResult.is_ok|RecordResult.record_or_raise()|RecordStream.close()
Batch write
Create or update multiple records:
DataSet users = DataSet.of("test", "users");
session.insert(users) .bins("name", "email") .id("user-1").values("Alice", "alice@example.com") .id("user-2").values("Bob", "bob@example.com") .id("user-3").values("Carol", "carol@example.com") .execute();📖 API reference:
Session.insert(DataSet)|OperationObjectBuilder.bins(...)|IdValuesBuilder.id(...)|IdValuesRowBuilder.values(...)|ChainableQueryBuilder.execute()
:::
Batch upsert
Insert or update multiple records:
DataSet users = DataSet.of("test", "users");
session .upsert(users.ids("user-1","user-2")) .bin("status").setTo("active") .bin("balance").add(500) .upsert(users.id("user-3")) .bin("status").setTo("inactive") .execute();📖 API reference:
DataSet.ids(...)|DataSet.id(...)|ChainableQueryBuilder.bin(...)|BinBuilder.add(...)|ChainableQueryBuilder.execute()
Per-key batch writes are not yet exposed by the Python SDK. Each key in a batch write carries its own payload, and the whole set travels in one round trip — see the Java tab for the shape. This example will be filled in when the Python API lands.
Batch reads and batch deletes are available today: pass several keys to one command with
DataSet.ids().
Batch delete
Delete multiple records:
DataSet users = DataSet.of("test", "users");
RecordStream result = session.delete(users.ids("user-1", "user-2", "user-3")) .execute();
// Check which deletes succeededint i = 0;while (result.hasNext()) { RecordResult rr = result.next(); System.out.println("Record " + i++ + " deleted: " + rr.asBoolean());}result.close();📖 API reference:
DataSet.ids(...)|Session.delete(List)|Session.delete(Key)|ChainableQueryBuilder.execute()|RecordStream.hasNext()|RecordStream.next()|RecordStream.close()
users = DataSet.of("test", "users")
stream = await session.delete(users.ids("user-1", "user-2", "user-3")).execute()
# Check which deletes succeededasync for rr in stream: print(f"Record {rr.index} deleted: {rr.as_bool()}")stream.close()📖 API reference:
DataSet.of()|DataSet.ids()|Session.delete()|WriteSegmentBuilder.execute()|RecordResult.index|RecordResult.as_bool()|RecordStream.close()
Batch exists
Check whether several records exist, without reading their bins:
DataSet users = DataSet.of("test", "users");
RecordStream result = session.exists(users.ids("user-1", "user-2", "user-3")).execute();
while (result.hasNext()) { RecordResult rr = result.next(); System.out.println(rr.getKey().userKey + " exists: " + rr.asBoolean());}result.close();users = DataSet.of("test", "users")
stream = await session.exists(users.ids("user-1", "user-2", "user-3")).execute()
async for rr in stream: print(f"{rr.key.value} exists: {rr.as_bool()}")stream.close()exists returns a row only for keys that are present. Ask about three keys where one is missing and
you get two rows, both true — not three rows with one false. Neither can you fall back on
the row index: unlike query, delete, and touch, an exists row does not carry a usable one.
Read each row’s own key, and treat any key absent from the results as a record that does not exist.
Batch touch
Reset the time to expiration on several records at once, without changing their bins:
DataSet users = DataSet.of("test", "users");
session.touch(users.ids("user-1", "user-2", "user-3")).execute();users = DataSet.of("test", "users")
stream = await session.touch(users.ids("user-1", "user-2", "user-3")).execute()stream.close()Touch cannot create a record, because the server deletes empty records. The command fails for any key that does not exist. See Set time-to-live (TTL) for how the expiration is derived.
Mixed batch operations
Combine different operation types in one call. Note that the operations on each
key are performed asynchronously across the nodes in the cluster, and hence the
returned results may not be in the same order as the commands. Each returned
item exposes a key and an index — key() and index() in Java, the key and
index attributes in Python (row.key, row.index). key is the unique key
of the record and index is the zero-based index of the command in the
original list.
DataSet users = DataSet.of("test", "users");
RecordStream stream = session .query(users.ids("user-1", "user-2")) .upsert(users.id("user-3")) .bin("status").setTo("active") .delete(users.id("user-4")) .execute();
stream.forEach(result -> { switch (result.getIndex()) { case 0 -> handleUser1(result); case 1 -> handleUser2(result); case 2 -> System.out.println("Upsert: " + (result.isOk() ? "ok" : result.getMessage())); case 3 -> System.out.println("Delete: " + (result.isOk() ? "ok" : result.getMessage())); }});📖 API reference:
DataSet.ids(...)|DataSet.id(...)|ChainableQueryBuilder.bin(...)|ChainableQueryBuilder.execute()|RecordStream.forEach(...)|RecordResult.isOk()
users = DataSet.of("test", "users")
stream = await ( session.query(users.ids("user-1", "user-2")) .upsert(users.id("user-3")).bin("status").set_to("active") .delete(users.id("user-4")) .execute())async for row in stream: match row.index: case 0: handle_user1(row) case 1: handle_user2(row) case 2: print(f"Upsert: {'ok' if row.is_ok else row.result_code}") case 3: print(f"Delete: {'ok' if row.is_ok else row.result_code}")stream.close()📖 API reference:
DataSet.of()|DataSet.id()|DataSet.ids()|Session.query()|RecordResult.index|RecordResult.is_ok|RecordStream.close()
Batch with selected bins
Read only specific bins in batch:
DataSet users = DataSet.of("test", "users");
RecordStream result = session.query(users.ids("user-1", "user-2", "user-3")) .bins("name", "email") .execute();📖 API reference:
DataSet.ids(...)|Session.query(List)|OperationObjectBuilder.bins(...)|ChainableQueryBuilder.execute()
users = DataSet.of("test", "users")
stream = await ( session.query(users.ids("user-1", "user-2", "user-3")) .bins(["name", "email"]) .execute())📖 API reference:
DataSet.of()|DataSet.ids()|Session.query()|QueryBuilder.bins()|QueryBuilder.execute()
Handle partial failures
Some operations in a batch may fail while others succeed:
DataSet users = DataSet.of("test", "users");
RecordStream result = session .update(users.ids("user-1", "nonexistent", "user-3")) .bin("lastSeen").setTo(System.currentTimeMillis()) .execute(ErrorStrategy.IN_STREAM); // Place errors in the RecordStreamresult.forEach(rr -> { String keyId = rr.getKey().userKey.toString(); if (!rr.isOk()) { System.out.println(keyId + " failed: " + rr.getMessage()); } else { System.out.println(keyId + ": updated"); }});📖 API reference:
DataSet.ids(...)|ChainableQueryBuilder.bin(...)|ChainableQueryBuilder.execute()|RecordStream.forEach(...)|RecordResult.isOk()|ErrorStrategy|ErrorStrategy.IN_STREAM
Per-key batch writes are not yet exposed by the Python SDK. Each key in a batch write carries its own payload, and the whole set travels in one round trip — see the Java tab for the shape. This example will be filled in when the Python API lands.
Batch reads and batch deletes are available today: pass several keys to one command with
DataSet.ids().
Dynamic batch building
Build batches programmatically:
// Additional imports for this example:import com.aerospike.client.sdk.IdValuesRowBuilder;
DataSet users = DataSet.of("test", "users");
IdValuesRowBuilder rows = session.upsert(users) .bins("name", "score") .id("user-1").values("User 1", 10);for (int id = 2; id <= 50; id++) { rows.id("user-" + id).values("User " + id, id * 10);}rows.execute();📖 API reference:
DataSet.id(...)|Session.upsert(DataSet)|OperationObjectBuilder.bins(...)|IdValuesBuilder.id(...)|IdValuesRowBuilder.values(...)|ChainableQueryBuilder.execute()
Per-key batch writes are not yet exposed by the Python SDK. Each key in a batch write carries its own payload, and the whole set travels in one round trip — see the Java tab for the shape. This example will be filled in when the Python API lands.
Batch reads and batch deletes are available today: pass several keys to one command with
DataSet.ids().
Complete example
This example is self-contained—it lists every import needed to run standalone.
import com.aerospike.client.sdk.Cluster;import com.aerospike.client.sdk.ClusterDefinition;import com.aerospike.client.sdk.DataSet;import com.aerospike.client.sdk.Record;import com.aerospike.client.sdk.RecordResult;import com.aerospike.client.sdk.RecordStream;import com.aerospike.client.sdk.Session;import com.aerospike.client.sdk.policy.Behavior;
public class BatchOperationsExample { public static void main(String[] args) { try (Cluster cluster = new ClusterDefinition("localhost", 3000).connect()) { Session session = cluster.createSession(Behavior.DEFAULT); DataSet users = DataSet.of("test", "users"); String key1 = "batch-example-1"; String key2 = "batch-example-2"; String key3 = "batch-example-3";
// Cleanup so the example is repeatable. session.delete(users.ids(key1, key2, key3)).execute().close();
// Batch insert session.insert(users) .bins("name", "age") .id(key1).values("Alice", 28) .id(key2).values("Bob", 35) .id(key3).values("Carol", 22) .execute(); System.out.println("Batch insert complete");
// Batch read try (RecordStream readStream = session.query(users.ids(key1, key2, key3)).execute()) { readStream.forEach(result -> { Record record = result.recordOrThrow(); System.out.println(" - " + record.getString("name")); }); }
// Batch update session .upsert(users.id(key1), users.id(key2)) .bin("status").setTo("active") .upsert(users.id(key3)) .bin("status").setTo("inactive") .execute().close(); System.out.println("\nBatch update complete");
// Batch delete session.delete(users.ids(key1, key2, key3)) .execute().close(); System.out.println("Batch delete complete"); } }}📖 API reference:
ClusterDefinition(String,int)|ClusterDefinition.connect()|Cluster.createSession(Behavior)|Cluster.close()|DataSet.of(...)|DataSet.ids(...)|Session.insert(DataSet)|Session.delete(List)|Session.delete(Key)|Session.query(List)|OperationObjectBuilder.bins(...)|IdValuesBuilder.id(...)|IdValuesRowBuilder.values(...)|ChainableQueryBuilder.bin(...)|ChainableQueryBuilder.execute()|ChainableNoBinsBuilder.execute()|RecordStream.forEach(...)|RecordStream.close()|RecordResult.recordOrThrow()|Record.getString(...)
Per-key batch writes are not yet exposed by the Python SDK. Each key in a batch write carries its own payload, and the whole set travels in one round trip — see the Java tab for the shape. This example will be filled in when the Python API lands.
Batch reads and batch deletes are available today: pass several keys to one command with
DataSet.ids().
API reference summary
| Java | Python | Description |
|---|---|---|
session.query(dataSet.ids(...)) | await session.query(data_set.ids(...)).execute() | Batch-read multiple record IDs |
session.insert(dataSet) + repeated .id().values() | pending | Batch insert with one request |
session.upsert(dataSet) + repeated .id().values() | pending | Batch upsert with one request |
session.delete(dataSet.ids(...)) | await session.delete(data_set.ids(...)).execute() | Batch delete multiple IDs |
.bins(...) (varargs) | .bins([...]) (list) | Select projected bins for batch reads |
RecordStream / forEach | RecordStream / async for | Iterate per-record results |
Next steps
Async Operations
Non-blocking operations for high throughput.
Query Records
Find records with DSL queries.
Behaviors
Configure batch timeouts and retries.