# 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.

-   [Java](#tab-panel-2928)
-   [Python](#tab-panel-2929)

```java
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);

    }

}
```

```python
from aerospike_sdk import Behavior, ClusterDefinition, Host

# ✅ Good: Single cluster instance, shared across your app

class AerospikeConfig:

    _cluster = None

    @classmethod

    async def get_cluster(cls):

        if cls._cluster is None:

            cls._cluster = await ClusterDefinition(

                hosts=[

                    Host.of("aerospike-1.example.com", 3000),

                    Host.of("aerospike-2.example.com", 3000),

                ]

            ).connect()

        return cls._cluster

# Usage in services (call await AerospikeConfig.get_cluster() once at app startup,

# then inject the shared cluster into services)

class UserService:

    def __init__(self, cluster):

        self.session = cluster.create_session(Behavior.DEFAULT)
```

### Don’t create clusters per request

-   [Java](#tab-panel-2930)
-   [Python](#tab-panel-2931)

```java
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;

    }

}
```

```python
from aerospike_sdk import ClusterDefinition, DataSet

# ❌ Bad: Creating a new cluster per request wastes resources

async def get_user(user_id):

    users = DataSet.of("test", "users")

    async with await ClusterDefinition("localhost", 3000).connect() as cluster:  # Don't do this!

        session = cluster.create_session(Behavior.DEFAULT)

        stream = await session.query(users.id(user_id)).execute()

        row = await stream.first_or_raise()

        record = row.record_or_raise()

        stream.close()

        return record
```

## Session management

### Use multiple sessions for different workloads

Create dedicated sessions for different operation types:

-   [Java](#tab-panel-2932)
-   [Python](#tab-panel-2933)

```java
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);

    }

}
```

```python
class DatabaseService:

    def __init__(self, cluster):

        # Fast reads for user-facing queries

        self.read_session = cluster.create_session(Behavior.READ_FAST)

        # Standard writes

        self.write_session = cluster.create_session(Behavior.DEFAULT)

        # Critical operations (payments, audit logs)

        self.critical_session = cluster.create_session(Behavior.DEFAULT)
```

## Resource cleanup

### Always close resources

Close sessions and clusters when your application shuts down:

-   [Java](#tab-panel-2934)
-   [Python](#tab-panel-2935)

```java
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();

}));
```

```python
from aerospike_sdk import Behavior, ClusterDefinition, DataSet

# Using async context managers (typical async app)

users = DataSet.of("test", "users")

async with await ClusterDefinition("localhost", 3000).connect() as cluster:

    session = cluster.create_session(Behavior.DEFAULT)

    await session.insert(key=users.id("user-1")).put({"name": "Alice"}).execute()

# Or register cleanup for a long-lived cluster from app startup

import atexit

# cluster = ...  # created once at startup (see Connect guide)

# session = cluster.create_session(Behavior.DEFAULT)

# atexit.register(session.close)

# atexit.register(cluster.close)
```

## Connection limits

### Size your connection pool appropriately

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

-   [Java](#tab-panel-2936)
-   [Python](#tab-panel-2937)

```java
Cluster cluster = new ClusterDefinition("localhost", 3000)

    .withSystemSettings(builder -> builder

        .connections(ops -> ops.maximumConnectionsPerNode(300))  // Default is usually 100

    )

    .connect();
```

```python
from aerospike_sdk import ClusterDefinition

from aerospike_sdk.policy.system_settings import SystemSettings

cluster = await (

    ClusterDefinition("localhost", 3000)

    .with_system_settings(

        SystemSettings(max_connections_per_node=300)  # Default is usually 100

    )

    .connect()

)
```

::: connection pool sizing
A good starting point is `maxConnectionsPerNode = expectedConcurrentOperations / numberOfNodes`. Monitor connection usage and adjust as needed.
:::

## Behavior reuse

### Define behaviors once, reuse everywhere

-   [Java](#tab-panel-2938)
-   [Python](#tab-panel-2939)

```java
// ✅ 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);
```

```python
# ✅ Good: Define behaviors as module-level constants

FAST_READ = Behavior.READ_FAST

SAFE_WRITE = Behavior.DEFAULT

# Usage

fast_session = cluster.create_session(FAST_READ)

safe_session = cluster.create_session(SAFE_WRITE)
```

## Error handling

### Handle errors at the right level

-   [Java](#tab-panel-2940)
-   [Python](#tab-panel-2941)

```java
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);

}
```

```python
from aerospike_sdk import AerospikeError, ConnectionError, DataSet, TimeoutError

from aerospike_async.exceptions import ResultCode

# ✅ Good: Catch specific exceptions, handle appropriately

users = DataSet.of("test", "users")

try:

    await session.insert(key=users.id(user_id)).put({"name": name}).execute()

except TimeoutError as e:

    # Transient: might succeed on retry

    raise RetryableException("Database timeout") from e

except ConnectionError as e:

    raise RetryableException("Database connection failed") from e

except AerospikeError as e:

    if e.result_code == ResultCode.KEY_EXISTS_ERROR:

        logger.info(f"User {user_id} already exists, skipping")

    else:

        logger.error("Unexpected database error", exc_info=True)

        raise DatabaseException("Failed to create user") from e
```

See [Handle Errors Gracefully](https://aerospike.com/docs/develop/client/sdk/concepts/errors) 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:

| Metric | Why It Matters |
| --- | --- |
| Operation latency (p50, p95, p99) | Detect performance degradation |
| Error rate by type | Identify systematic issues |
| Connection pool utilization | Prevent connection exhaustion |
| Retry rate | Detect cluster instability |

## Next steps

Tune Performance & Reliability

Configure Behaviors for your workload.

[Behaviors →](https://aerospike.com/docs/develop/client/sdk/concepts/behaviors)

Handle Errors Gracefully

Implement robust error handling.

[Error Handling →](https://aerospike.com/docs/develop/client/sdk/concepts/errors)

Configuration Options

Externalize and manage configuration.

[Configuration →](https://aerospike.com/docs/develop/client/sdk/concepts/configuration)