Skip to content

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 settings
  • Selectors — 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 timedelta
from 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

SelectorTargets
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 reads
Selectors.reads().get() // single-key reads
Selectors.reads().batch() // batch reads
Selectors.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 writes
Selectors.writes().retryable() // idempotent writes (safe to retry)
Selectors.writes().nonRetryable() // non-idempotent writes
Selectors.writes().retryable().point() // single-key retryable writes
Selectors.writes().retryable().batch() // batch retryable writes
Selectors.writes().nonRetryable().point() // single-key non-retryable writes
Selectors.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 safety
Selectors.reads().batch().ap() // exposes readMode()
Selectors.writes().retryable().point().ap() // exposes commitLevel()
// Works but loses type-specific methods in IDE
Selectors.writes().ap().retryable().point() // commitLevel() not visible at compile time
from 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)

SettingDescription
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().*:

SettingDescription
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():

SettingDescription
recordQueueSize(int)Client-side result buffer size

Read-specific

SettingSelectorDescription
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

SettingSelectorDescription
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 timedelta
from 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 timedelta
from 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 workload
writes_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 timedelta
from 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:

StepSourceWhat happens
1DEFAULT resolvesall() -> 1s timeout, 3 retries
2production patches appliedall() overrides -> 5s timeout, 3 retries
3highLoad patches appliedreads().batch() adds -> 16 concurrent nodes
Result5s 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:

StepSourceTimeout
1base: all()5s
2base: reads() overrides2s
3base resolved for batch read2s (parent done)
4child: reads().batch() overrides10s (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 timedelta
from 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.

Feedback

Was this page helpful?

What type of feedback are you giving?

What would you like us to know?

+Capture screenshot

Can we reach out to you?