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 behaviorSession session = cluster.createSession(Behavior.DEFAULT);
// All operations use DEFAULT settings by defaultRecordStream stream = session.query(users.id("user-1")).execute();stream.getFirst().ifPresent(r -> { /* ... */ });session.insert(users) .bins("name") .id("user-2").values("Bob") .execute();# Create a session with a pre-built behaviorsession = cluster.create_session(Behavior.DEFAULT)
# All operations use DEFAULT settings by defaultstream = await session.query(users.id("user-1")).execute()row = await stream.first()if row is not None: # ... handle the record ... passstream.close()await session.insert(key=users.id("user-2")).put({"name": "Bob"}).execute()Behavior profiles
Use these common behavior profiles as starting points:
| Behavior | Best For | Characteristics |
|---|---|---|
DEFAULT | General purpose | Built-in baseline behavior |
READ_FAST (derived) | Read-heavy workloads | Lower latency, allows AP replica reads |
STRICT_READS (Java derived) / STRICTLY_CONSISTENT (Python built-in) | SC workloads | Linearizable 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 onlysession = cluster.create_session(Behavior.DEFAULT)
# Built-in Behavior.DEFAULT:# - Socket timeout: 5s# - Total timeout: 30s# - Max retries: 2# - send_key: True# - Read mode: Master onlyUse 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)session = cluster.create_session(Behavior.READ_FAST)
# Built-in Behavior.READ_FAST:# - Socket timeout: 50ms# - Total timeout: 200ms# - Max retries: 3# - 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 replicassession = cluster.create_session(Behavior.DEFAULT)
# Typical settings:# - Socket timeout: 100ms# - Total timeout: 5000ms# - Max retries: 3# - Commit level: All replicasUse 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 itSession session = cluster.createSession(fastReads);from datetime import timedelta
from aerospike_sdk import Behaviorfrom aerospike_sdk.policy import Settings
# Build a custom behavior with explicit scope settings.fast_reads = Behavior.DEFAULT.derive_with_changes( "READ_FAST_CUSTOM", reads_ap=Settings( total_timeout=timedelta(milliseconds=100), max_retries=2, retry_delay=timedelta(milliseconds=5), ), reads_batch=Settings( total_timeout=timedelta(milliseconds=200), max_retries=3, ),)
# Use itsession = cluster.create_session(fast_reads)Available configuration options (Java)
| Setting goal | Java behavior method |
|---|---|
| End-to-end call timeout | abandonCallAfter(Duration) |
| Retry count | maximumNumberOfCallAttempts(int) |
| Delay between retries | delayBetweenRetries(Duration) |
| AP read mode | readMode(ReadModeAP) |
| SC read mode | consistency(ReadModeSC) |
| AP write durability | commitLevel(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 goal | Python behavior fields |
|---|---|
| End-to-end call timeout | Settings(total_timeout=timedelta(...)) |
| Retry count | Settings(max_retries=int) |
| Delay between retries | Settings(retry_delay=timedelta(...)) |
| AP read scope | reads_ap=Settings(...) |
| SC read scope | reads_sc=Settings(...) |
| Batch read scope | reads_batch=Settings(...) |
| All operations scope | all=Settings(...) |
Python recipes for common behavior tasks
Use these patterns directly when deriving behaviors in Python:
from datetime import timedelta
from aerospike_sdk import Behaviorfrom 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))));from datetime import timedelta
from aerospike_sdk import Behaviorfrom aerospike_sdk.policy import Settings
# Example: Allow up to 5 attempts for AP reads, with a 2-second total timeout.patient = Behavior.DEFAULT.derive_with_changes( "PATIENT", reads_ap=Settings( total_timeout=timedelta(seconds=2), max_retries=5, retry_delay=timedelta(milliseconds=25), ),)Read consistency options
For clusters in AP mode (availability-prioritized):
| ReadModeAP | Behavior |
|---|---|
ONE | Read from master only (strong consistency) |
ALL | Read from any replica (eventual consistency, lower latency) |
For clusters in SC mode (strong consistency):
| ReadModeSC | Behavior |
|---|---|
SESSION | Session consistency (your writes are visible to your reads) |
LINEARIZE | Linearizable 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)));# For AP mode cluster: allow replica readseventual_reads = Behavior.READ_FAST
# For SC mode cluster: linearizable readsstrict_reads = Behavior.STRICTLY_CONSISTENTSession-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 settingsBehavior criticalWrites = Behavior.DEFAULT.deriveWithChanges("CRITICAL_WRITES", b -> {});Session session = cluster.createSession(criticalWrites);
session.insert(users) .bins("name") .id("user-1").values("Alice") .execute(); // DurableRecordStream read = session.query(users.id("user-1")).execute(); // Durableread.getFirst().ifPresent(r -> { /* ... */ });read.close();session.delete(users.id("user-1")).execute().close(); // Durablefrom datetime import timedelta
from aerospike_sdk import Behaviorfrom aerospike_sdk.policy import Settings
# All operations on this session use custom critical-write settingscritical_writes = Behavior.DEFAULT.derive_with_changes( "CRITICAL_WRITES", writes=Settings( total_timeout=timedelta(seconds=5), max_retries=5, retry_delay=timedelta(milliseconds=50), ),)session = cluster.create_session(critical_writes)
await session.insert(key=users.id("user-1")).put({"name": "Alice"}).execute() # Durablestream = await session.query(users.id("user-1")).execute() # Durableawait stream.first_or_raise()stream.close()stream = await session.delete(key=users.id("user-1")).execute() # Durablestream.close()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 DEFAULTRecordStream 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();from datetime import timedelta
from aerospike_sdk import Behaviorfrom aerospike_sdk.policy import Settings
# Session uses DEFAULT by defaultsession = cluster.create_session(Behavior.DEFAULT)
# Most operations use DEFAULTstream = await session.query(users.id("user-1")).execute()row = await stream.first()# ... handle row if present ...stream.close()
# This specific write uses a dedicated critical-write sessioncritical_writes = Behavior.DEFAULT.derive_with_changes( "CRITICAL_WRITES", writes=Settings( total_timeout=timedelta(seconds=5), max_retries=5, retry_delay=timedelta(milliseconds=50), ),)critical_session = cluster.create_session(critical_writes)await critical_session.insert(key=orders.id("order-1")).put( {"total": 999.99}).execute()
# Back to DEFAULTstream = await session.query(users.id("user-2")).execute()row = await stream.first()# ... handle row if present ...stream.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 policySession 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 -> { /* ... */ });# DEFAULT behavior: automatic retries per policysession = cluster.create_session(Behavior.DEFAULT)
# This operation will automatically retry on transient failures (per policy)stream = await session.query(users.id("user-1")).execute()await stream.first_or_raise()stream.close()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)); } }}
// UsageRecord user = retryWithBackoff( () -> { try (RecordStream stream = session.query(users.id("user-1")).execute()) { return stream.getFirstRecord(); } }, 5 // Max 5 retries);import asyncioimport randomimport time
from aerospike_sdk import ConnectionError, TimeoutError
async def retry_with_backoff(operation, max_retries=5): attempt = 0 delay = 0.1 # 100ms
while True: try: return await operation() except (TimeoutError, ConnectionError): attempt += 1 if attempt >= max_retries: raise # Exhausted retries
print(f"Retry {attempt} after {delay * 1000:.0f}ms") await asyncio.sleep(delay)
# Exponential backoff with jitter delay = delay * 2 + random.uniform(0, 0.1)
# Usageasync def load_user(): stream = await session.query(users.id("user-1")).execute() try: row = await stream.first_or_raise() return row.record_or_raise() finally: stream.close()
user = await retry_with_backoff(load_user, max_retries=5)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(); }}import time
from datetime import timedelta
from aerospike_sdk import Behavior, DataSetfrom aerospike_sdk.policy import Settings
class DatabaseService: users = DataSet.of("app", "users") payments = DataSet.of("app", "payments")
def __init__(self, cluster): read_fast = Behavior.DEFAULT.derive_with_changes( "READ_FAST", reads_ap=Settings( total_timeout=timedelta(milliseconds=100), max_retries=2, ), ) critical_writes = Behavior.DEFAULT.derive_with_changes( "CRITICAL_WRITES", writes=Settings( total_timeout=timedelta(seconds=5), max_retries=5, retry_delay=timedelta(milliseconds=50), ), )
# Fast reads for user-facing queries self.read_session = cluster.create_session(read_fast)
# Standard writes self.write_session = cluster.create_session(Behavior.DEFAULT)
# Critical operations (payments, audit logs) self.critical_session = cluster.create_session(critical_writes)
async def get_user(self, user_id): stream = await self.read_session.query(self.users.id(user_id)).execute() row = await stream.first_or_raise() record = row.record_or_raise() stream.close() return record
async def update_preferences(self, user_id, prefs): await self.write_session.upsert(key=self.users.id(user_id)).put( {"prefs": prefs} ).execute()
async def record_payment(self, payment_id, amount): await self.critical_session.insert(key=self.payments.id(payment_id)).put( {"amount": amount, "timestamp": time.time()} ).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? → DEFAULTQuick reference
| Scenario | Recommended Behavior |
|---|---|
| User profile lookups | READ_FAST |
| Shopping cart updates | DEFAULT |
| Payment processing | custom critical-write behavior |
| Real-time game state | READ_FAST |
| Audit logging | custom critical-write behavior |
| Session management | DEFAULT |
| Analytics writes | DEFAULT |
| Cache reads | READ_FAST |
Behavior anti-patterns
❌ Don’t: use critical-write settings for everything
// Too conservative—unnecessarily slowBehavior 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 settingss.getFirst().ifPresent(r -> { /* ... */ });s.close();❌ Don’t: set zero timeouts
// Will fail constantlyBehavior 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 varianceBehavior 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 needsBehavior 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
Data Model
Understand namespaces, sets, and records.
Error Handling
Handle timeouts and retries gracefully.
Connect to Aerospike
Configure your cluster connection.
Async Operations
Scale with non-blocking operations.