Skip to content

Behaviors

Learn how Behaviors decouple operational configuration from business logic, letting you tune performance and reliability without changing your code.

What are behaviors?

A Behavior is a reusable configuration object that defines how operations execute:

  • Timeouts — How long to wait for a response
  • Retries — How many times to retry on transient failures
  • Consistency — Read from master or allow replicas
  • Durability — Wait for commits to complete

Instead of scattering timeout and retry settings throughout your code, you define Behaviors once and apply them where needed.

import com.aerospike.client.sdk.RecordStream;
// Create a session with a pre-built behavior
Session session = cluster.createSession(Behavior.DEFAULT);
// All operations use DEFAULT settings by default
RecordStream stream = session.query(users.id("user-1")).execute();
stream.getFirst().ifPresent(r -> { /* ... */ });
session.insert(users)
.bins("name")
.id("user-2").values("Bob")
.execute();

Behavior profiles

Use these common behavior profiles as starting points:

BehaviorBest ForCharacteristics
DEFAULTGeneral purposeBuilt-in baseline behavior
READ_FAST (derived)Read-heavy workloadsLower latency, allows AP replica reads
STRICT_READS (Java derived) / STRICTLY_CONSISTENT (Python built-in)SC workloadsLinearizable reads for SC namespaces

DEFAULT

The default choice for most applications. Provides reasonable timeouts and retry behavior for mixed workloads.

Session session = cluster.createSession(Behavior.DEFAULT);
// Typical settings:
// - Socket timeout: 30ms
// - Total timeout: 1000ms
// - Max retries: 2
// - Read mode: Master only

Use DEFAULT when:

  • You’re not sure which behavior to choose
  • Your workload is a mix of reads and writes
  • You want sensible defaults without fine-tuning

READ_FAST

Optimized for read-heavy applications where speed matters more than perfect consistency.

import java.time.Duration;
import com.aerospike.client.sdk.policy.ReadModeAP;
import com.aerospike.client.sdk.policy.Behavior.Selectors;
Behavior readFast = Behavior.DEFAULT.deriveWithChanges("READ_FAST", builder -> builder
.on(Selectors.reads().get().ap(), ops -> ops
.abandonCallAfter(Duration.ofMillis(100))
.maximumNumberOfCallAttempts(2)
.readMode(ReadModeAP.ALL)));
Session session = cluster.createSession(readFast);
// Typical settings:
// - Socket timeout: 10ms
// - Total timeout: 100ms
// - Max retries: 1
// - Read mode: Allow replicas (eventual consistency)

Use READ_FAST when:

  • Reading cached or frequently-accessed data
  • Milliseconds matter (real-time bidding, gaming)
  • You can tolerate reading slightly stale data
  • The cluster has high replication (RF ≥ 2)

CRITICAL_WRITES (custom)

Prioritizes data safety over speed. Use for financial transactions, audit logs, and other critical writes.

import java.time.Duration;
import com.aerospike.client.sdk.policy.CommitLevel;
import com.aerospike.client.sdk.policy.Behavior.Selectors;
Behavior criticalWrites = Behavior.DEFAULT.deriveWithChanges("CRITICAL_WRITES", builder -> builder
.on(Selectors.writes().retryable().point().ap(), ops -> ops
.abandonCallAfter(Duration.ofSeconds(5))
.maximumNumberOfCallAttempts(5)
.delayBetweenRetries(Duration.ofMillis(50))
.commitLevel(CommitLevel.COMMIT_ALL))
.on(Selectors.writes().nonRetryable().point().ap(), ops -> ops
.abandonCallAfter(Duration.ofSeconds(5))
.maximumNumberOfCallAttempts(1)
.commitLevel(CommitLevel.COMMIT_ALL))
);
Session session = cluster.createSession(criticalWrites);
// Typical settings:
// - Socket timeout: 100ms
// - Total timeout: 5000ms
// - Max retries: 3
// - Commit level: All replicas

Use a critical-write custom behavior when:

  • Writing financial transactions
  • Storing audit logs or compliance data
  • You need confirmation that data is safely persisted
  • Losing a write would cause business impact

Custom behaviors

Create custom Behaviors when the pre-built options don’t fit your needs:

import java.time.Duration;
import com.aerospike.client.sdk.policy.ReadModeAP;
import com.aerospike.client.sdk.policy.Behavior.Selectors;
// Build a custom read behavior with explicit Java APIs.
Behavior fastReads = Behavior.DEFAULT.deriveWithChanges("READ_FAST_CUSTOM", builder -> builder
.on(Selectors.reads().get().ap(), ops -> ops
.abandonCallAfter(Duration.ofMillis(100))
.maximumNumberOfCallAttempts(2)
.delayBetweenRetries(Duration.ofMillis(5))
.readMode(ReadModeAP.ALL))
.on(Selectors.reads().batch().ap(), ops -> ops
.abandonCallAfter(Duration.ofMillis(200))
.maximumNumberOfCallAttempts(3))
);
// Use it
Session session = cluster.createSession(fastReads);

Available configuration options (Java)

Setting goalJava behavior method
End-to-end call timeoutabandonCallAfter(Duration)
Retry countmaximumNumberOfCallAttempts(int)
Delay between retriesdelayBetweenRetries(Duration)
AP read modereadMode(ReadModeAP)
SC read modeconsistency(ReadModeSC)
AP write durabilitycommitLevel(CommitLevel)

Available configuration options (Python)

Use Settings(...) values with Behavior.derive_with_changes(...) scope kwargs:

Python does not expose a Selectors class. Use scope keyword arguments such as reads_ap=..., reads_batch=..., writes=..., and all=... on derive_with_changes(...).

Setting goalPython behavior fields
End-to-end call timeoutSettings(total_timeout=timedelta(...))
Retry countSettings(max_retries=int)
Delay between retriesSettings(retry_delay=timedelta(...))
AP read scopereads_ap=Settings(...)
SC read scopereads_sc=Settings(...)
Batch read scopereads_batch=Settings(...)
All operations scopeall=Settings(...)

Python recipes for common behavior tasks

Use these patterns directly when deriving behaviors in Python:

from datetime import timedelta
from aerospike_sdk import Behavior
from aerospike_sdk.policy import Settings
# TC-1: 100ms total timeout for AP reads.
tc1_behavior = Behavior.DEFAULT.derive_with_changes(
"TC1_AP_READ_100MS",
reads_ap=Settings(total_timeout=timedelta(milliseconds=100)),
)
# TC-2: 3-second retry delay for all operations.
tc2_behavior = Behavior.DEFAULT.derive_with_changes(
"TC2_ALL_DELAY_3S",
all=Settings(retry_delay=timedelta(seconds=3)),
)
# TC-3: up to 7 attempts for batch reads.
tc3_behavior = Behavior.DEFAULT.derive_with_changes(
"TC3_BATCH_READ_RETRIES",
reads_batch=Settings(max_retries=7),
)

Behavior derivation does not require running a record read/write. If you add a read to demonstrate usage, ensure your key exists before calling first_or_raise().

For Python batch reads, use query(data_set.ids(...)). The async session.batch() builder is for batch writes/deletes/operate chains and does not expose a get(...) read method.

Timeout deep dive

Understanding the two timeout types is crucial:

┌─────────────────────────────────────────────────────────┐
│ totalTimeout │
├─────────────┬─────────────┬─────────────┬──────────────┤
│ Attempt 1 │ Retry 1 │ Retry 2 │ Retry 3 │
│ (socket TO) │ (socket TO) │ (socket TO) │ (socket TO) │
└─────────────┴─────────────┴─────────────┴──────────────┘
  • abandonCallAfter(…): Max time for an operation call attempt before it is abandoned
  • maximumNumberOfCallAttempts(…): Max attempts (initial attempt + retries)
import java.time.Duration;
import com.aerospike.client.sdk.policy.Behavior.Selectors;
// Example: Allow up to 5 attempts, with a 2-second call cap.
Behavior patient = Behavior.DEFAULT.deriveWithChanges("PATIENT", builder -> builder
.on(Selectors.reads().get().ap(), ops -> ops
.abandonCallAfter(Duration.ofSeconds(2))
.maximumNumberOfCallAttempts(5)
.delayBetweenRetries(Duration.ofMillis(25))));

Read consistency options

For clusters in AP mode (availability-prioritized):

ReadModeAPBehavior
ONERead from master only (strong consistency)
ALLRead from any replica (eventual consistency, lower latency)

For clusters in SC mode (strong consistency):

ReadModeSCBehavior
SESSIONSession consistency (your writes are visible to your reads)
LINEARIZELinearizable reads (strongest guarantee, highest latency)
import com.aerospike.client.sdk.policy.ReadModeAP;
import com.aerospike.client.sdk.policy.ReadModeSC;
import com.aerospike.client.sdk.policy.Behavior.Selectors;
// For AP mode cluster: allow replica reads.
Behavior eventualReads = Behavior.DEFAULT.deriveWithChanges("EVENTUAL_READS", builder -> builder
.on(Selectors.reads().get().ap(), ops -> ops.readMode(ReadModeAP.ALL)));
// For SC mode cluster: linearizable reads.
Behavior strictReads = Behavior.DEFAULT.deriveWithChanges("STRICT_READS", builder -> builder
.on(Selectors.reads().get().cp(), ops -> ops.consistency(ReadModeSC.LINEARIZE)));

Session-level behavior selection

In Java, behavior is selected at session creation time. Use multiple sessions when a subset of operations needs a different behavior:

Session-level (default for all operations)

Set a Behavior when creating the session. All operations inherit it:

import com.aerospike.client.sdk.RecordStream;
// All operations on this session use critical-write settings
Behavior criticalWrites = Behavior.DEFAULT.deriveWithChanges("CRITICAL_WRITES", b -> {});
Session session = cluster.createSession(criticalWrites);
session.insert(users)
.bins("name")
.id("user-1").values("Alice")
.execute(); // Durable
RecordStream read = session.query(users.id("user-1")).execute(); // Durable
read.getFirst().ifPresent(r -> { /* ... */ });
read.close();
session.delete(users.id("user-1")).execute().close(); // Durable

Per-operation behavior choice (dedicated sessions)

Use a dedicated session for operations that need different retry/consistency settings:

// Session uses DEFAULT by default.
Session defaultSession = cluster.createSession(Behavior.DEFAULT);
// Most operations use DEFAULT
RecordStream q1 = defaultSession.query(users.id("user-1")).execute();
q1.getFirst().ifPresent(r -> { /* ... */ });
q1.close();
// This specific write uses a dedicated critical-write session.
Behavior criticalWrites = Behavior.DEFAULT.deriveWithChanges("CRITICAL_WRITES", builder -> builder
.on(Selectors.writes().retryable().point().ap(), ops -> ops.maximumNumberOfCallAttempts(5)));
Session criticalSession = cluster.createSession(criticalWrites);
criticalSession.insert(orders)
.bins("total")
.id("order-1").values(999.99)
.execute();
// Back to DEFAULT session.
RecordStream q2 = defaultSession.query(users.id("user-2")).execute();
q2.getFirst().ifPresent(r -> { /* ... */ });
q2.close();

Retry strategies

Automatic retries

The Developer SDK automatically retries transient failures based on your Behavior configuration:

import com.aerospike.client.sdk.RecordStream;
import com.aerospike.client.sdk.Session;
// DEFAULT behavior: automatic retries per policy
Session session = cluster.createSession(Behavior.DEFAULT);
// This operation will automatically retry on transient failures (per policy)
RecordStream stream = session.query(users.id("user-1")).execute();
stream.getFirst().ifPresent(r -> { /* ... */ });

Manual retry with backoff

For application-level retries with exponential backoff:

import java.time.Duration;
import com.aerospike.client.sdk.Record;
import com.aerospike.client.sdk.RecordResult;
import com.aerospike.client.sdk.RecordStream;
public <T> T retryWithBackoff(Supplier<T> operation, int maxRetries) {
int attempt = 0;
long delayMs = 100;
while (true) {
try {
return operation.get();
} catch (AerospikeException.Timeout | AerospikeException.Connection e) {
attempt++;
if (attempt >= maxRetries) {
throw e; // Exhausted retries
}
System.out.println("Retry " + attempt + " after " + delay.toMillis() + "ms");
try {
Thread.sleep(delayMs);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new RuntimeException(ie);
}
// Exponential backoff with jitter
delayMs = delayMs * 2 + ((long)(Math.random() * 100));
}
}
}
// Usage
Record user = retryWithBackoff(
() -> {
try (RecordStream stream = session.query(users.id("user-1")).execute()) {
return stream.getFirstRecord();
}
},
5 // Max 5 retries
);

For more information on Aerospike exceptions, see Error handling.

Multiple sessions pattern

A common pattern is to create multiple sessions with different behaviors:

import com.aerospike.client.sdk.DataSet;
import com.aerospike.client.sdk.Record;
import com.aerospike.client.sdk.RecordResult;
import com.aerospike.client.sdk.RecordStream;
public class DatabaseService {
private final DataSet users = DataSet.of("app", "users");
private final DataSet payments = DataSet.of("app", "payments");
private final Session readSession;
private final Session writeSession;
private final Session criticalSession;
public DatabaseService(Cluster cluster) {
Behavior readFast = Behavior.DEFAULT.deriveWithChanges("READ_FAST", builder -> builder
.on(Selectors.reads().get().ap(), ops -> ops.maximumNumberOfCallAttempts(2)));
Behavior criticalWrites = Behavior.DEFAULT.deriveWithChanges("CRITICAL_WRITES", builder -> builder
.on(Selectors.writes().retryable().point().ap(), ops -> ops.maximumNumberOfCallAttempts(5)));
// Fast reads for user-facing queries
this.readSession = cluster.createSession(readFast);
// Standard writes
this.writeSession = cluster.createSession(Behavior.DEFAULT);
// Critical operations (payments, audit logs)
this.criticalSession = cluster.createSession(criticalWrites);
}
public Optional<Record> getUser(String userId) {
RecordStream stream = readSession.query(users.id(userId)).execute();
Optional<Record> out = stream.getFirst()
.filter(RecordResult::isOk)
.map(RecordResult::recordOrThrow);
stream.close();
return out;
}
public void updatePreferences(String userId, Map<String, Object> prefs) {
writeSession.upsert(users)
.bins("prefs")
.id(userId).values(prefs)
.execute();
}
public void recordPayment(String paymentId, double amount) {
criticalSession.insert(payments)
.bins("amount", "timestamp")
.id(paymentId).values(amount, System.currentTimeMillis())
.execute();
}
}

Choosing the right behavior

Use this decision tree:

What type of operation?
├── Read operation
│ ├── Need latest data? → DEFAULT
│ └── Speed critical, stale OK? → READ_FAST
└── Write operation
├── Critical data (payments, audit)? → custom critical-write behavior
└── Normal data? → DEFAULT

Quick reference

ScenarioRecommended Behavior
User profile lookupsREAD_FAST
Shopping cart updatesDEFAULT
Payment processingcustom critical-write behavior
Real-time game stateREAD_FAST
Audit loggingcustom critical-write behavior
Session managementDEFAULT
Analytics writesDEFAULT
Cache readsREAD_FAST

Behavior anti-patterns

❌ Don’t: use critical-write settings for everything

// Too conservative—unnecessarily slow
Behavior criticalWrites = Behavior.DEFAULT.deriveWithChanges("CRITICAL_WRITES", builder -> builder
.on(Selectors.writes().retryable().point().ap(), ops -> ops.maximumNumberOfCallAttempts(5)));
Session session = cluster.createSession(criticalWrites);
RecordStream s = session.query(users.id("user-1")).execute(); // Reads don't need durable write settings
s.getFirst().ifPresent(r -> { /* ... */ });
s.close();

❌ Don’t: set zero timeouts

// Will fail constantly
Behavior bad = Behavior.DEFAULT.deriveWithChanges("BAD_TIMEOUTS", builder -> builder
.on(Selectors.all(), ops -> ops.abandonCallAfter(Duration.ZERO)));

❌ Don’t: ignore timeouts in production

// Development might work, production will have network variance
Behavior risky = Behavior.DEFAULT.deriveWithChanges("RISKY_TIMEOUTS", builder -> builder
.on(Selectors.all(), ops -> ops.abandonCallAfter(Duration.ofDays(1))));

✅ Do: match behavior to operation type

// Different behaviors for different needs
Behavior readFast = Behavior.DEFAULT.deriveWithChanges("READ_FAST", builder -> builder
.on(Selectors.reads().get().ap(), ops -> ops.maximumNumberOfCallAttempts(2)));
Behavior criticalWrites = Behavior.DEFAULT.deriveWithChanges("CRITICAL_WRITES", builder -> builder
.on(Selectors.writes().retryable().point().ap(), ops -> ops.maximumNumberOfCallAttempts(5)));
Session reads = cluster.createSession(readFast);
Session writes = cluster.createSession(Behavior.DEFAULT);
Session critical = cluster.createSession(criticalWrites);

Next steps

Connect to Aerospike

Configure your cluster connection.

Connect →

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?