Bin operations
This page describes bin operations that can be executed by the operate command. Each bin operation works on a specified bin of the target record.
Overview
Aerospike records are made up of metadata and one or more bins containing a supported data types. Nested Lists and Maps, also called collection data types (or CDTs), may be combined to implement a document as a complex data structure.
operate
Acquires a record lock and executes multiple bin operations atomically and in isolation as a single record command. Operations execute sequentially against an in-memory copy of the record. Operations include bin read and write operations, and the operation API of the data types present in the record.
The read side of operate is one form of projection:
you can return full bins or the results of read-only operations and expressions.
The same bin projection and operation projection model applies to
batched operate and, on Database 8.1.2 and later, to
foreground queries.
One of the arguments to the operate method is an ordered list of bin operations
to execute. The syntax used by Aerospike clients varies by programming language.
Unlike a record get operation, which returns an Aerospike record, operate
returns output from each sub-operation per bin. The list of operations executes
in an “all or nothing” fashion, unless the operation has an explicit NO_FAIL
instruction for the server to skip and continue with the next operation.
Without it, any failure results in the in-memory copy of the record being
discarded and the operate command ends with an error sent back to the client.
The following is the comprehensive list of bin operations that can be part of an operate command.
| Data Type | Bin operation | Description |
|---|---|---|
| All | op_put | Upsert (create or update) a bin. Also called write. |
| All | op_get | Read a bin. Also called read. |
| All | op_touch | Increase the generation counter for a record. |
| All | op_delete | Remove a record from the database. Generates a tombstone when you use the durable delete policy. |
| Integer Float | op_add | Add (or subtract) a value. Used to implement counters. Also called increment. |
| String | op_append op_prepend | Modify a string. |
| List | For a list of all List Operations, see Collection Data Types: Lists. | |
| Map | For a list of all Map Operations, see Collection Data Types: Maps. | |
| Blob/ Bytes | For a list of all Blob/Byte Operations, see Data Types: Blob/Bytes. | |
| HLL | For a list of all HyperLogLog Operations, see Data Types: HyperLogLog. | |
| Geo | For a list of all Geospatial Operations, see Data Types: Geospatial. |
Filtering
An operate command, batched operate commands, and a query can be conditionally applied, based on the result of a
record filter expression.
If the filter expression evaluates to true, the command will proceed.
How operate executes
When a client sends an operate command, the server loads the target record
from storage into an in-memory copy and acquires the record lock. Every
operation in the list then executes sequentially against this copy.
Write operations (put, add, append, CDT remove_by_*, write
expressions, and others) mutate the in-memory copy. Read operations
(get, CDT reads, read expressions) observe the copy’s current state,
including mutations made by earlier operations in the same list.
After all operations complete:
- If the list contained any write operation, the modified copy is persisted back to storage and replicated.
- If the list contained only read operations, the results are returned to the client, the in-memory copy is discarded, and nothing is written to storage.
A mixed list of reads and writes is common for single-record and batched operate commands.
Reads see the effect of preceding writes, and the final mutated state is what gets persisted.
Execution trace
Consider a record that starts in storage as:
{"name": "J. Smith", "visits": 1, "status": "active"}The following five-operation list mixes writes, a bin delete, and reads:
| Step | Operation | In-memory state after | Returned to client |
|---|---|---|---|
| — | (loaded from storage) | {name: "J. Smith", visits: 1, status: "active"} | — |
| 1 | add("visits", 1) | {name: "J. Smith", visits: 2, status: "active"} | — |
| 2 | get("visits") | (unchanged) | visits: 2 |
| 3 | append("name", " Jr.") | {name: "J. Smith Jr.", visits: 2, status: "active"} | — |
| 4 | put("status", NIL) | {name: "J. Smith Jr.", visits: 2} | — |
| 5 | get("name") | (unchanged) | name: "J. Smith Jr." |
Persisted to storage: {name: "J. Smith Jr.", visits: 2} and the status
bin is gone because writing NIL deletes a bin.
Returned to the client: {visits: 2, name: "J. Smith Jr."}, output for the two
read operations.
Key takeaways:
- Step 2 reads
visitsafter step 1 incremented it, so it sees2. - Step 4 deletes the
statusbin by writingNIL. Any later read ofstatuswould see it absent. - Step 5 reads
nameafter step 3 appended to it, so it sees"J. Smith Jr.".
Key key = new Key("test", "demo", "trace-example");
client.put(null, key, new Bin("name", "J. Smith"), new Bin("visits", 1), new Bin("status", "active"));
Record record = client.operate(null, key, Operation.add(new Bin("visits", 1)), Operation.get("visits"), Operation.append(new Bin("name", " Jr.")), Operation.put(Bin.asNull("status")), Operation.get("name"));
System.out.println(record.bins);// {visits=2, name=J. Smith Jr.}from aerospike_helpers.operations import operations
key = ("test", "demo", "trace-example")
client.put(key, {"name": "J. Smith", "visits": 1, "status": "active"})
ops = [ operations.increment("visits", 1), operations.read("visits"), operations.append("name", " Jr."), operations.write("status", None), operations.read("name"),]
(_, _, bins) = client.operate(key, ops)print(bins)# {'visits': 2, 'name': 'J. Smith Jr.'}key, _ := as.NewKey("test", "demo", "trace-example")
client.PutBins(nil, key, as.NewBin("name", "J. Smith"), as.NewBin("visits", 1), as.NewBin("status", "active"))
record, _ := client.Operate(nil, key, as.AddOp(as.NewBin("visits", 1)), as.GetBinOp("visits"), as.AppendOp(as.NewBin("name", " Jr.")), as.PutOp(as.NewBin("status", nil)), as.GetBinOp("name"))
fmt.Println(record.Bins)// map[name:J. Smith Jr. visits:2]as_key key;as_key_init_str(&key, "test", "demo", "trace-example");
as_record rec;as_record_inita(&rec, 3);as_record_set_str(&rec, "name", "J. Smith");as_record_set_int64(&rec, "visits", 1);as_record_set_str(&rec, "status", "active");aerospike_key_put(&as, &err, NULL, &key, &rec);as_record_destroy(&rec);
as_operations ops;as_operations_inita(&ops, 5);as_operations_add_incr(&ops, "visits", 1);as_operations_add_read(&ops, "visits");as_operations_add_append_str(&ops, "name", " Jr.");as_operations_add_write_str(&ops, "status", NULL);as_operations_add_read(&ops, "name");
as_record* p_rec = NULL;aerospike_key_operate(&as, &err, NULL, &key, &ops, &p_rec);
printf("visits: %lld\n", (long long)as_record_get_int64(p_rec, "visits", 0));printf("name: %s\n", as_record_get_str(p_rec, "name"));// visits: 2// name: J. Smith Jr.
as_operations_destroy(&ops);as_record_destroy(p_rec);Key key = new Key("test", "demo", "trace-example");
client.Put(null, key, new Bin("name", "J. Smith"), new Bin("visits", 1), new Bin("status", "active"));
Record record = client.Operate(null, key, Operation.Add(new Bin("visits", 1)), Operation.Get("visits"), Operation.Append(new Bin("name", " Jr.")), Operation.Put(Bin.AsNull("status")), Operation.Get("name"));
Console.WriteLine($"visits: {record.GetValue("visits")}");Console.WriteLine($"name: {record.GetValue("name")}");// visits: 2// name: J. Smith Jr.const key = new Aerospike.Key('test', 'demo', 'trace-example')
await client.put(key, { name: 'J. Smith', visits: 1, status: 'active' })
const ops = [ op.incr('visits', 1), op.read('visits'), op.append('name', ' Jr.'), op.write('status', null), op.read('name'),]
const record = await client.operate(key, ops)console.log(record.bins)// { visits: 2, name: 'J. Smith Jr.' }Code example of the operate command
The following example combines read and update operations on the same record into one command.
import com.aerospike.client.AerospikeClient;import com.aerospike.client.Bin;import com.aerospike.client.Key;import com.aerospike.client.Record;import com.aerospike.client.Operation;
AerospikeClient client = new AerospikeClient("127.0.0.1", 3000);
Key key = new Key("test", "demo", "op-example");
// Create a record with two binsclient.put(null, key, new Bin("name", "J. Smith"), new Bin("visits", 1));
// Combine multiple operations in one atomic commandRecord record = client.operate(null, key, Operation.add(new Bin("visits", 1)), Operation.append(new Bin("name", " Jr.")), Operation.get("visits"), Operation.get("name"));
System.out.format("visits: %s%n", record.bins.get("visits"));System.out.format("name: %s%n", record.bins.get("name"));// visits: 2// name: J. Smith Jr.
client.close();import aerospikefrom aerospike_helpers.operations import operations
config = {"hosts": [("127.0.0.1", 3000)]}client = aerospike.client(config).connect()
key = ("test", "demo", "op-example")
# Create a record with two binsclient.put(key, {"name": "J. Smith", "visits": 1})
# Combine multiple operations in one atomic commandops = [ operations.increment("visits", 1), operations.append("name", " Jr."), operations.read("visits"), operations.read("name"),]
(key_, meta, bins) = client.operate(key, ops)
print(f"visits: {bins['visits']}")print(f"name: {bins['name']}")# visits: 2# name: J. Smith Jr.
client.close()import ( "fmt" "log"
as "github.com/aerospike/aerospike-client-go/v6")
client, err := as.NewClient("127.0.0.1", 3000)if err != nil { log.Fatal(err)}defer client.Close()
key, _ := as.NewKey("test", "demo", "op-example")
// Create a record with two binserr = client.PutBins(nil, key, as.NewBin("name", "J. Smith"), as.NewBin("visits", 1))if err != nil { log.Fatal(err)}
// Combine multiple operations in one atomic commandrecord, err := client.Operate(nil, key, as.AddOp(as.NewBin("visits", 1)), as.AppendOp(as.NewBin("name", " Jr.")), as.GetBinOp("visits"), as.GetBinOp("name"))if err != nil { log.Fatal(err)}
fmt.Printf("visits: %v\n", record.Bins["visits"])fmt.Printf("name: %v\n", record.Bins["name"])// visits: 2// name: J. Smith Jr.#include <aerospike/aerospike.h>#include <aerospike/aerospike_key.h>#include <aerospike/as_operations.h>#include <aerospike/as_record.h>
as_config config;as_config_init(&config);as_config_add_host(&config, "127.0.0.1", 3000);
aerospike as;aerospike_init(&as, &config);
as_error err;aerospike_connect(&as, &err);
as_key key;as_key_init_str(&key, "test", "demo", "op-example");
// Create a record with two binsas_record rec;as_record_inita(&rec, 2);as_record_set_str(&rec, "name", "J. Smith");as_record_set_int64(&rec, "visits", 1);aerospike_key_put(&as, &err, NULL, &key, &rec);as_record_destroy(&rec);
// Combine multiple operations in one atomic commandas_operations ops;as_operations_inita(&ops, 4);as_operations_add_incr(&ops, "visits", 1);as_operations_add_append_str(&ops, "name", " Jr.");as_operations_add_read(&ops, "visits");as_operations_add_read(&ops, "name");
as_record *p_rec = NULL;aerospike_key_operate(&as, &err, NULL, &key, &ops, &p_rec);
printf("visits: %lld\n", (long long)as_record_get_int64(p_rec, "visits", 0));printf("name: %s\n", as_record_get_str(p_rec, "name"));// visits: 2// name: J. Smith Jr.
as_operations_destroy(&ops);as_record_destroy(p_rec);aerospike_close(&as, &err);aerospike_destroy(&as);using Aerospike.Client;
AerospikeClient client = new AerospikeClient("127.0.0.1", 3000);
Key key = new Key("test", "demo", "op-example");
// Create a record with two binsclient.Put(null, key, new Bin("name", "J. Smith"), new Bin("visits", 1));
// Combine multiple operations in one atomic commandRecord record = client.Operate(null, key, Operation.Add(new Bin("visits", 1)), Operation.Append(new Bin("name", " Jr.")), Operation.Get("visits"), Operation.Get("name"));
Console.WriteLine($"visits: {record.GetValue("visits")}");Console.WriteLine($"name: {record.GetValue("name")}");// visits: 2// name: J. Smith Jr.
client.Close();import Aerospike from "aerospike";const op = Aerospike.operations;
const client = await Aerospike.connect({ hosts: "127.0.0.1:3000" });
const key = new Aerospike.Key("test", "demo", "op-example");
// Create a record with two binsawait client.put(key, { name: "J. Smith", visits: 1 });
// Combine multiple operations in one atomic commandconst ops = [ op.incr("visits", 1), op.append("name", " Jr."), op.read("visits"), op.read("name"),];
const record = await client.operate(key, ops);
console.log(`visits: ${record.bins.visits}`);console.log(`name: ${record.bins.name}`);// visits: 2// name: J. Smith Jr.
await client.close();Usage notes
All read and write operations on records accept policy parameters: Max Retries and Total Timeout.
Write commands take an optional time to live (TTL) parameter which specifies how long the record can live before Aerospike automatically expires it and removes it from the primary index. For details on the interaction between TTL and eviction, see Managing storage capacity.
A transaction’s constituent operations may accept or require additional policy parameters. For additional details about those parameters, refer to the API reference for the client and operation you are using.
Server responses
By default, only read operations return results. In the example above, the four operations produce two results — one per read — grouped by bin name:
{visits: 2, name: "J. Smith Jr."}The add and append writes execute but do not appear in the output.
To include every operation in the result, enable the respond-all-ops setting on
the write policy. With respond-all-ops enabled, each bin returns a list with one
entry per operation — null for writes that have no return value:
{visits: [null, 2], name: [null, "J. Smith Jr."]}ClientPolicy clientPolicy = new ClientPolicy();clientPolicy.writePolicyDefault.respondAllOps = true;wp := aerospike.NewWritePolicy(0, 0)wp.RespondPerEachOp = trueas_policy_operate policy;as_policy_operate_init(&policy);policy.respond_all_ops = true;WritePolicy writePolicy = new WritePolicy();writePolicy.respondAllOps = true;References
See these topics for language-specific examples: