Skip to content

Best practices

Follow these best practices to get the most out of the Developer SDK in production environments.

Client sharing patterns

Share the cluster instance

Create one Cluster instance per application and share it. The Cluster manages connection pooling, node discovery, and cluster state.

import com.aerospike.client.sdk.policy.Behavior;
import com.aerospike.client.sdk.Cluster;
import com.aerospike.client.sdk.ClusterDefinition;
import com.aerospike.client.sdk.Host;
import com.aerospike.client.sdk.Session;
// ✅ Good: Single cluster instance, shared across your app
public class AerospikeConfig {
private static final Cluster cluster = new ClusterDefinition(
new Host("aerospike-1.example.com", 3000),
new Host("aerospike-2.example.com", 3000)
).connect();
public static Cluster getCluster() {
return cluster;
}
}
// Usage in services
public class UserService {
private final Session session;
public UserService() {
this.session = AerospikeConfig.getCluster()
.createSession(Behavior.DEFAULT);
}
}

Don’t create clusters per request

import com.aerospike.client.sdk.Cluster;
import com.aerospike.client.sdk.ClusterDefinition;
import com.aerospike.client.sdk.DataSet;
import com.aerospike.client.sdk.Record;
import com.aerospike.client.sdk.RecordStream;
import com.aerospike.client.sdk.Session;
// ❌ Bad: Creating a new cluster per request wastes resources
public Record getUser(String userId) {
DataSet users = DataSet.of("test", "users");
try (Cluster cluster = new ClusterDefinition("localhost", 3000).connect()) { // Don't do this per request!
Session session = cluster.createSession(Behavior.DEFAULT);
Record out;
try (RecordStream stream = session.query(users.id(userId)).execute()) {
out = stream.getFirstRecord();
}
return out;
}
}

Session management

Use multiple sessions for different workloads

Create dedicated sessions for different operation types:

public class DatabaseService {
private final Session readSession;
private final Session writeSession;
private final Session criticalSession;
public DatabaseService(Cluster cluster) {
Behavior readFast = Behavior.DEFAULT.deriveWithChanges("READ_FAST", b -> {});
Behavior criticalWrites = Behavior.DEFAULT.deriveWithChanges("CRITICAL_WRITES", b -> {});
// 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);
}
}

Resource cleanup

Always close resources

Close sessions and clusters when your application shuts down:

import com.aerospike.client.sdk.policy.Behavior;
import com.aerospike.client.sdk.Cluster;
import com.aerospike.client.sdk.ClusterDefinition;
import com.aerospike.client.sdk.DataSet;
import com.aerospike.client.sdk.Session;
// Using try-with-resources
DataSet users = DataSet.of("test", "users");
try (Cluster cluster = new ClusterDefinition("localhost", 3000).connect()) {
Session session = cluster.createSession(Behavior.DEFAULT);
session.insert(users)
.bins("name")
.id("user-1").values("Alice")
.execute();
}
// Or with shutdown hooks
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
session.close();
cluster.close();
}));

Connection limits

Size your connection pool appropriately

The default connection pool is usually sufficient, but high-throughput applications may need tuning:

Cluster cluster = new ClusterDefinition("localhost", 3000)
.withSystemSettings(builder -> builder
.connections(ops -> ops.maximumConnectionsPerNode(300)) // Default is usually 100
)
.connect();

Behavior reuse

Define behaviors once, reuse everywhere

// ✅ Good: Define behaviors as constants
public class Behaviors {
public static final Behavior FAST_READ =
Behavior.DEFAULT.deriveWithChanges("FAST_READ", b -> {});
public static final Behavior SAFE_WRITE =
Behavior.DEFAULT.deriveWithChanges("SAFE_WRITE", b -> {});
}
// Usage
Session fastSession = cluster.createSession(Behaviors.FAST_READ);
Session safeSession = cluster.createSession(Behaviors.SAFE_WRITE);

Error handling

Handle errors at the right level

import com.aerospike.client.sdk.DataSet;
import com.aerospike.client.sdk.AerospikeException.RecordExistsException;
import com.aerospike.client.sdk.AerospikeException.Timeout;
import com.aerospike.client.sdk.AerospikeException;
// ✅ Good: Catch specific exceptions, handle appropriately
DataSet users = DataSet.of("test", "users");
try {
session.insert(users)
.bins("name")
.id(userId).values(name)
.execute();
} catch (RecordExistsException e) {
// Expected case: user already exists
log.info("User {} already exists, skipping", userId);
} catch (Timeout e) {
// Transient: might succeed on retry
throw new RetryableException("Database timeout", e);
} catch (AerospikeException e) {
// Unexpected: log and fail
log.error("Unexpected database error", e);
throw new DatabaseException("Failed to create user", e);
}

See Handle Errors Gracefully for detailed error handling patterns.

Production recommendations

Pre-flight checklist

Before deploying to production:

  1. Connection pooling: Verify pool size matches your concurrency needs
  2. Timeouts: Set appropriate timeouts for your SLAs
  3. Retries: Configure retry behavior for transient failures
  4. Monitoring: Enable metrics collection
  5. Logging: Configure appropriate log levels
  6. Resource cleanup: Ensure proper shutdown handling

Monitoring essentials

Track these metrics in production:

MetricWhy It Matters
Operation latency (p50, p95, p99)Detect performance degradation
Error rate by typeIdentify systematic issues
Connection pool utilizationPrevent connection exhaustion
Retry rateDetect cluster instability

Next steps

Tune Performance & Reliability

Configure Behaviors for your workload.

Behaviors →

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?