Bin operations
For the complete documentation index see: llms.txt
All documentation pages available in markdown.
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.
An operate list can also carry
operation expressions, which compute a value
from the record rather than acting on one named bin. A read expression returns that
value under a name that exists only in the response, leaving the record unchanged; a
write expression persists it to a bin. For how the two surfaces relate and when to
reach for each, see
Operations and expressions.
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. |
| 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/Bytes operations, see Data Types: Blob/Bytes. | |
| HLL | For a list of all HyperLogLog Operations, see Data Types: HyperLogLog. | |
| String | For a list of all Unicode-aware string read, modify, and conversion operations, see Data Types: String. | |
| 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, 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 four-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 | put("status", NIL) | {name: "J. Smith", visits: 2} | — |
| 4 | get("name") | (unchanged) | name: "J. Smith" |
Persisted to storage: {name: "J. Smith", visits: 2} and the status
bin is gone because writing NIL deletes a bin.
Returned to the client: {visits: 2, name: "J. Smith"}, output for the two
read operations.
Key takeaways:
- Step 2 reads
visitsafter step 1 incremented it, so it sees2. - Step 3 deletes the
statusbin by writingNIL. Any later read ofstatuswould see it absent. - Step 4 reads
name, which no operation in the list touched, so it sees the value loaded from storage.
Session session = cluster.createSession(Behavior.DEFAULT);DataSet demo = DataSet.of("test", "demo");
session.upsert(demo.id("trace-example")) .bin("name").setTo("J. Smith") .bin("visits").setTo(1) .bin("status").setTo("active") .execute();
// The SDK returns one result per operation, read by position.try (RecordStream rs = session.upsert(demo.id("trace-example")) .bin("visits").add(1) // op 0 — a write, no result .bin("visits").get() // op 1 .bin("status").remove() // op 2 — a write, no result .bin("name").get() // op 3 .execute()) { Record record = rs.getFirstRecord(); System.out.println(record.operationResult(1).getLong()); // 2 System.out.println(record.operationResult(3).getString()); // J. Smith}session = cluster.create_session(Behavior.DEFAULT)demo = DataSet.of("test", "demo")
session.upsert(demo.id("trace-example")) \ .bin("name").set_to("J. Smith") \ .bin("visits").set_to(1) \ .bin("status").set_to("active") \ .execute()
# The SDK returns one result per operation, read by position.with session.upsert(demo.id("trace-example")) \ .bin("visits").add(1) \ .bin("visits").get() \ .bin("status").remove() \ .bin("name").get() \ .execute() as rs: record = rs.first() print(record.operation_result(1)) # 2 print(record.operation_result(3)) # J. Smith// Requires: use aerospike::operations::scalar;let key = Key::new("test", "demo", Value::from("trace-example"))?;
client.put(&WritePolicy::default(), &key, &[ as_bin!("name", "J. Smith"), as_bin!("visits", 1), as_bin!("status", "active"),]).await?;
let record = client.operate(&WritePolicy::default(), &key, &[ scalar::add(&as_bin!("visits", 1)), scalar::get_bin("visits"), scalar::put(&as_bin!("status", None)), // a nil value deletes the bin scalar::get_bin("name"),]).await?;
println!("{}", record.bins.get("visits").unwrap()); // 2println!("{}", record.bins.get("name").unwrap()); // J. SmithKey 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.Put(Bin.AsNull("status")), Operation.Get("name"));
Console.WriteLine($"visits: {record.GetValue("visits")}");Console.WriteLine($"name: {record.GetValue("name")}");// visits: 2// name: J. Smith// Requires: import as "github.com/aerospike/aerospike-client-go/v8"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.PutOp(as.NewBin("status", nil)), as.GetBinOp("name"))
fmt.Println(record.Bins)// map[name:J. Smith visits:2]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.write('status', null), op.read('name'),]
const record = await client.operate(key, ops)console.log(record.bins)// { visits: 2, name: 'J. Smith' }#include <aerospike/as_nil.h> // for as_nil, used to delete a bin
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, 4);as_operations_add_incr(&ops, "visits", 1);as_operations_add_read(&ops, "visits");as_operations_add_write(&ops, "status", (as_bin_value*)&as_nil);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
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.put(Bin.asNull("status")), Operation.get("name"));
System.out.println(record.bins);// {visits=2, name=J. Smith}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.write("status", None), operations.read("name"),]
(_, _, bins) = client.operate(key, ops)print(bins)# {'visits': 2, 'name': 'J. Smith'}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.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.RecordStream;import com.aerospike.client.sdk.Session;import com.aerospike.client.sdk.policy.Behavior;
try (Cluster cluster = new ClusterDefinition("127.0.0.1", 3000).connect()) { Session session = cluster.createSession(Behavior.DEFAULT); DataSet demo = DataSet.of("test", "demo");
// Create a record with two bins session.upsert(demo.id("op-example")) .bin("name").setTo("J. Smith") .bin("visits").setTo(1) .execute();
// Combine multiple operations in one atomic command try (RecordStream rs = session.upsert(demo.id("op-example")) .bin("visits").add(1) // op 0 — a write, no result .bin("visits").get() // op 1 .bin("name").get() // op 2 .execute()) { Record record = rs.getFirstRecord(); System.out.format("visits: %s%n", record.operationResult(1).getLong()); System.out.format("name: %s%n", record.operationResult(2).getString()); // visits: 2 // name: J. Smith }}from aerospike_sdk import Behavior, DataSetfrom aerospike_sdk.sync import ClusterDefinition
with ClusterDefinition("127.0.0.1", 3000).connect() as cluster: session = cluster.create_session(Behavior.DEFAULT) demo = DataSet.of("test", "demo")
# Create a record with two bins session.upsert(demo.id("op-example")) \ .bin("name").set_to("J. Smith") \ .bin("visits").set_to(1) \ .execute()
# Combine multiple operations in one atomic command with session.upsert(demo.id("op-example")) \ .bin("visits").add(1) \ .bin("visits").get() \ .bin("name").get() \ .execute() as rs: record = rs.first() print(f"visits: {record.operation_result(1)}") print(f"name: {record.operation_result(2)}") # visits: 2 # name: J. Smithuse aerospike::operations::scalar;use aerospike::{as_bin, Client, ClientPolicy, Key, Value, WritePolicy};
let client = Client::new(&ClientPolicy::default(), &"127.0.0.1:3000".to_string()).await?;let key = Key::new("test", "demo", Value::from("op-example"))?;
// Create a record with two binsclient.put(&WritePolicy::default(), &key, &[ as_bin!("name", "J. Smith"), as_bin!("visits", 1),]).await?;
// Combine multiple operations in one atomic commandlet record = client.operate(&WritePolicy::default(), &key, &[ scalar::add(&as_bin!("visits", 1)), scalar::get_bin("visits"), scalar::get_bin("name"),]).await?;
println!("visits: {}", record.bins.get("visits").unwrap());println!("name: {}", record.bins.get("name").unwrap());// visits: 2// name: J. Smithusing 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.Get("visits"), Operation.Get("name"));
Console.WriteLine($"visits: {record.GetValue("visits")}");Console.WriteLine($"name: {record.GetValue("name")}");// visits: 2// name: J. Smith
client.Close();import ( "fmt" "log"
as "github.com/aerospike/aerospike-client-go/v8")
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.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. Smithimport 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.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
await client.close();#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, 3);as_operations_add_incr(&ops, "visits", 1);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
as_operations_destroy(&ops);as_record_destroy(p_rec);aerospike_close(&as, &err);aerospike_destroy(&as);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.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
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.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
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 three operations produce two results — one per read — grouped by bin name:
{visits: 2, name: "J. Smith"}The add write executes but does not appear in the output.
To include every operation in the result, enable the respond-all-ops setting on
the write policy. A bin that more than one operation touched then returns a list
with one entry per operation — null for a write that has no return value —
while a bin touched by a single operation returns that operation’s value directly:
{visits: [null, 2], name: "J. Smith"}WritePolicy writePolicy = new WritePolicy();writePolicy.respondAllOps = true;// Requires: import as "github.com/aerospike/aerospike-client-go/v8"wp := as.NewWritePolicy(0, 0)wp.RespondPerEachOp = trueas_policy_operate policy;as_policy_operate_init(&policy);policy.respond_all_ops = true;import com.aerospike.client.policy.ClientPolicy;
ClientPolicy clientPolicy = new ClientPolicy();clientPolicy.writePolicyDefault.respondAllOps = true;Legacy append and prepend
op_append and op_prepend are the original single-value modify operations. They work on
both string and blob bins, take no policy or context argument, and remain supported.
For strings they are superseded by
append and prepend, which are
Unicode-aware and accept a
string policy and a
context path to a string nested inside a List or
Map bin.
References
See these topics for language-specific examples: