Behaviors and selectors
For the complete documentation index see: llms.txt
All documentation pages available in markdown.
Behaviors control timeout, retry, and protocol settings per operation type. They replace the old per-policy model with a single, composable configuration.
Core concepts
Behavior— an immutable set of operational settingsSelectors— target which operation types a setting applies to (reads, writes, batches, etc.)Session— created from a cluster with a specific behavior; all operations on that session use those settings
Creating a session with a behavior
Cluster cluster = new ClusterDefinition("localhost", 3000).connect();
Session defaultSession = cluster.createSession(Behavior.DEFAULT);📖 API reference:
ClusterDefinition(String,int)|ClusterDefinition.connect()|Cluster.createSession(Behavior)
from aerospike_sdk import Behavior, ClusterDefinition
cluster = await ClusterDefinition("localhost", 3000).connect()
default_session = cluster.create_session(Behavior.DEFAULT)📖 API reference:
Behavior.DEFAULT
Deriving behaviors
All custom behaviors derive from an existing one — deriveWithChanges in Java, derive_with_changes in Python:
Behavior production = Behavior.DEFAULT.deriveWithChanges("production", builder -> builder .on(Selectors.all(), ops -> ops .abandonCallAfter(Duration.ofSeconds(5)) ));
Session productionSession = cluster.createSession(production);📖 API reference:
Cluster.createSession(Behavior)
from datetime import timedeltafrom aerospike_sdk import Behaviorfrom aerospike_sdk.policy import Settings
production = Behavior.DEFAULT.derive_with_changes( "production", all=Settings(total_timeout=timedelta(seconds=5)),)
production_session = cluster.create_session(production)📖 API reference:
Behavior.DEFAULT
Selectors reference
Selectors narrow which operations a setting applies to. Declare them from general to specific so that more specific selectors override broader ones (see How resolution works).
In Python, each selector is a keyword scope on derive_with_changes() (reads_batch=Settings(...)); scopes are single-axis and cascade the same way.
Entry points
| Selector | Targets |
|---|---|
Selectors.all() | Every operation |
Selectors.reads() | All read operations |
Selectors.writes() | All write operations |
Selectors.transaction() | Transaction verify/roll operations |
Narrowing reads
import java.time.Duration;import com.aerospike.client.sdk.policy.Behavior;import com.aerospike.client.sdk.policy.Behavior.Selectors;
Behavior readsTuned = Behavior.DEFAULT.deriveWithChanges("reads-tuned", b -> b .on(Selectors.reads(), ops -> ops.abandonCallAfter(Duration.ofSeconds(2))) // all reads .on(Selectors.reads().get(), ops -> ops.abandonCallAfter(Duration.ofMillis(500))) // single-key reads .on(Selectors.reads().batch(), ops -> ops.abandonCallAfter(Duration.ofSeconds(5))) // batch reads .on(Selectors.reads().query(), ops -> ops.abandonCallAfter(Duration.ofSeconds(30))) // dataset queries (primary-index and secondary-index queries));📖 API reference:
com.aerospike.client.sdk
from datetime import timedelta
from aerospike_sdk import Behaviorfrom aerospike_sdk.policy import Settings
# Python names each selector as a derive_with_changes() keyword argument# that takes a Settings(...) value:reads_tuned = Behavior.DEFAULT.derive_with_changes( "reads-tuned", reads=Settings(total_timeout=timedelta(seconds=2)), # all reads reads_point=Settings(total_timeout=timedelta(milliseconds=500)), # single-key reads reads_batch=Settings(total_timeout=timedelta(seconds=5)), # batch reads reads_query=Settings(total_timeout=timedelta(seconds=30)), # dataset queries (primary-index and secondary-index queries))📖 API reference:
Behavior
Narrowing writes
import java.time.Duration;import com.aerospike.client.sdk.policy.Behavior;import com.aerospike.client.sdk.policy.Behavior.Selectors;
Behavior writesTuned = Behavior.DEFAULT.deriveWithChanges("writes-tuned", b -> b .on(Selectors.writes(), ops -> ops.abandonCallAfter(Duration.ofSeconds(2))) // all writes .on(Selectors.writes().retryable(), ops -> ops.maximumNumberOfCallAttempts(4)) // idempotent writes (safe to retry) .on(Selectors.writes().nonRetryable(), ops -> ops.maximumNumberOfCallAttempts(1)) // non-idempotent writes .on(Selectors.writes().retryable().point(), ops -> ops.sendKey(true)) // single-key retryable writes .on(Selectors.writes().retryable().batch(), ops -> ops.abandonCallAfter(Duration.ofSeconds(5))) // batch retryable writes .on(Selectors.writes().nonRetryable().point(), ops -> ops.sendKey(true)) // single-key non-retryable writes .on(Selectors.writes().nonRetryable().batch(), ops -> ops.abandonCallAfter(Duration.ofSeconds(5))) // batch non-retryable writes);📖 API reference:
com.aerospike.client.sdk
from datetime import timedelta
from aerospike_sdk import Behaviorfrom aerospike_sdk.policy import Settings
# Python write scopes narrow by one dimension each. They layer in the order# writes -> writes_ap / writes_sc -> retryability -> shape, so a single-key# retryable write picks up writes, writes_retryable, and writes_point.writes_tuned = Behavior.DEFAULT.derive_with_changes( "writes-tuned", writes=Settings(total_timeout=timedelta(seconds=2)), # all writes writes_retryable=Settings(max_retries=3), # idempotent writes (safe to retry) writes_non_retryable=Settings(max_retries=0), # non-idempotent writes writes_point=Settings(send_key=True), # single-key writes writes_batch=Settings(total_timeout=timedelta(seconds=5)), # batch writes)📖 API reference:
Behavior
Mode selection (AP vs CP)
Append .ap() or .cp() for mode-specific settings. Select mode last for best compile-time type safety:
import java.time.Duration;import com.aerospike.client.sdk.policy.Behavior;import com.aerospike.client.sdk.policy.Behavior.Selectors;import com.aerospike.client.sdk.policy.CommitLevel;import com.aerospike.client.sdk.policy.ReadModeAP;
Behavior byMode = Behavior.DEFAULT.deriveWithChanges("by-mode", b -> b // Recommended: mode last -> full type safety .on(Selectors.reads().batch().ap(), ops -> ops.readMode(ReadModeAP.ALL)) // exposes readMode() .on(Selectors.writes().retryable().point().ap(), ops -> ops.commitLevel(CommitLevel.COMMIT_ALL)) // exposes commitLevel()
// Works but loses type-specific methods in IDE .on(Selectors.writes().ap().retryable().point(), ops -> ops.abandonCallAfter(Duration.ofSeconds(2))) // commitLevel() not visible at compile time);📖 API reference:
com.aerospike.client.sdk
from aerospike_native import CommitLevel, ReadModeAP, ReadModeSCfrom aerospike_sdk import Behaviorfrom aerospike_sdk.policy import Settings
# Python selects the namespace mode with its own scopes, which apply on top of# reads / writes and below the shape scopes:by_mode = Behavior.DEFAULT.derive_with_changes( "by-mode", reads_ap=Settings(read_mode_ap=ReadModeAP.ALL), # reads on AP namespaces (read_mode_ap) reads_sc=Settings(read_mode_sc=ReadModeSC.LINEARIZE), # reads on SC namespaces (read_mode_sc) writes_ap=Settings(commit_level=CommitLevel.COMMIT_ALL), # writes on AP namespaces (commit_level) writes_sc=Settings(durable_delete=True), # writes on SC namespaces (durable_delete))📖 API reference:
Behavior.DEFAULT
Available settings
Common (available on all selectors)
| Setting | Description |
|---|---|
abandonCallAfter(Duration) | Total timeout for the entire operation (including retries) |
waitForCallToComplete(Duration) | Socket timeout per individual attempt |
waitForConnectionToComplete(Duration) | Connection establishment timeout |
waitForSocketResponseAfterCallFails(Duration) | How long to wait for a socket response after the call has already timed out |
maximumNumberOfCallAttempts(int) | Max attempts, including the first (Python max_retries counts retries only) |
delayBetweenRetries(Duration) | Fixed delay between retry attempts |
replicaOrder(Replica) | Which replica to read/write first (SEQUENCE, MASTER, etc.) |
sendKey(boolean) | Whether to send the user key to the server |
useCompression(boolean) | Enable network compression |
Batch-specific
Available on .reads().batch(), .writes().*.batch(), and .transaction().*:
| Setting | Description |
|---|---|
maxConcurrentNodes(int) | Max parallel node connections for batch |
allowInlineMemoryAccess(boolean) | Allow batch sub-commands to run inline on memory-only namespaces |
allowInlineSsdAccess(boolean) | Allow batch sub-commands to run inline on SSD-backed namespaces |
Query-specific
Available on .reads().query() and .writes().*.query():
| Setting | Description |
|---|---|
recordQueueSize(int) | Client-side result buffer size |
Read-specific
| Setting | Selector | Description |
|---|---|---|
resetTtlOnReadAtPercent(int) | .reads() | Reset record TTL on read when remaining TTL drops below this percentage |
readMode(ReadModeAP) | .reads().ap() | AP read consistency (ONE, ALL) |
consistency(ReadModeSC) | .reads().cp() | SC read consistency (SESSION, LINEARIZE) |
Write-specific
| Setting | Selector | Description |
|---|---|---|
useDurableDelete(boolean) | .writes() | Use durable delete for tombstones |
simulateXdrWrite(boolean) | .writes() | Simulate XDR (cross-datacenter replication) write |
commitLevel(CommitLevel) | .writes().*.ap() | AP write durability (COMMIT_ALL, COMMIT_MASTER) |
Transaction operations
Behavior custom = Behavior.DEFAULT.deriveWithChanges("custom", builder -> builder .on(Selectors.transaction().txnVerify(), ops -> ops .maximumNumberOfCallAttempts(10) ) .on(Selectors.transaction().txnRoll(), ops -> ops .delayBetweenRetries(Duration.ofSeconds(1)) ));📖 API reference:
com.aerospike.client.sdk
from datetime import timedeltafrom aerospike_sdk import Behaviorfrom aerospike_sdk.policy import Settings
custom = Behavior.DEFAULT.derive_with_changes( "custom", system_txn_verify=Settings(max_retries=9), # 10 attempts system_txn_roll=Settings(retry_delay=timedelta(seconds=1)),)📖 API reference:
Behavior.DEFAULT
How resolution works
Settings resolve through two independent dimensions: selector cascade within a single behavior, and inheritance across derived behaviors.
Dimension 1: Selector cascade
Each .on(selector, ...) call adds a patch. When the SDK needs settings for a specific operation, it applies all matching patches in the order you declared them. Declare general selectors first so more specific ones override them:
all() -> reads() -> reads().batch() -> reads().batch().ap() ^ writes() -> writes().retryable() -> writes().retryable().point()Within a single Java behavior, this is last-writer wins — if two patches match the same operation, the one declared later takes precedence, even if it is broader.
In Python, keyword order does not matter. Scopes always apply from least to most specific: all → reads / writes → mode (reads_ap, writes_sc, …) → retryability (writes only) → shape (reads_batch, writes_point, …). A shape scope therefore overrides a mode scope, so reads_batch wins over reads_ap for an AP batch read.
Behavior tiered = Behavior.DEFAULT.deriveWithChanges("tiered", builder -> builder .on(Selectors.all(), ops -> ops .abandonCallAfter(Duration.ofSeconds(2)) ) .on(Selectors.reads(), ops -> ops .abandonCallAfter(Duration.ofSeconds(1)) ) .on(Selectors.reads().batch(), ops -> ops .abandonCallAfter(Duration.ofSeconds(5)) ));📖 API reference:
com.aerospike.client.sdk
Resolution for a batch read: all() sets 2s, reads() narrows to 1s, reads().batch() narrows to 5s.
Resolution for a single-key read: all() sets 2s, reads() narrows to 1s. The reads().batch() patch doesn’t match.
Resolution for a write: only all() matches -> 2s.
from datetime import timedeltafrom aerospike_sdk import Behaviorfrom aerospike_sdk.policy import Settings
tiered = Behavior.DEFAULT.derive_with_changes( "tiered", all=Settings(total_timeout=timedelta(seconds=2)), reads=Settings(total_timeout=timedelta(seconds=1)), reads_batch=Settings(total_timeout=timedelta(seconds=5)),)📖 API reference:
Behavior.DEFAULT
Dimension 2: Behavior inheritance
A derived behavior starts with the fully resolved settings from its parent, then applies its own patches on top. This forms a tree:
DEFAULT (all: 1s, 3 attempts) / \ v v production reporting (all: 5s, 3 attempts) (all: 30s) | v highLoad (all: 5s, 3 attempts) (batch reads: 16 concurrent nodes)Behavior production = Behavior.DEFAULT.deriveWithChanges("production", builder -> builder .on(Selectors.all(), ops -> ops .abandonCallAfter(Duration.ofSeconds(5)) .maximumNumberOfCallAttempts(3) ));
Behavior highLoad = production.deriveWithChanges("highLoad", builder -> builder .on(Selectors.reads().batch(), ops -> ops .maxConcurrentNodes(16) ));📖 API reference:
com.aerospike.client.sdk
from datetime import timedeltafrom aerospike_sdk import Behaviorfrom aerospike_sdk.policy import Settings
production = Behavior.DEFAULT.derive_with_changes( "production", all=Settings(total_timeout=timedelta(seconds=5), max_retries=2), # 3 attempts)
high_load = production.derive_with_changes( "high_load", reads_batch=Settings(max_concurrent_nodes=16),)📖 API reference:
Behavior.DEFAULT
Resolution for a batch read on highLoad:
| Step | Source | What happens |
|---|---|---|
| 1 | DEFAULT resolves | all() -> 1s timeout, 3 attempts |
| 2 | production patches applied | all() overrides -> 5s timeout, 3 attempts |
| 3 | highLoad patches applied | reads().batch() adds -> 16 concurrent nodes |
| Result | 5s timeout, 3 attempts, 16 concurrent nodes |
A child’s patches can override anything from the parent — parent settings are a starting point, not a floor.
Both dimensions combined
The two dimensions compose naturally. Within each behavior in the chain, selector patches cascade from general to specific. Across behaviors, the child’s resolved matrix starts from the parent’s fully resolved matrix.
Behavior base = Behavior.DEFAULT.deriveWithChanges("base", builder -> builder .on(Selectors.all(), ops -> ops .abandonCallAfter(Duration.ofSeconds(5)) ) .on(Selectors.reads(), ops -> ops .abandonCallAfter(Duration.ofSeconds(2)) ));
Behavior child = base.deriveWithChanges("child", builder -> builder .on(Selectors.reads().batch(), ops -> ops .abandonCallAfter(Duration.ofSeconds(10)) ));📖 API reference:
com.aerospike.client.sdk
from datetime import timedeltafrom aerospike_sdk import Behaviorfrom aerospike_sdk.policy import Settings
base = Behavior.DEFAULT.derive_with_changes( "base", all=Settings(total_timeout=timedelta(seconds=5)), reads=Settings(total_timeout=timedelta(seconds=2)),)
child = base.derive_with_changes( "child", reads_batch=Settings(total_timeout=timedelta(seconds=10)),)📖 API reference:
Behavior.DEFAULT
Resolution for a batch read on child:
| Step | Source | Timeout |
|---|---|---|
| 1 | base: all() | 5s |
| 2 | base: reads() overrides | 2s |
| 3 | base resolved for batch read | 2s (parent done) |
| 4 | child: reads().batch() overrides | 10s (final) |
Resolution for a single-key read on child: inherits 2s from base (no child patch matches) -> 2s.
Resolution for a write on child: inherits 5s from base’s all() -> 5s.
Multiple sessions, one cluster
Behavior reporting = Behavior.DEFAULT.deriveWithChanges("reporting", builder -> builder .on(Selectors.all(), ops -> ops .abandonCallAfter(Duration.ofSeconds(30)) ));
Session defaultSession = cluster.createSession(Behavior.DEFAULT);Session reportingSession = cluster.createSession(reporting);📖 API reference:
Cluster.createSession(Behavior)
from datetime import timedeltafrom aerospike_sdk import Behaviorfrom aerospike_sdk.policy import Settings
reporting = Behavior.DEFAULT.derive_with_changes( "reporting", all=Settings(total_timeout=timedelta(seconds=30)),)
default_session = cluster.create_session(Behavior.DEFAULT)reporting_session = cluster.create_session(reporting)📖 API reference:
Behavior.DEFAULT
Both sessions share the same cluster connection pool but have different operational settings.