Monitor client performance
For the complete documentation index see: llms.txt
All documentation pages available in markdown.
Applies to
- Aerospike Developer SDK (Java 21+ and Python 3.11+)
- Aerospike Database 8.2.0 or later, unless a section states otherwise
Client-side performance monitoring helps you detect regressions, troubleshoot timeouts and connection issues, and correlate application behavior with cluster health. Typical signals include operation volume, error and retry rates, latency distributions, and connection pool usage, aggregated by cluster, node, or namespace.
Both SDKs have a built-in, cluster-scoped metrics collector, like the classic Aerospike clients. It’s off by default. In the Java SDK, turn it on in the cluster definition’s metrics settings and poll cluster.getMetricsSnapshot(). In the Python SDK, call cluster.enable_metrics(MetricsPolicy(operational_enabled=True)) and poll cluster.metrics_snapshot(). A snapshot holds latency histograms, byte counts, connection lifecycle, and retry and timeout counters; without the operational tier, the collector records only connection and node gauges. Both SDKs can also write the snapshots to a periodic metrics log file. The sections below cover the built-in collector and its metrics, and manual instrumentation for exporting measurements to the tools your team already uses (for example Prometheus, OpenTelemetry, Datadog, or structured logs). For complementary request-level detail during development, see Configure logging.
Except where noted, snippets on this page use the imports below. A snippet lists additional import lines only when it needs a type not shown here. When this page includes a Complete example section, that block is fully self-contained with every import required to run it.
import com.aerospike.client.sdk.Cluster;import com.aerospike.client.sdk.ClusterDefinition;import com.aerospike.client.sdk.DataSet;import com.aerospike.client.sdk.Session;import com.aerospike.client.sdk.metrics.LatencySnapshot;import com.aerospike.client.sdk.metrics.LatencyType;import com.aerospike.client.sdk.metrics.MetricsExporterType;import com.aerospike.client.sdk.metrics.MetricsSnapshot;import com.aerospike.client.sdk.metrics.NamespaceMetricsSnapshot;import com.aerospike.client.sdk.metrics.NodeMetricsSnapshot;import com.aerospike.client.sdk.policy.Behavior;
import java.time.Clock;import java.time.Duration;import java.util.Arrays;import java.util.Map;from datetime import timedeltaimport time
from aerospike_sdk import Behavior, ClusterDefinition, DataSetfrom aerospike_sdk.metrics import CommandType, LatencyType, MetricsPolicy, Samplerfrom aerospike_sdk.policy.system_settings import MetricsSettings, SystemSettingsAvailable metrics
Collection comes in tiers, so the cost of measuring is opt-in. The tiers are the same in both SDKs.
| Tier | Contents | Enabled by |
|---|---|---|
| Always on | Per-node connection counts (in use, in pool, opened, closed) and node membership | enabled(true) / enabled=True |
| Operational | Per-namespace latency histograms (conn, write, read, batch, query), bytes in and out, errors, timeouts, key-busy and retry counters | operational(o -> o.enabled(true)) / operational_enabled=True |
| Usage | Counts of which SDK features the application uses | usage(u -> u.enabled(true)) / usage_enabled=True |
Enabling metrics without the operational tier records no per-command timing, so the latency histograms stay empty.
Latency histograms default to milliseconds with 7 columns and a shift of 1, giving the buckets <=1, >1, >2, >4, >8, >16 and >32 ms. Snapshot values are cumulative since metrics were enabled, not deltas since the last export.
The metric names below are the columns of the metrics log file.
Standard metrics
Enabling metrics records these.
| Metric | Type | Description |
|---|---|---|
conns_in_use | Gauge | Connections executing a command, per node |
conns_in_pool | Gauge | Idle connections in the pool, per node |
conns_opened | Counter | Connections opened, per node |
conns_closed | Counter | Connections closed, per node |
cpu | Gauge | CPU use of the client process, as a percentage |
mem | Gauge | Memory use of the client process, in bytes |
Extended metrics
Extended metrics come in two tiers, each turned on separately. The operational tier records the first eight rows, and the usage tier the last four.
| Metric | Type | Description |
|---|---|---|
command_count | Counter | Commands issued, cluster-wide |
command_retries | Counter | Command retries, cluster-wide |
errors | Counter | Failed commands that no more specific counter records, per namespace |
timeouts | Counter | Commands that timed out, per namespace. The Python SDK counts only the timeouts the server reports. |
key_busy | Counter | Commands that failed with KEY_BUSY (14), per namespace |
bytes_in | Counter | Bytes received from the server, per namespace |
bytes_out | Counter | Bytes sent to the server, per namespace |
latency | Histogram | Command latency per namespace, one histogram each for conn, write, read, batch and query |
blocking_count | Counter | Calls that return once their result is ready |
deferred_count | Counter | Asynchronous calls |
background_count | Counter | Background tasks started |
tran_count | Counter | Transactions |
Enable metrics
ClusterDefinition definition = new ClusterDefinition("localhost", 3000) .withSystemSettings(settings -> settings .metrics(metrics -> metrics .enabled(true) .exporter(MetricsExporterType.NONE) .extended(extended -> extended .operational(operational -> operational.enabled(true)))));
try (Cluster cluster = definition.connect()) { Session session = cluster.createSession(Behavior.DEFAULT); DataSet users = DataSet.of("test", "users"); session.query(users.id("user-1")).execute().getFirstRecord();}📖 API reference:
ClusterDefinition(String,int)|ClusterDefinition.withSystemSettings(...)|ClusterDefinition.connect()|Cluster.enableMetrics(...)|Cluster.disableMetrics()|MetricsExporterType
definition = ClusterDefinition("localhost", 3000).with_system_settings( SystemSettings( metrics=MetricsSettings( enabled=True, exporter="none", operational_enabled=True, ), ),)
async with definition.connect() as cluster: session = cluster.create_session(Behavior.DEFAULT) users = DataSet.of("test", "users") stream = await session.query(users.id("user-1")).execute() await stream.first() stream.close()📖 API reference:
ClusterDefinition|SystemSettings|Cluster.enable_metrics()|Cluster.disable_metrics()
Disabling metrics, or closing the cluster, exports one final snapshot so the activity since the last export is not lost.
Access metrics snapshots
Get current metric values programmatically:
// NONE: poll snapshots and export nothing.try (Cluster cluster = new ClusterDefinition("localhost", 3000) .withSystemSettings(s -> s.metrics(m -> m .enabled(true) .exporter(MetricsExporterType.NONE) .extended(e -> e.operational(o -> o.enabled(true))))) .connect()) {
// ... application traffic runs here ...
MetricsSnapshot snapshot = cluster.getMetricsSnapshot(); long reads = 0; for (NodeMetricsSnapshot node : snapshot.nodes) { for (NamespaceMetricsSnapshot ns : node.namespaces) { for (LatencySnapshot latency : ns.latencies) { if (latency.type == LatencyType.READ) { for (long count : latency.bucketCounts) { reads += count; } } } } } System.out.printf("%d reads%n", reads);
// Per-node connection pool detail: for (NodeMetricsSnapshot node : snapshot.nodes) { System.out.printf("%s: %d connections in use, %d in pool%n", node.nodeName, node.connections.inUse, node.connections.inPool); }}📖 API reference:
ClusterDefinition.withSystemSettings()|Cluster.getMetricsSnapshot()|MetricsSnapshot
cluster.enable_metrics(MetricsPolicy(operational_enabled=True))
# ... application traffic runs here ...
snapshot = cluster.metrics_snapshot()reads = snapshot.latency(LatencyType.READ)print(f"{reads.count} reads, avg {reads.average:.1f} ms")
# Per-command, cluster-wide detail:agg = snapshot.cluster_aggregatedgets = agg.command_histogram(CommandType.GET)print(f"{gets.count} GET commands, {gets.average:.0f}ms avg")📖 PSDK Guide: Client metrics
Poll on an interval (for example every 30 seconds) rather than per operation.
Metrics log files
The file exporter writes the line-oriented metrics log format under a report directory: a header line that declares the schema, then one line per export. It is the default exporter type (FILE in Java, learn_metrics_file in Python), and it writes nothing unless a report directory is set. To send snapshots somewhere else, select CUSTOM and add an IMetricsExporter (Java), or register an AsyncMetricsExporter with cluster.add_exporter() (Python).
// Each export interval appends a snapshot line to metrics-<yyyyMMddHHmmss>.log in reportDir.try (Cluster cluster = new ClusterDefinition("localhost", 3000) .withSystemSettings(s -> s.metrics(m -> m .enabled(true) .reportDir("/var/log/aerospike/metrics") .exportInterval(Duration.ofSeconds(30)))) .connect()) {
// ... application traffic runs here ...}📖 API reference:
ClusterDefinition.withSystemSettings()|MetricsSettings.MetricsTweaks
# Each export interval appends a snapshot line to metrics-<yyyyMMddHHmmss>.log in report_dir.async def main(): definition = ClusterDefinition("localhost", 3000).with_system_settings( SystemSettings(metrics=MetricsSettings( enabled=True, report_dir="/var/log/aerospike/metrics", export_interval=timedelta(seconds=30), )) ) async with definition.connect() as cluster: ... # application traffic runs here📖 API reference:
ClusterDefinition.with_system_settings()|SystemSettings|MetricsSettings
📖 PSDK Guide: Writing metrics to files
Client identification labels
Add labels to identify metrics by application or environment. Labels are attached to every snapshot and written to every log line.
// Labels go on every metrics snapshot and metrics log line.try (Cluster cluster = new ClusterDefinition("localhost", 3000) .withSystemSettings(s -> s.metrics(m -> m .enabled(true) .reportDir("/var/log/aerospike/metrics") .labels(Map.of("app", "my-service", "env", "production", "region", "us-west-2")))) .connect()) {
// ... application traffic runs here ...}📖 API reference:
ClusterDefinition.withSystemSettings()|MetricsSettings.MetricsTweaks
# Labels go on every metrics snapshot and metrics log line.async def main(): definition = ClusterDefinition("localhost", 3000).with_system_settings( SystemSettings(metrics=MetricsSettings( enabled=True, report_dir="/var/log/aerospike/metrics", labels={"app": "my-service", "env": "production", "region": "us-west-2"}, )) ) async with definition.connect() as cluster: ... # application traffic runs here📖 API reference:
ClusterDefinition.with_system_settings()|MetricsSettings
Instrument your own calls
You can also time application operations yourself and export them to the tools your team already uses, for example Prometheus, OpenTelemetry, Datadog, or structured logs.
// Additional imports for this example:import io.micrometer.core.instrument.MeterRegistry;import io.micrometer.core.instrument.Timer;import io.micrometer.prometheus.PrometheusConfig;import io.micrometer.prometheus.PrometheusMeterRegistry;
// Application-owned registry (not provided by the Aerospike JAR)MeterRegistry registry = new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
try (Cluster cluster = new ClusterDefinition("localhost", 3000).connect()) { Timer.Sample sample = Timer.start(registry); try { // session.query(...).execute(); etc. } finally { sample.stop(registry.timer("aerospike.sdk.requests")); }}📖 API reference:
ClusterDefinition(String,int)|ClusterDefinition.connect()|Cluster.close()|ChainableQueryBuilder.execute()
async def main(): async with ClusterDefinition("localhost", 3000).connect() as cluster: session = cluster.create_session(Behavior.DEFAULT) users = DataSet.of("test", "users")
start = time.perf_counter() stream = await session.query(users.id("user-1")).execute() await stream.first() stream.close() duration_ms = (time.perf_counter() - start) * 1000 print(f"aerospike.read.latency_ms={duration_ms:.2f}")📖 API reference:
ClusterDefinition|Cluster.create_session()|DataSet.of()|DataSet.id()|Behavior.DEFAULT|Session.query()|RecordStream.first()|RecordStream.close()|QueryBuilder.execute()
Prometheus integration
# Additional imports for this example:from prometheus_client import CollectorRegistry, generate_latestfrom prometheus_client import Counter, Histogram
registry = CollectorRegistry()reads_total = Counter("aerospike_reads_total", "Read operations", registry=registry)read_latency = Histogram("aerospike_read_latency_ms", "Read latency ms", registry=registry)
async def main(): async with ClusterDefinition("localhost", 3000).connect() as cluster: session = cluster.create_session(Behavior.DEFAULT) users = DataSet.of("test", "users") with read_latency.time(): stream = await session.query(users.id("user-1")).execute() await stream.first() stream.close() reads_total.inc()
# Expose metrics endpoint@app.route('/metrics')def metrics(): return generate_latest(registry)📖 API reference:
ClusterDefinition|Cluster.create_session()|DataSet.of()|DataSet.id()|Behavior.DEFAULT|Session.query()|RecordStream.first()|RecordStream.close()|QueryBuilder.execute()
Integration with monitoring systems
Prometheus + Grafana
- Export metrics using an HTTP endpoint
- Configure Prometheus to scrape your application
- Import the Aerospike client dashboard in Grafana
Datadog
// Additional imports for this example:import io.micrometer.datadog.DatadogConfig;import io.micrometer.datadog.DatadogMeterRegistry;
DatadogMeterRegistry registry = new DatadogMeterRegistry( DatadogConfig.DEFAULT, Clock.SYSTEM);// Register timers/counters around your own Aerospike calls; the SDK does not wire this for you.📖 API reference:
com.aerospike.client.sdk
# Additional imports for this example:from datadog import statsd
async def main(): async with ClusterDefinition("localhost", 3000).connect() as cluster: session = cluster.create_session(Behavior.DEFAULT) users = DataSet.of("test", "users") row = await session.query(users.id("user-1")).first() statsd.increment("aerospike.read.total", tags=["app:my-service"]) statsd.gauge("aerospike.read.found", 1 if row is not None else 0)📖 API reference:
ClusterDefinition|Cluster.create_session()|DataSet.of()|DataSet.id()|Behavior.DEFAULT|Session.query()|RecordStream.first()|RecordStream.close()|QueryBuilder.execute()
Performance impact
Built-in collection is cheap enough that it is not a reason to leave metrics off. The operational tier, which times every command, is the part with a per-command cost. For latency-critical applications, leave it off, or record only a sample of commands:
// Gauges only: with the operational tier off, no latency histograms are recorded.try (Cluster cluster = new ClusterDefinition("localhost", 3000) .withSystemSettings(s -> s.metrics(m -> m .enabled(true) .exporter(MetricsExporterType.NONE))) .connect()) {
// ... application traffic runs here ...}📖 API reference:
ClusterDefinition.withSystemSettings()
# Gauges only: with the operational tier off, no latency histograms are recorded.cluster.enable_metrics(MetricsPolicy())Or record only a sample of commands:
# Record the operational tier for about 10% of calls.cluster.enable_metrics(MetricsPolicy( operational_enabled=True, sampler=Sampler.probability(0.1),))📖 API reference:
Cluster.enable_metrics()
Next steps
Configure Logging
Set up client-side logging for debugging.
Tune Performance
Optimize client configuration with Behaviors.