Skip to content

Use transactions

Transactions allow you to execute multiple operations atomically across different records. Either all operations succeed, or none do.

What are transactions?

Traditional Aerospike operations are atomic at the single-record level. Transactions extend atomicity across multiple records:

  • Atomicity: All operations commit or all abort
  • Consistency: Intermediate states are never visible
  • Isolation: Concurrent transactions don’t interfere
  • Durability: Committed transactions survive failures

Start a transaction

import com.aerospike.client.sdk.DataSet;
import com.aerospike.client.sdk.Record;
import com.aerospike.client.sdk.Session;
// Transactions run through Session.doInTransaction; commit happens when the block returns.
DataSet accounts = DataSet.of("test", "accounts");
try {
session.doInTransaction(txn -> {
txn.insert(accounts)
.bins("balance")
.id("account-A").values(1000)
.execute();
txn.insert(accounts)
.bins("balance")
.id("account-B").values(500)
.execute();
});
} catch (AerospikeException e) {
// On unsupported server/namespace configs this can fail (for example "Unsupported Server Feature").
System.err.println("Transaction failed: " + e.getMessage());
}

Use doInTransactionReturning(...) when you need a value back from the transaction:

String status = session.doInTransactionReturning(txn -> {
Record account = txn.query(accounts.id("account-A")).execute().getFirstRecord();
long balance = account != null ? account.getLong("balance") : 0L;
if (balance < 100) {
txn.abort();
return "insufficient-funds";
}
txn.update(accounts.id("account-A"))
.bin("balance").add(-100)
.execute();
return "debited";
});

doInTransaction and doInTransactionReturning (Java) and do_in_transaction (Python) automatically retry the whole callable if the transaction failed for what it considers a “retryable” reason. These are typically if the transaction being managed was blocked by another transaction. Retries occur on result codes of MRT_BLOCKED, MRT_VERSION_MISMATCH or TXN_FAILED. The number of retries and the duration between retries is controllable at the Behavior level.

CRUD within a transaction

All standard operations work within transactions:

import com.aerospike.client.sdk.DataSet;
import com.aerospike.client.sdk.Record;
import java.util.UUID;
DataSet accounts = DataSet.of("test", "accounts");
DataSet ledger = DataSet.of("test", "ledger");
DataSet pendingTransfers = DataSet.of("test", "pending_transfers");
session.doInTransaction(txn -> {
Record account = txn.query(accounts.id("account-A")).execute().getFirstRecord();
long balance = account != null ? account.getLong("balance") : 0L;
txn.update(accounts.id("account-A"))
.bin("balance").setTo(balance - 100)
.execute();
txn.insert(ledger)
.bins("from", "amount", "timestamp")
.id(UUID.randomUUID().toString()).values("account-A", 100, System.currentTimeMillis())
.execute();
txn.delete(pendingTransfers.id("transfer-123")).execute();
});

Commit and abort

Commit

Commit makes all transaction operations permanent:

// With session.doInTransaction(...), commit is performed automatically after the lambda completes.
// There is no separate txn.commit() API on Session — doInTransaction commits for you.

Abort

Abort discards all transaction operations:

// From inside doInTransaction, call txn.abort() to abort programmatically.
// Outside that API, letting an exception propagate aborts the transaction.

Exclude a specific operation from the current transaction (Java)

Use notInAnyTransaction() when a specific operation must execute outside the active transaction.

import com.aerospike.client.sdk.DataSet;
DataSet accounts = DataSet.of("test", "accounts");
DataSet audit = DataSet.of("test", "audit");
session.doInTransaction(txn -> {
txn.update(accounts.id("acct:1"))
.bin("balance").add(-100)
.execute();
// This write executes outside the transaction.
txn.insert(audit)
.bins("event", "amount")
.id("audit:1").values("debit", 100)
.notInAnyTransaction()
.execute();
});

Handling in-doubt state

If the client loses connection during commit, the transaction may be in an “in-doubt” state. However, the commit automatically retries depending on policy settings for up to 15 seconds; more than enough time for the cluster to re-adjust.

commit is an idempotent operation, so even if it does fail after this time, it is safe to retry the commit.

Retry patterns for transactions

Implement robust retry logic for transient failures. Note that Behaviors can be configured with automatic retries, so transient failures are typically only an issue on non-idempotent writes (which cannot easily be retried).

import com.aerospike.client.sdk.AerospikeException;
import com.aerospike.client.sdk.ResultCode;
public void transferWithRetry(Session session, int maxAttempts) throws InterruptedException {
AtomicBoolean timeoutFailure = new AtomicBoolean();
for (int attempt = 1; attempt <= maxAttempts; attempt++) {
timeoutFailure.set(false);
try {
session.doInTransaction(txn -> {
try {
// Non-idempotent operations cannot handle `inDoubt` well
txn.update(acctKey).bin("balance").add(500);
}
catch (Timeout timeout) {
timeoutFailure.set(true);
txn.abort();
}
});
if (timeoutFailure.get()) {
Thread.sleep((long) Math.pow(2, attempt) * 10L);
continue;
}
return;
} catch (AerospikeException e) {
if ((e.getResultCode() == ResultCode.MRT_BLOCKED
|| e.getResultCode() == ResultCode.MRT_VERSION_MISMATCH
|| e.getResultCode() == ResultCode.TXN_FAILED)
&& attempt < maxAttempts) {
Thread.sleep((long) Math.pow(2, attempt) * 10L);
continue;
}
throw e;
}
}
}

Complete example

import com.aerospike.client.sdk.AerospikeException;
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.Session;
import com.aerospike.client.sdk.policy.Behavior;
public class TransactionTransferExample {
public static void main(String[] args) {
try (Cluster cluster = new ClusterDefinition("localhost", 3000).connect()) {
Session session = cluster.createSession(Behavior.DEFAULT);
DataSet accounts = DataSet.of("test", "accounts");
String from = "txn-example-A";
String to = "txn-example-B";
long transfer = 100L;
// Cleanup + seed so the example is repeatable.
session.delete(accounts.ids(from, to)).execute();
session.insert(accounts)
.bins("owner", "balance")
.id(from).values("Alice", 1000L)
.id(to).values("Bob", 300L)
.execute();
// Atomic transfer.
try {
session.doInTransaction(txn -> {
Record fromRec = txn.query(accounts.id(from)).execute().getFirstRecord();
Record toRec = txn.query(accounts.id(to)).execute().getFirstRecord();
long fromBal = fromRec != null ? fromRec.getLong("balance") : 0L;
long toBal = toRec != null ? toRec.getLong("balance") : 0L;
if (fromBal < transfer) {
txn.abort();
return;
}
txn.update(accounts.id(from))
.bin("balance").add(-transfer)
.execute();
txn.update(accounts.id(to))
.bin("balance").add(transfer)
.execute();
});
// Verify resulting balances.
Record afterFrom = session.query(accounts.id(from)).execute().getFirstRecord();
Record afterTo = session.query(accounts.id(to)).execute().getFirstRecord();
System.out.println("From balance: " + (afterFrom != null ? afterFrom.getLong("balance") : null));
System.out.println("To balance: " + (afterTo != null ? afterTo.getLong("balance") : null));
} catch (AerospikeException e) {
System.err.println("Transaction failed: " + e.getMessage());
System.err.println("Ensure this namespace supports transactions (SC namespace on server 8.0+).");
}
}
}
}

Transaction limitations

LimitationValueNotes
Max records per transaction128Configurable on server
Max transaction duration10sDefault timeout
NamespacesSC onlyStrong consistency required
OperationsPoint-key CRUDFull-set query/scan and batch APIs are not transactional

Best practices

  1. Keep transactions short — Minimize time between begin and commit
  2. Limit scope — Include only records that must be atomic
  3. Handle conflicts — Implement retry logic for concurrent modifications
  4. Use idempotent operations — Enables safe retries
  5. Monitor in-doubt state — Log and alert on in-doubt transactions

Next steps

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?