Skip to main content
Loading

Using transactions

This page describes how to create and use transactions in Aerospike Database.

Overview​

A transaction is several commands isolated from commands outside the transaction and executed atomically.

To ensure strict serializability, transactions in Aerospike require CP consistency mode, known in Aerospike as the strong-consistency (SC) namespace configuration. A transaction can guarantee one of two outcomes: either all commands succeed together, or one or more commands fail, in which case you can request to roll back to the state of the records prior to the attempted transaction. No commands outside the transaction can see the state changes being created inside the transaction.

Best practices​

  • Remember that transactions and single-record commands can be mixed in the same SC namespace.
  • Make sure to call abort() when your app gives up on a transaction. Not calling abort() invokes unnecessary monitor activity, and leaves records “locked” for much longer than necessary.
  • Use batch-writes to write a group of independent records in a transaction A batch is especially efficient inside a transaction.
  • Don’t mix expiration and non-durable deletes with transactions. This is a general recommendation for strong consistency namespaces.
  • Pay attention to tombstone accumulation and tomb-raider configs.
  • Do not truncate in a namespace with active transactions. If you must use transactions, see Pause and drain transactions before truncating for steps to first disable and drain existing transactions.
  • Do not mix transactions with active-active XDR, unless you’re certain that you are not writing to the same records on both sides. Use stretch clusters (multi-site clustering) instead of XDR if you intend to use transactions this way.

Create transaction​

The following steps use the Java client to describe how to create an instance of the Txn class that will later be used to logically bind all related requests together. Txn is a property of the base policy class and should be available to all policy classes that inherit from that class.

  1. Create a Txn ID.

    import com.aerospike.client.Txn;

    Txn txn = new Txn();
    System.out.println("Begin txn: " + txn.getId());

  2. Embed the transaction in a try block and attach the Txn ID.

Issue commands to your Aerospike client within a try block. Set policy.txn to the txn instance you created to bind the Txn ID to each command.

try {
// attach txn you created to the write policy
WritePolicy wp = client.copyWritePolicyDefault();
wp.txn = txn;

Key key1 = new Key(params.namespace, params.set, 1);
client.put(wp, key1, new Bin("a", "val1"));
}
  1. Commit the transaction.
System.out.println("Commit txn: " + txn.getId());
client.commit(txn);

How to handle transaction errors​

When an exception is thrown after attempting to embed the transaction in a try block, use the catch block to call client.abort(txn) and re-throw the exception.

catch (Throwable t) {
// Abort and rollback transaction (multi-record transaction) if any errors occur.
client.abort(txn);
throw t;
}

Status codes​

The following status codes detect and handle within the exception.

MRT_BLOCKED - You will receive this if your transaction was blocked by another transaction. MRT_EXPIRED - You will receive this if the deadline was reached without a commit or abort for the transaction.

The following sample code shows how to structure handling these new outcomes:

catch (AerospikeException) {
// Abort and rollback transaction (multi-record transaction) if any errors occur.
client.abort(txn);
if (ae.getResultCode() == MRT_BLOCKED){
// handle transaction being blocked by another transaction - retry the transaction
}
if (ae.getResultCode() == MRT_EXPIRED) {
// handle transaction expiring by hitting deadline prior to commit or abort; retry the transaction
}
throw ae;
}
catch (Throwable t) {
client.abort(txn);
// code for handling non-Aerospike errors
throw t;
}

Failed commits​

A commit may fail if the read verify step fails. Aerospike attempts to do the read, even if it is dirty during the command execution step. Aerospike later sends an exception with the MRT_VERSION_MISMATCH result code during the commit if the read is determined to be invalid due to version mismatch. This indicates that another command outside the transaction changed the targeted record. The correct way to handle this situation is to retry the entire transaction.

There is also a chance that the transactions succeed but the commit fails. An example of this would be if there is a problem updating transaction monitor on the server. In these situations, Aerospike returns a commit exception with status code TXN_FAILED, and if there is uncertainty about whether the transaction was committed fully, it sets the inDoubt flag to true.

To safely handle this situation you can catch the exception from the commit, and have separate handling logic as follows.

note

Recommit logic as shown is currently only supported in the transaction Java Client.

console.info("Commit txn: " + txn.getId());
try {
client.commit(txn);
}
catch (AerospikeException ae) {
if (ae.getInDoubt()){
// special handling for in doubt case - will need to attempt to commit again
try {
client.commit(txn);
}
catch(AerospikeException ae2) {
if (ae2.getInDoubt()){
// if recommit fails and still in doubt, you need to specially handle the records;
// possibly log them for later cleanup
}
else {
// otherwise retry transaction here
}
}
}
else {
if (ae.getResultCode() == MRT_VERSION_MISMATCH ) {
// Transaction was invalidated due to a read verification failure; retry
}
else {
// other commit failure - transaction is aborted so normally best course of action is to retry
}
}
}
catch (Throwable t) {
// handle non-Aerospike exception
}

Examples​

Example setting an transaction timeout​

You can set a timeout (in seconds) that dictates the total allowed time to complete all of the commands in the transaction and commit. You can set the timeout after you create the Txn object using Txn.setTimeout(int timeout). The timeout clock starts when the first transaction request for the transaction is submitted.

import com.aerospike.client.Txn;

Txn txn = new Txn();
System.out.println("Begin txn: " + txn.getId());

mrtTimeoutSecs = 20;
txn.setTimeout(mrtTimeoutSecs);

try {
// attach txn you created to the write policy
WritePolicy wp = client.copyWritePolicyDefault();
wp.txn = txn;

// mrtTimeout clock will start after this operation
Key key1 = new Key(params.namespace, params.set, 1);
client.put(wp, key1, new Bin("a", "val1"));

Key key2 = new Key(params.namespace, params.set, 2);
client.put(wp, key2, new Bin("b", "val2"));
}

Example with batched writes​

For batched writes, set the Txn instance within the batchPolicy parameter, not the writePolicy parameter:

import com.aerospike.client.Txn;

Txn txn = new Txn();

WritePolicy wp = client.copyWritePolicyDefault();
BatchPolicy bp = client.copyBatchPolicyDefault();

// for batched commands, attach the transaction instance to the batch policy, NOT the write policy
bp.txn = txn;

// then submit the transaction
try {
BatchResults bresults = client.operate(bp, wp, keys,
ListOperation.append(ListPolicy.Default, BinName3, Value.get(999)),
ListOperation.size(BinName3),
ListOperation.getByIndex(BinName3, -1, ListReturnType.VALUE)
);
}

Example with put, get, and delete in the same transaction​

The following is a longer example with multiple commands tied to the same transaction.

try {
WritePolicy wp = client.copyWritePolicyDefault();
wp.txn = txn;

Key key1 = new Key(params.namespace, params.set, 1);
client.put(wp, key1, new Bin("a", "val1"));

Policy p = client.copyReadPolicyDefault();
p.txn = txn;

Key key3 = new Key(params.namespace, params.set, 3);
Record rec = client.get(p, key3);

WritePolicy dp = client.copyWritePolicyDefault();
dp.txn = txn;
dp.durableDelete = true; // Required when running delete in a transaction.
client.delete(dp, key3);
}
catch (Throwable t) {
// Abort and rollback transaction (multi-record transaction) if any errors occur.
client.abort(txn);
throw t;
}

System.out.println("Commit txn: " + txn.getId());
client.commit(txn);