Skip to content

Async operations

For the complete documentation index see: llms.txt

All documentation pages available in markdown.

Applies to

  • Aerospike Developer SDK preview (Java 21+ and Python 3.10+)
  • Aerospike Database 6.0 or later unless a section states otherwise

Learn how to use asynchronous operations for high-throughput, non-blocking database access. Async operations allow your application to continue processing while waiting for database responses.

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.ErrorStrategy;
import com.aerospike.client.sdk.Record;
import com.aerospike.client.sdk.RecordResult;
import com.aerospike.client.sdk.RecordStream;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
import java.util.function.Function;

Why async?

ApproachBehaviorBest For
SyncBlocks thread until responseSimple scripts, low concurrency
AsyncReturns immediately, notifies on completionHigh throughput, web servers, microservices

See Error handling for more information about async execution modes.

Async read

DataSet users = DataSet.of("test", "users");
// Non-blocking: results are published to the returned stream as they arrive.
RecordStream stream = session.query(users.id("user-1")).executeAsync(ErrorStrategy.IN_STREAM);
stream.getFirst().ifPresent(result -> {
if (result.isOk()) {
Record user = result.recordOrThrow();
System.out.println("Name: " + user.getString("name"));
}
});

📖 API reference: DataSet.of(...) | DataSet.id(...) | Session.query(Key) | ChainableQueryBuilder.executeAsync(...) | RecordStream.getFirst() | RecordResult.isOk() | RecordResult.recordOrThrow() | Record.getString(...) | ErrorStrategy | ErrorStrategy.IN_STREAM

Async write

DataSet users = DataSet.of("test", "users");
RecordStream writeStream = session.insert(users)
.bins("name", "email")
.id("user-1").values("Alice", "alice@example.com")
.executeAsync(ErrorStrategy.IN_STREAM);
writeStream.forEach(result -> {
if (result.isOk()) {
System.out.println("Insert complete for key index " + result.index());
}
});

📖 API reference: DataSet.of(...) | Session.insert(DataSet) | OperationObjectBuilder.bins(...) | IdValuesBuilder.id(...) | IdValuesRowBuilder.values(...) | ChainableQueryBuilder.executeAsync(...) | RecordStream.forEach(...) | RecordResult.isOk() | ErrorStrategy | ErrorStrategy.IN_STREAM

Parallel operations

Execute multiple operations concurrently:

DataSet users = DataSet.of("test", "users");
// Parallelize with virtual threads: each task runs a synchronous query and maps the first record.
Function<String, Optional<Record>> readOne = id -> {
RecordStream s = session.query(users.id(id)).execute();
return s.getFirst().filter(RecordResult::isOk).map(RecordResult::recordOrThrow);
};
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
CompletableFuture<Optional<Record>> u1 = CompletableFuture.supplyAsync(() -> readOne.apply("user-1"), executor);
CompletableFuture<Optional<Record>> u2 = CompletableFuture.supplyAsync(() -> readOne.apply("user-2"), executor);
CompletableFuture<Optional<Record>> u3 = CompletableFuture.supplyAsync(() -> readOne.apply("user-3"), executor);
CompletableFuture.allOf(u1, u2, u3).join();
List<Optional<Record>> results = List.of(u1.join(), u2.join(), u3.join());
}

📖 API reference: DataSet.of(...) | Session.query(Key) | ChainableQueryBuilder.execute() | RecordStream.getFirst() | RecordResult.isOk()

Async with callbacks

Handle results with callbacks (Java) or an await chain (Python) instead of blocking:

DataSet users = DataSet.of("test", "users");
CompletableFuture.runAsync(() -> {
RecordStream stream = session.query(users.id("user-1")).executeAsync(ErrorStrategy.IN_STREAM);
stream.getFirst().ifPresent(result -> {
if (result.isOk()) {
Record user = result.recordOrThrow();
System.out.println("Got user: " + user.getString("name"));
}
});
});
System.out.println("Operation submitted");

📖 API reference: DataSet.of(...) | DataSet.id(...) | Session.query(Key) | ChainableQueryBuilder.executeAsync(...) | RecordStream.getFirst() | RecordResult.isOk() | RecordResult.recordOrThrow() | Record.getString(...) | ErrorStrategy | ErrorStrategy.IN_STREAM

Async pipeline

Chain operations together:

DataSet users = DataSet.of("test", "users");
DataSet orders = DataSet.of("test", "orders");
session.query(users.id("user-1"))
.bins("loyalty_tier")
.executeAsync(ErrorStrategy.IN_STREAM)
.asCompletableFuture()
.thenCompose(results -> {
String tier = results.get(0).recordOrThrow().getString("loyalty_tier");
double discount = "gold".equals(tier) ? 0.20 : 0.05;
return session.update(orders.id("order-99"))
.bin("discount").setTo(discount)
.executeAsync(ErrorStrategy.IN_STREAM)
.asCompletableFuture();
})
.thenRun(() -> System.out.println("Discount applied!"))
.exceptionally(ex -> {
System.err.println("Pipeline failed: " + ex.getMessage());
return null;
});

📖 API reference: DataSet.of(...) | DataSet.id(...) | Session.update(DataSet) | Session.query(Key) | OperationObjectBuilder.bins(...) | ChainableOperationBuilder.bin(...) | ChainableQueryBuilder.bin(...) | ChainableQueryBuilder.executeAsync(...) | RecordStream.asCompletableFuture() | RecordResult.recordOrThrow() | Record.getString(...) | ErrorStrategy | ErrorStrategy.IN_STREAM

Async batch

Batch operations also support async:

DataSet users = DataSet.of("test", "users");
CompletableFuture.runAsync(() -> {
RecordStream stream = session
.query(users.id("user-1"), users.id("user-2"), users.id("user-3"))
.executeAsync(ErrorStrategy.IN_STREAM);
long count = 0;
try {
while (stream.hasNext()) {
RecordResult rr = stream.next();
if (rr.isOk() && rr.recordOrNull() != null) {
count++;
}
}
} finally {
stream.close();
}
System.out.println("Batch complete, got " + count + " records");
});

📖 API reference: DataSet.of(...) | DataSet.id(...) | ChainableQueryBuilder.executeAsync(...) | RecordStream.hasNext() | RecordStream.next() | RecordStream.close() | RecordResult.isOk() | ErrorStrategy | ErrorStrategy.IN_STREAM

Async query

Stream query results asynchronously:

DataSet users = DataSet.of("test", "users");
CompletableFuture.runAsync(() -> {
RecordStream queryStream = session.query(users)
.where("$.status == 'active'")
.executeAsync(ErrorStrategy.IN_STREAM);
queryStream.forEach((RecordResult result) -> {
if (result.isOk()) {
Record user = result.recordOrThrow();
System.out.println("Active user: " + user.getString("name"));
}
});
});

📖 API reference: DataSet.of(...) | Session.query(DataSet) | ChainableQueryBuilder.where(...) | ChainableQueryBuilder.executeAsync(...) | RecordStream.forEach(...) | RecordResult.isOk() | RecordResult.recordOrThrow() | Record.getString(...) | ErrorStrategy | ErrorStrategy.IN_STREAM

AsyncPool (Python, free-threaded builds)

AsyncPool runs your async work on several CPU cores at once for higher throughput. Each pool loop gets its own Client — the pool calls client_factory() once per loop at construction, so a pool with loop_count=4 holds 4 Clients total and reuses them across every dispatched item.

It only speeds things up on free-threaded CPython (run with PYTHON_GIL=0); on standard CPython it still works correctly but gives no speedup, because the GIL forces all the work onto one core.

# Additional imports for this example:
from aerospike_sdk import AsyncPool, Behavior, Client
async def main():
pool = AsyncPool(
client_factory=lambda: Client("localhost:3000"),
loop_count=4,
)
async with pool:
users = DataSet.of("test", "users")
# One-shot dispatch via pool.run:
await pool.run(
lambda client: client.create_session(Behavior.DEFAULT)
.upsert(users.id(1))
.put({"name": "Alice"})
.execute()
)
# Run 1000 upserts in parallel, spread across the pool's 4 loops:
async def upsert_one(client: Client, uid: int) -> None:
await (
client.create_session(Behavior.DEFAULT)
.upsert(users.id(uid))
.put({"name": f"User {uid}"})
.execute()
)
await pool.map(upsert_one, range(1, 1001))

For fast-path single-key reads and writes (session.get, session.put), performance mode trade-offs, and benchmark guidance, see the Python SDK performance guide.

Java reactive streams with asPublisher()

asPublisher() returns a java.util.concurrent.Flow.Publisher<RecordResult> with backpressure. The stream closes automatically when the subscription completes, errors, or is cancelled. This is unicast only; a second subscriber receives onError(IllegalStateException).

Use it for large or unbounded query results where collecting everything via asCompletableFuture() is impractical:

// Additional imports for this example:
import java.util.concurrent.Flow;
DataSet users = DataSet.of("test", "users");
Flow.Publisher<RecordResult> publisher = session.query(users)
.where("$.status == 'active'")
.executeAsync(ErrorStrategy.IN_STREAM)
.asPublisher();
publisher.subscribe(new Flow.Subscriber<>() {
private Flow.Subscription subscription;
@Override
public void onSubscribe(Flow.Subscription sub) {
subscription = sub;
sub.request(100); // initial batch; request more in onNext for backpressure
}
@Override
public void onNext(RecordResult result) {
if (result.isOk()) {
System.out.println(result.recordOrThrow().getString("name"));
}
subscription.request(1);
}
@Override
public void onError(Throwable t) {
System.err.println("Publisher error: " + t.getMessage());
}
@Override
public void onComplete() {
System.out.println("Query complete");
}
});

📖 API reference: Session.query(DataSet) | ChainableQueryBuilder.where(...) | ChainableQueryBuilder.executeAsync(...) | RecordStream.asPublisher() | RecordResult.isOk() | RecordResult.recordOrThrow() | ErrorStrategy.IN_STREAM

Async error handling

Async operations use the same error modes as synchronous execute(). Pass ErrorStrategy.IN_STREAM to embed failures in the stream, or an error-handler callback to dispatch them separately. See Error handling for the full mode comparison.

Errors in the stream (IN_STREAM)

Use executeAsync(ErrorStrategy.IN_STREAM) (Java) or execute(on_error=ErrorStrategy.IN_STREAM) (Python) so single-key and batch async operations return failures as RecordResult entries instead of throwing:

DataSet users = DataSet.of("test", "users");
RecordStream stream = session
.update(users.id("exists")).bin("count").add(1)
.update(users.id("no-such-key")).bin("count").add(1)
.executeAsync(ErrorStrategy.IN_STREAM);
stream.forEach(result -> {
if (result.isOk()) {
System.out.println(result.key().userKey + ": success");
} else {
System.out.println(result.key().userKey + ": " + result.message());
}
});

📖 API reference: DataSet.of(...) | DataSet.id(...) | Session.update(DataSet) | ChainableQueryBuilder.bin(...) | BinBuilder.add(...) | ChainableQueryBuilder.executeAsync(...) | RecordStream.forEach(...) | RecordResult.isOk() | ErrorStrategy.IN_STREAM

Error callback

Pass an error handler to executeAsync(handler) (Java) or execute(on_error=<callable>) (Python). Failures are dispatched to the callback and excluded from the returned stream:

DataSet users = DataSet.of("test", "users");
RecordStream stream = session
.update(users.id("exists")).bin("count").add(1)
.update(users.id("no-such-key")).bin("count").add(1)
.executeAsync((key, index, exception) ->
System.err.println("Failed key " + key.userKey +
" at index " + index + ": " + exception.getMessage())
);
// Stream contains only successful results
stream.forEach(result ->
System.out.println(result.key().userKey + ": OK")
);

📖 API reference: DataSet.of(...) | DataSet.id(...) | ChainableQueryBuilder.bin(...) | BinBuilder.add(...) | ChainableQueryBuilder.executeAsync(...) | RecordStream.forEach(...)

API reference summary

MethodDescription
Java: .executeAsync(ErrorStrategy.IN_STREAM)Non-blocking execution; failures embedded in the stream
Java: .executeAsync(ErrorHandler)Non-blocking execution; failures dispatched to callback
Python: await … .execute(on_error=ErrorStrategy.IN_STREAM)Non-blocking execution; failures embedded in the stream
Python: await … .execute(on_error=<callable>)Non-blocking execution; failures dispatched to callback
Python: await <builder>.execute() on an aerospike_sdk.aio session/builderNon-blocking execution that returns a RecordStream
CompletableFuture + virtual threadsCompose parallel synchronous execute() calls (Java)
CompletableFuture.allOf()Wait for multiple futures (Java)
awaitWait for async result (Python)
asyncio.gather()Wait for multiple tasks (Python)
AsyncPoolMulti-loop async on free-threaded Python
session.get() / session.put()Fast-path single-key API (Python, sync and async)

Next steps

Behaviors

Configure timeouts and retries for async.

Behaviors →

Feedback

Was this page helpful?

What type of feedback are you giving?

What would you like us to know?

+Capture screenshot

Can we reach out to you?