Behaviors and Selectors
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);from aerospike_sdk import Behavior, ClusterDefinition
cluster = await ClusterDefinition("localhost", 3000).connect()
default_session = cluster.create_session(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);from datetime import timedeltafrom aerospike_sdk import Behavior
production = Behavior.DEFAULT.derive_with_changes( "production", total_timeout=timedelta(seconds=5),)
production_session = cluster.create_session(production)Selectors reference
Selectors narrow which operations a setting applies to. Settings cascade from general to specific — more specific selectors override broader ones.
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
Selectors.reads() // all readsSelectors.reads().get() // single-key readsSelectors.reads().batch() // batch readsSelectors.reads().query() // dataset queries (scans, SI queries)# Python has no Selectors.reads() / .get() / .batch() / .query() API. Configure reads# via flat kwargs on Behavior (e.g. total_timeout, replica, consistency_level,# max_concurrent_nodes, record_queue_size) for the whole session; you cannot target only# one read shape (single-key vs batch vs query) inside a single behavior.Narrowing writes
Selectors.writes() // all writesSelectors.writes().retryable() // idempotent writes (safe to retry)Selectors.writes().nonRetryable() // non-idempotent writesSelectors.writes().retryable().point() // single-key retryable writesSelectors.writes().retryable().batch() // batch retryable writesSelectors.writes().nonRetryable().point() // single-key non-retryable writesSelectors.writes().nonRetryable().batch() // batch non-retryable writes# No Selectors.writes() narrowing in Python (retryable vs point vs batch). Use one# behavior’s max_retries / retry_delay / commit_level for all writes on that session,# or split workloads across multiple behaviors if you need different write tuning.Mode selection (AP vs CP)
Append .ap() or .cp() for mode-specific settings. Select mode last for best compile-time type safety:
// Recommended: mode last -> full type safetySelectors.reads().batch().ap() // exposes readMode()Selectors.writes().retryable().point().ap() // exposes commitLevel()
// Works but loses type-specific methods in IDESelectors.writes().ap().retryable().point() // commitLevel() not visible at compile timefrom aerospike_sdk import Behavior
# Python has no .ap() / .cp() selector chain. Use predefined bases (e.g.# Behavior.DEFAULT, Behavior.READ_FAST, Behavior.FAST_RACK_AWARE) for AP-style setups,# and Behavior.STRICTLY_CONSISTENT for SC. Further tune with derive_with_changes(...)# and kwargs such as replica, consistency_level, and commit_level.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 retry attempts |
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)) ));from datetime import timedeltafrom aerospike_sdk import Behavior
# No per-operation txn_verify / txn_roll selectors in Python; retry settings apply to# the whole behavior. Split sessions only if you need different globals for verify vs roll.custom = Behavior.DEFAULT.derive_with_changes( "custom", max_retries=10, retry_delay=timedelta(seconds=1),)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 order. More specific selectors override broader ones:
all() -> reads() -> reads().batch() -> reads().batch().ap() ^ writes() -> writes().retryable() -> writes().retryable().point()Within a single behavior, this is last-writer wins — if two patches match the same operation, the more specific one takes precedence.
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)) ));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 Behavior
# Python has no per-selector cascade. Model different operation timeouts# with separate behaviors, one per workload.tiered_writes = Behavior.DEFAULT.derive_with_changes( "tiered_writes", total_timeout=timedelta(seconds=2))tiered_reads = Behavior.DEFAULT.derive_with_changes( "tiered_reads", total_timeout=timedelta(seconds=1))tiered_batch_reads = Behavior.DEFAULT.derive_with_changes( "tiered_batch_reads", total_timeout=timedelta(seconds=5))
# Create a session per workloadwrites_session = cluster.create_session(tiered_writes)reads_session = cluster.create_session(tiered_reads)batch_reads_session = cluster.create_session(tiered_batch_reads)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 retries) / \ v v production reporting (all: 5s, 3 retries) (all: 30s) | v highLoad (all: 5s, 3 retries) (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) ));from datetime import timedeltafrom aerospike_sdk import Behavior
production = Behavior.DEFAULT.derive_with_changes( "production", total_timeout=timedelta(seconds=5), max_retries=3,)
high_load = production.derive_with_changes( "high_load", max_concurrent_nodes=16,)Resolution for a batch read on highLoad:
| Step | Source | What happens |
|---|---|---|
| 1 | DEFAULT resolves | all() -> 1s timeout, 3 retries |
| 2 | production patches applied | all() overrides -> 5s timeout, 3 retries |
| 3 | highLoad patches applied | reads().batch() adds -> 16 concurrent nodes |
| Result | 5s timeout, 3 retries, 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)) ));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);from datetime import timedeltafrom aerospike_sdk import Behavior
reporting = Behavior.DEFAULT.derive_with_changes( "reporting", total_timeout=timedelta(seconds=30),)
default_session = cluster.create_session(Behavior.DEFAULT)reporting_session = cluster.create_session(reporting)Both sessions share the same cluster connection pool but have different operational settings.