Introduction to Transactions
For an interactive Jupyter notebook experience:
Last updated: June 22, 2021
This notebook explains the basics of executing multiple operations on one record as a transaction.
Aerospike was architected to process a high volume of concurrent real time reads and writes for Internet scale applications. Aerospike provides scale out performance by adding additional nodes or racks without changing application code.
Application code specifies the necessary process and data policy to execute one or billions of Aerospike read and write operations.
This notebook covers:
- Discrete operations:
- Record operations
- Operations on simple data types:
- Strings
- Integers
- Doubles
- Operations on complex data types:
- Blobs
- HyperLogLogs
- Lists
- Maps
- GeoJSON
- Simple transactions combining all of the above.
This notebook does not detail replication across a cluster.
This Jupyter Notebook requires the Aerospike Database running locally with Java kernel and Aerospike Java Client. To create a Docker container that satisfies the requirements and holds a copy of these notebooks, visit the Aerospike Notebooks Repo.
Notebook Setup
Import Jupyter Java Integration
import io.github.spencerpark.ijava.IJava;
import io.github.spencerpark.jupyter.kernel.magic.common.Shell;
IJava.getKernelInstance().getMagics().registerMagics(Shell.class);
Start Aerospike
%sh asd
Download the Aerospike Java Client
%%loadFromPOM
<dependencies>
<dependency>
<groupId>com.aerospike</groupId>
<artifactId>aerospike-client</artifactId>
<version>5.0.0</version>
</dependency>
</dependencies>
Start the Aerospike Java Client and Connect
The default cluster location for the Docker container is localhost port 3000. If your cluster is not running on your local machine, modify localhost and 3000 to the values for your Aerospike cluster.
import com.aerospike.client.AerospikeClient;
AerospikeClient client = new AerospikeClient("localhost", 3000);
System.out.println("Initialized the client and connected to the cluster.");
Output:
Initialized the client and connected to the cluster.
Aerospike Provides Intuitive Atomic Reads and Writes
Aerospike provides client APIs to read and write different types of data. Each record read or write operation that an Aerospike server or cluster executes as an atomic (ACID) operation.
For additional information on Aerospike's single-record variant of ACID compliance, go
here.Operate Applies One or More Operations
For the case where an application uses Aerospike as a key/value store without applying Aerospike data types to data, the AerospikeClient provides the super fast, basic getters and setters for bins of data.
The most frequently used and simplest technique to execute more than one operation in an atomic fashion is the Operate API. Using this API, the Aerospike client can execute a single read or write operation or complex combinations of reads and writes
For more information on Operate, go here.
For more information on applying multiple operations to a record, go here.
Using Discrete Operations on a Full Record
Aerospike provides the following record operations:
- Get – Read a record.
- Getheader – Read a record's TTL and generation counter.
- Touch – Increase a record's generation counter.
- Delete – Delete a record.
Create Test Data
Create a simple instance of every Aerospike data type.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
String txnString = "atomic";
Integer txnInteger = 8;
Double txnDouble = 6.022;
byte[] txnBlob = new byte[] {0b00000001, 0b00000010, 0b00000011, 0b00000100, 0b00000101};
String txnGeo = String.format("{ \"type\": \"Polygon\", \"coordinates\": [ [[-122.500, 37.000], [-121.000, 37.000], [-121.000, 38.080], [-122.500, 38.080], [-122.500, 37.000]] ] }");
ArrayList<Integer> txnList = new ArrayList<Integer>();
txnList.add(1);
HashMap<Integer, Integer> txnMap = new HashMap <Integer, Integer>();
txnMap.put(2, 4);
System.out.println("String: " + txnString);
System.out.println("Integer: " + txnInteger);
System.out.println("Double: " + txnDouble);
System.out.println("Blob: " + Arrays.toString(txnBlob));
System.out.println("HLL: Starts with no data.");
System.out.println("Geo: " + txnGeo);
System.out.println("List: " + txnList);
System.out.println("Map: " + txnMap);
Output:
String: atomic
Integer: 8
Double: 6.022
Blob: [1, 2, 3, 4, 5]
HLL: Starts with no data.
Geo: { "type": "Polygon", "coordinates": [ [[-122.500, 37.000], [-121.000, 37.000], [-121.000, 38.080], [-122.500, 38.080], [-122.500, 37.000]] ] }
List: [1]
Map: {2=4}
Put Data Into An Aerospike Record
import com.aerospike.client.Key;
import com.aerospike.client.Bin;
import com.aerospike.client.Value;
import com.aerospike.client.policy.ClientPolicy;
Integer theKey = 0;
String txnSet = "txnset";
String txnNamespace = "test";
String txnStringBin = "str";
String txnIntegerBin = "int";
String txnDoubleBin = "double";
String txnBlobBin = "blob";
String txnHLLBin = "hll";
String txnGeoBin = "geo";
String txnListBin = "list";
String txnMapBin = "map";
ClientPolicy clientPolicy = new ClientPolicy();
Key key = new Key(txnNamespace, txnSet, theKey);
Bin bin0 = new Bin(txnStringBin, txnString);
Bin bin1 = new Bin(txnIntegerBin, txnInteger);
Bin bin2 = new Bin(txnDoubleBin, txnDouble);
Bin bin3 = new Bin(txnBlobBin, txnBlob);
Bin bin4 = new Bin(txnHLLBin, Value.getAsNull());
Bin bin5 = new Bin(txnGeoBin, Value.getAsGeoJSON(txnGeo));
Bin bin6 = new Bin(txnListBin, txnList);
Bin bin7 = new Bin(txnMapBin, txnMap);
client.put(clientPolicy.writePolicyDefault, key, bin0, bin1, bin2, bin3, bin5, bin6, bin7);
System.out.println("Put data into Aerospike: "
+ txnStringBin + ", "
+ txnIntegerBin + ", "
+ txnDoubleBin + ", "
+ txnBlobBin + ", "
+ txnHLLBin + ", "
+ txnGeoBin + ", "
+ txnListBin + ", "
+ txnMapBin);
Output:
Put data into Aerospike: str, int, double, blob, hll, geo, list, map
Get the Record
import com.aerospike.client.Record;
Key key = new Key(txnNamespace, txnSet, theKey);
Record record = client.get(null, key);
System.out.println(record);
Output:
(gen:4),(exp:358043147),(bins:(str:atomic),(int:8),(double:6.022),(blob:[B@5c70658),(geo:{ "type": "Polygon", "coordinates": [ [[-122.500, 37.000], [-121.000, 37.000], [-121.000, 38.080], [-122.500, 38.080], [-122.500, 37.000]] ] }),(list:[1]),(map:{2=4}),(hll:00080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000400000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000c0000000000))
Get the Record Header
Key key = new Key(txnNamespace, txnSet, theKey);
Record record = client.getHeader(null, key);
System.out.println(record);
Output:
(gen:4),(exp:358043147),(bins:null)
Touch the Record
Key key = new Key(txnNamespace, txnSet, theKey);
client.touch(client.writePolicyDefault, key);
Record record = client.get(client.writePolicyDefault, key);
System.out.println(record);
Output:
(gen:5),(exp:358043147),(bins:(str:atomic),(int:8),(double:6.022),(blob:[B@3efad74b),(geo:{ "type": "Polygon", "coordinates": [ [[-122.500, 37.000], [-121.000, 37.000], [-121.000, 38.080], [-122.500, 38.080], [-122.500, 37.000]] ] }),(list:[1]),(map:{2=4}),(hll:00080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000400000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000c0000000000))
Operating on Simple Bin Data
When operating on simple data–Strings, Integers, and Doubles–, Aerospike provides standard create, read, update, and delete operations and also increment operations, like prepend/append and add.
For more information on record operations and simple data operations, go here.
Operating on String Data
Append to a string.
String txnAppendString = "-operation";
bin0 = new Bin(txnStringBin, txnAppendString);
Key key = new Key(txnNamespace, txnSet, theKey);
Record record = client.get(client.writePolicyDefault, key);
client.append(client.writePolicyDefault, key, bin0);
Record after = client.get(client.writePolicyDefault, key);
System.out.println("Before, the " + txnStringBin + " was - " + record.getValue(txnStringBin));
System.out.println(" After, the " + txnStringBin + " is - " + after.getValue(txnStringBin));
Output:
Before, the str was - atomic
After, the str is - atomic-operation
Operating on Integer Data
Add to an integer.
Integer txnAddInt = 5;
Bin binIntAdd = new Bin(txnIntegerBin, txnAddInt);
Key key = new Key(txnNamespace, txnSet, theKey);
Record record = client.get(client.writePolicyDefault, key);
client.add(client.writePolicyDefault, key, binIntAdd);
Record after = client.get(client.writePolicyDefault, key);
System.out.println("Before, the " + txnIntegerBin + " was - " + record.getValue(txnIntegerBin));
System.out.println(" After, the " + txnIntegerBin + " is - " + after.getValue(txnIntegerBin));
Output:
Before, the int was - 8
After, the int is - 13
Operating on Double Data
Subtract from a double.
Double txnAddDouble = -3.142;
Bin binDoubleAdd = new Bin(txnDoubleBin, txnAddDouble);
Key key = new Key(txnNamespace, txnSet, theKey);
Record record = client.get(client.writePolicyDefault, key);
client.add(client.writePolicyDefault, key, binDoubleAdd);
Record after = client.get(client.writePolicyDefault, key);
System.out.println("Before, the " + txnDoubleBin + " was - " + record.getValue(txnDoubleBin));
System.out.println(" After, the " + txnDoubleBin + " is - " + after.getValue(txnDoubleBin));
Output:
Before, the double was - 6.022
After, the double is - 2.8800000000000003
Operating on Complex Data Types
Aerospike also provides data-type-specific operations to work with complex data types:
- Collection Data Types
- Lists
- Maps
- Blob/Bit Data
- HyperLogLog (as a HyperMinHash)
- GeoJSON
Operating on Lists
Append 5 to the list.
Aerospike provides operations to create and manage:
- a simple list
- list containing lists
- list containing maps
For a tutorial on working with lists, go here. For more information on ListOperations, go here.
import com.aerospike.client.cdt.ListOperation;
Integer listAddition = 5;
Key key = new Key(txnNamespace, txnSet, theKey);
Record record = client.get(client.writePolicyDefault, key);
client.operate(client.writePolicyDefault, key,
ListOperation.append(txnListBin, Value.get(listAddition))
);
Record after = client.get(client.writePolicyDefault, key);
System.out.println("Before, the " + txnListBin + " was - " + record.getValue(txnListBin));
System.out.println(" After, the " + txnListBin + " is - " + after.getValue(txnListBin));
Output:
Before, the list was - [1]
After, the list is - [1, 5]
Operating on Maps
Increment the Value of mapkey 2 by 57.
Aerospike provides operations to create and manage:
- a map
- a map containing lists
- a map containing maps
For a tutorial on working with maps, go here.
For more information on Map Operations, go here.
import com.aerospike.client.cdt.MapOperation;
import com.aerospike.client.cdt.MapPolicy;
Integer mapKey = 2;
Integer mapIncrementValue = 57;
MapPolicy txnMapPolicy = new MapPolicy();
Key key = new Key(txnNamespace, txnSet, theKey);
Record record = client.get(client.writePolicyDefault, key);
client.operate(client.writePolicyDefault, key,
MapOperation.increment(txnMapPolicy, txnMapBin, Value.get(mapKey), Value.get(mapIncrementValue))
);
Record after = client.get(client.writePolicyDefault, key);
System.out.println("Before, the " + txnMapBin + " was - " + record.getValue(txnMapBin));
System.out.println(" After, the " + txnMapBin + " is - " + after.getValue(txnMapBin));
Output:
Before, the map was - {2=4}
After, the map is - {2=61}
Operating on Blob/Bit Data
In addition to CRUD operations, Aerospike provides standard bitwise operations, such as logical operators AND, OR, and NOT, add/subtract, and shifts.
For more information on Bit Operations, go here.
import com.aerospike.client.operation.BitOperation;
import com.aerospike.client.operation.BitPolicy;
byte[] bitsToSet = new byte[] {(byte)0b11100000};
Integer bitSize = 8;
Integer bitOffset = 13;
BitPolicy bitPolicy = new BitPolicy();
Key key = new Key(txnNamespace, txnSet, theKey);
Record record = client.get(client.writePolicyDefault, key);
client.operate(client.writePolicyDefault, key,
BitOperation.set(bitPolicy.Default, txnBlobBin, bitOffset, bitSize, bitsToSet)
);
Record after = client.get(client.writePolicyDefault, key);
byte[] beforeBytes = (byte[])record.getValue(txnBlobBin);
byte[] afterBytes = (byte[])after.getValue(txnBlobBin);
System.out.println("Before, the " + txnBlobBin + " was - " + Arrays.toString(beforeBytes));
System.out.println(" After, the " + txnBlobBin + " is - " + Arrays.toString(afterBytes));
Output:
Before, the blob was - [1, 2, 3, 4, 5]
After, the blob is - [1, 7, 3, 4, 5]
Operating on HyperLogLog Data
Init the HyperLogLog bin.
HyperLogLog is a probabilistic data type used for counting really large data sets. Aerospike provides operations to:
- Maintain the data type such as init, and reset
- Add data to these data sets.
- Compare HyperLogLog data such as intersection and unions.
For more information on HyperLogLog Operations, go here.
import com.aerospike.client.operation.HLLOperation;
import com.aerospike.client.operation.HLLPolicy;
HLLPolicy defHLLPolicy = new HLLPolicy();
Integer bitsHLLIndex = 8;
Key key = new Key(txnNamespace, txnSet, theKey);
Record record = client.get(client.writePolicyDefault, key);
client.operate(client.writePolicyDefault, key,
HLLOperation.init(defHLLPolicy, txnHLLBin, bitsHLLIndex)
);
Record after = client.get(client.writePolicyDefault, key);
System.out.println("Before, the " + txnHLLBin + " was - " + record.getValue(txnHLLBin));
System.out.println(" After, the " + txnHLLBin + " is - " + after.getValue(txnHLLBin));
Output:
Before, the hll was - 00080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000400000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000c0000000000
After, the hll is - 0008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
Operating on GeoJSON
Get GeoJSON data.
For the purposes of simple transactions, Aerospike quickly stores and retrieves GeoJSON data in bins, and optionally nested in maps. In addition, Aerospike validates GeoJSON and processes Geospatial queries, including circle queries.
For more information on using Aerospike for geospatial indexes and queries, go
here.Key key = new Key(txnNamespace, txnSet, theKey);
Record pullGeo = client.get(client.writePolicyDefault, key, txnGeoBin);
System.out.println("The " + txnGeoBin + " is - " + pullGeo.getValue(txnGeoBin));
Output:
The geo is - { "type": "Polygon", "coordinates": [ [[-122.500, 37.000], [-121.000, 37.000], [-121.000, 38.080], [-122.500, 38.080], [-122.500, 37.000]] ] }
Performing a Simple Transaction on a Record
The above operations were each performed as atomic operations. Operate executes multiple operations to one or more bins during a single record lock in an ACID-compliant fashion. Results are returned in an array for each bin.
Execute All of the Previous Operations as a Transaction
Create data for each data type.
Put it into Aerospike.
Apply the following operations as one transaction.
- Touch the record.
- Append to the string.
- Increment the integer.
- Subtract from the double.
- Put an item in the list.
- Put a new value in the map.
- Set bits in the blob.
- Init and add set elements to the hyperloglog.
- Get the GeoJSON.
// Create data for each data type.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
String txnString = "atomic";
Integer txnInteger = 8;
Double txnDouble = 6.022;
byte[] txnBlob = new byte[] {0b00000001, 0b00000010, 0b00000011, 0b00000100, 0b00000101};
String txnGeo = String.format("{ \"type\": \"Polygon\", \"coordinates\": [ [[-122.500, 37.000], [-121.000, 37.000], [-121.000, 38.080], [-122.500, 38.080], [-122.500, 37.000]] ] }");
ArrayList<Integer> txnList = new ArrayList<Integer>();
txnList.add(1);
HashMap<Integer, Integer> txnMap = new HashMap <Integer, Integer>();
txnMap.put(2, 4);
System.out.println("--Initial Data–-");
System.out.println("String: " + txnString);
System.out.println("Integer: " + txnInteger);
System.out.println("Double: " + txnDouble);
System.out.println("Blob: " + Arrays.toString(txnBlob));
System.out.println("HLL: Starts with no data.");
System.out.println("Geo: " + txnGeo);
System.out.println("List: " + txnList);
System.out.println("Map: " + txnMap);
System.out.println();
// Put it into Aerospike.
import com.aerospike.client.AerospikeClient;
import com.aerospike.client.Key;
import com.aerospike.client.Bin;
import com.aerospike.client.Value;
import com.aerospike.client.policy.ClientPolicy;
import com.aerospike.client.Operation;
import com.aerospike.client.cdt.ListOperation;
import com.aerospike.client.cdt.MapOperation;
import com.aerospike.client.cdt.MapPolicy;
import com.aerospike.client.operation.BitOperation;
import com.aerospike.client.operation.BitPolicy;
import com.aerospike.client.operation.HLLOperation;
import com.aerospike.client.operation.HLLPolicy;
Integer theKey = 0;
String txnSet = "txnset";
String txnNamespace = "test";
String txnStringBin = "str";
String txnIntegerBin = "int";
String txnDoubleBin = "double";
String txnBlobBin = "blob";
String txnHLLBin = "hll";
String txnGeoBin = "geo";
String txnListBin = "list";
String txnMapBin = "map";
AerospikeClient client = new AerospikeClient("localhost", 3000);
ClientPolicy clientPolicy = new ClientPolicy();
BitPolicy bitPolicy = new BitPolicy();
HLLPolicy defHLLPolicy = new HLLPolicy();
MapPolicy txnMapPolicy = new MapPolicy();
Key key = new Key(txnNamespace, txnSet, theKey);
Bin bin0 = new Bin(txnStringBin, txnString);
Bin bin1 = new Bin(txnIntegerBin, txnInteger);
Bin bin2 = new Bin(txnDoubleBin, txnDouble);
Bin bin3 = new Bin(txnBlobBin, txnBlob);
Bin bin4 = new Bin(txnHLLBin, Value.getAsNull());
Bin bin5 = new Bin(txnGeoBin, Value.getAsGeoJSON(txnGeo));
Bin bin6 = new Bin(txnListBin, txnList);
Bin bin7 = new Bin(txnMapBin, txnMap);
client.put(clientPolicy.writePolicyDefault, key, bin0, bin1, bin2, bin3, bin5, bin6, bin7);
// Apply the following operations as one transaction.
// 1. Touch the record.
// 2. Append to the string.
// 3. Increment the integer.
// 4. Subtract from the double.
// 5. Put an item in the list.
// 6. Increment a value in the map.
// 7. Set bits in the blob.
// 8. Init and add set elements to the hyperloglog.
// 9. Get the GeoJSON.
String txnAppendString = "-transactions";
Bin binStrAppend = new Bin(txnStringBin, txnAppendString);
Integer txnAddInt = 5;
Bin binIntAdd = new Bin(txnIntegerBin, txnAddInt);
Double txnAddDouble = -3.142;
Bin binDoubleSub = new Bin(txnDoubleBin, txnAddDouble);
byte[] bitsToSet = new byte[] {(byte)0b11100000};
Integer bitSize = 8;
Integer bitOffset = 13;
Integer bitsHLLIndex = 8;
Integer listAddition = 5;
Integer mapKey = 2;
Integer mapIncrementValue = 57;
ArrayList<Value> dataListForHLL = new ArrayList<Value>();
dataListForHLL.add(Value.get(txnAddInt));
dataListForHLL.add(Value.get(bitSize));
dataListForHLL.add(Value.get(bitOffset));
dataListForHLL.add(Value.get(bitsHLLIndex));
dataListForHLL.add(Value.get(listAddition));
dataListForHLL.add(Value.get(mapKey));
dataListForHLL.add(Value.get(mapIncrementValue));
Record beforeOps = client.get(client.writePolicyDefault, key);
Record operationsRecord = client.operate(client.writePolicyDefault, key,
Operation.touch(),
Operation.append(binStrAppend),
Operation.add(binIntAdd),
Operation.add(binDoubleSub),
ListOperation.append(txnListBin, Value.get(listAddition)),
MapOperation.increment(txnMapPolicy, txnMapBin, Value.get(mapKey), Value.get(mapIncrementValue)),
BitOperation.set(bitPolicy.Default, txnBlobBin, bitOffset, bitSize, bitsToSet),
HLLOperation.init(defHLLPolicy, txnHLLBin, bitsHLLIndex),
HLLOperation.add(defHLLPolicy, txnHLLBin, dataListForHLL, bitsHLLIndex)
);
Record afterOps = client.get(client.writePolicyDefault, key);
System.out.println("--The Data in Aerospike–-");
System.out.println(beforeOps);
System.out.println();
System.out.println("--Operation Details–-");
System.out.println("Before, the " + txnStringBin + " was - " + beforeOps.getValue(txnStringBin));
System.out.println(" After, the " + txnStringBin + " is - " + afterOps.getValue(txnStringBin));
System.out.println();
System.out.println("Before, the " + txnIntegerBin + " was - " + beforeOps.getValue(txnIntegerBin));
System.out.println(" After, the " + txnIntegerBin + " is - " + afterOps.getValue(txnIntegerBin));
System.out.println();
System.out.println("Before, the " + txnDoubleBin + " was - " + beforeOps.getValue(txnDoubleBin));
System.out.println(" After, the " + txnDoubleBin + " is - " + afterOps.getValue(txnDoubleBin));
System.out.println();
System.out.println("Before, the " + txnListBin + " was - " + beforeOps.getValue(txnListBin));
System.out.println(" After, the " + txnListBin + " is - " + afterOps.getValue(txnListBin));
System.out.println();
System.out.println("Before, the " + txnMapBin + " was - " + beforeOps.getValue(txnMapBin));
System.out.println(" After, the " + txnMapBin + " is - " + afterOps.getValue(txnMapBin));
System.out.println();
byte[] beforeBytes = (byte[])beforeOps.getValue(txnBlobBin);
byte[] afterBytes = (byte[])afterOps.getValue(txnBlobBin);
System.out.println("Before, the " + txnBlobBin + " was - " + Arrays.toString(beforeBytes));
System.out.println(" After, the " + txnBlobBin + " is - " + Arrays.toString(afterBytes));
System.out.println();
System.out.println("Before, the " + txnHLLBin + " was - " + beforeOps.getValue(txnHLLBin));
System.out.println(" After, the " + txnHLLBin + " is - " + afterOps.getValue(txnHLLBin));
System.out.println();
System.out.println("The " + txnGeoBin + " is - " + afterOps.getValue(txnGeoBin));
System.out.println();
System.out.println("--The Record After the Operations–-");
System.out.println(afterOps);
Output:
--Initial Data–-
String: atomic
Integer: 8
Double: 6.022
Blob: [1, 2, 3, 4, 5]
HLL: Starts with no data.
Geo: { "type": "Polygon", "coordinates": [ [[-122.500, 37.000], [-121.000, 37.000], [-121.000, 38.080], [-122.500, 38.080], [-122.500, 37.000]] ] }
List: [1]
Map: {2=4}
--The Data in Aerospike–-
(gen:13),(exp:358043150),(bins:(str:atomic),(int:8),(double:6.022),(blob:[B@42a7022c),(geo:{ "type": "Polygon", "coordinates": [ [[-122.500, 37.000], [-121.000, 37.000], [-121.000, 38.080], [-122.500, 38.080], [-122.500, 37.000]] ] }),(list:[1]),(map:{2=4}),(hll:0008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000))
--Operation Details–-
Before, the str was - atomic
After, the str is - atomic-transactions
Before, the int was - 8
After, the int is - 13
Before, the double was - 6.022
After, the double is - 2.8800000000000003
Before, the list was - [1]
After, the list is - [1, 5]
Before, the map was - {2=4}
After, the map is - {2=61}
Before, the blob was - [1, 2, 3, 4, 5]
After, the blob is - [1, 7, 3, 4, 5]
Before, the hll was - 0008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
After, the hll is - 00080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000400000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000c0000000000
The geo is - { "type": "Polygon", "coordinates": [ [[-122.500, 37.000], [-121.000, 37.000], [-121.000, 38.080], [-122.500, 38.080], [-122.500, 37.000]] ] }
--The Record After the Operations–-
(gen:14),(exp:358043150),(bins:(str:atomic-transactions),(int:13),(double:2.8800000000000003),(blob:[B@56bc27c5),(geo:{ "type": "Polygon", "coordinates": [ [[-122.500, 37.000], [-121.000, 37.000], [-121.000, 38.080], [-122.500, 38.080], [-122.500, 37.000]] ] }),(list:[1, 5]),(map:{2=61}),(hll:00080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000400000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000c0000000000))
Use Write Policies To Replace Existence Checks
Simple transactions require arbitrary logic. The main technique to add conditional logic to these transactions is to apply a write policy.
Write operations use policy with the policy flags to indicate how to
behave when data does or does not exist. For example, if executing a
simple transaction containing multiple operations including one map
operation that uses the conditional logic
if (bin doesn't have a value), then put a default value into the bin
,
apply that one operation using a write policy using the flags:
- CREATE_ONLY – Only apply the write if the data doesn't exist.
- NO_FAIL – Do not throw an error upon failure.
- PARTIAL – Allow other operations to succeed.
The default write policy if data exists in a bin is to merge data, whenever possible.
Each complex data type has its own write mode policy options.
- For information on Bit Write Flags, go here.
- For information on HyperLogLog Write Flags, go here.
- For information on List Write Flags, go here.
- For information on Map Write Flags, go here.
Now, insert a new Mapkey:Value pair only if the mapkey doesn't already exist.
import com.aerospike.client.cdt.MapOrder;
import com.aerospike.client.cdt.MapWriteFlags;
Integer txnDefaultMapkey=2;
Integer txnDefaultValue=1;
MapPolicy txnMapPolicy = new MapPolicy(MapOrder.UNORDERED, MapWriteFlags.CREATE_ONLY | MapWriteFlags.NO_FAIL | MapWriteFlags.PARTIAL);
Key key = new Key(txnNamespace, txnSet, theKey);
Record mapFailRecord = client.operate(client.writePolicyDefault, key,
MapOperation.put(txnMapPolicy, txnMapBin, Value.get(txnDefaultMapkey), Value.get(txnDefaultValue))
);
Record afterOps = client.get(client.writePolicyDefault, key);
System.out.println("The " + txnMapBin + " is - " + afterOps.getValue(txnMapBin));
System.out.println();
Output:
The map is - {2=61}
Use RMF or Record UDFs to Apply Other Conditional Logic
If a process does requires conditional logic to check data values before writing, the common practice is to use a Read-Modify-Write pattern to check the data and write only if the generation counter is the same as when read.
For more information about Read-Modify-Write, go here.
Notebook Cleanup
Truncate the Set
Truncate the set from the Aerospike Database.
import com.aerospike.client.policy.InfoPolicy;
InfoPolicy infoPolicy = new InfoPolicy();
client.truncate(infoPolicy, txnNamespace, txnSet, null);
System.out.println("Set Truncated.");
Output:
Set Truncated.
Close the Connection to Aerospike
client.close();
System.out.println("Server connection closed.");
Output:
Server connection closed.
Takeaway – Record Transactions are Powerful
Simple transactions are a tool for efficient atomic execution of multiple operations on one record. The ability to process many real time, multi-operation simple transactions at scale is a strength of the Aerospike platform. A little forethought into application reads and writes before coding results in higher performance applications.
What's Next?
Next Steps
Have questions? Don't hesitate to reach out if you have additional questions about executing app transactions at https://discuss.aerospike.com/.
Want to check out other Java notebooks?
Are you running this from Binder? Download the Aerospike Notebook Repo and work with Aerospike Database and Jupyter locally using a Docker container.
Additional Resources
Simple transactions are one of Aerospike's tools to work with data at scale. Other tools include Queries and UDFs, Batches, and Scans.