Skip to content

Session store recipe

Build a high-performance session store for web applications using the Developer SDK.

Use case

Store user session data with:

  • Sub-millisecond reads for every page load
  • Automatic expiration (TTL)
  • High write throughput for session updates

Quick implementation

import com.aerospike.client.sdk.AerospikeException;
import com.aerospike.client.sdk.Cluster;
import com.aerospike.client.sdk.DataSet;
import com.aerospike.client.sdk.RecordResult;
import com.aerospike.client.sdk.RecordStream;
import com.aerospike.client.sdk.ResultCode;
import com.aerospike.client.sdk.Session;
import com.aerospike.client.sdk.policy.Behavior;
import java.util.Map;
import java.util.Optional;
public class SessionStore {
private final Session session;
private final DataSet sessions;
private final int ttlSeconds;
public SessionStore(Cluster cluster, int ttlSeconds) {
this.session = cluster.createSession(Behavior.DEFAULT);
this.sessions = DataSet.of("test", "sessions");
this.ttlSeconds = ttlSeconds;
}
public void save(String sessionId, Map<String, Object> data) {
try {
session.upsert(sessions.id(sessionId))
.bin("data").setTo(data)
.expireRecordAfterSeconds(ttlSeconds)
.execute();
} catch (AerospikeException e) {
// Some local/dev server policies disallow TTL updates (FailForbidden).
if (e.getResultCode() != ResultCode.FAIL_FORBIDDEN) {
throw e;
}
session.upsert(sessions.id(sessionId))
.bin("data").setTo(data)
.execute();
}
}
public Optional<Map<String, Object>> get(String sessionId) {
RecordStream stream = session.query(sessions.id(sessionId)).execute();
Optional<Map<String, Object>> out = stream.getFirst()
.filter(RecordResult::isOk)
.map(RecordResult::recordOrThrow)
.map(r -> (Map<String, Object>) r.bins.get("data"));
stream.close();
return out;
}
public void delete(String sessionId) {
session.delete(sessions.id(sessionId)).execute();
}
public void touch(String sessionId) {
try {
session.touch(sessions.id(sessionId))
.expireRecordAfterSeconds(ttlSeconds)
.execute();
} catch (AerospikeException e) {
if (e.getResultCode() != ResultCode.FAIL_FORBIDDEN) {
throw e;
}
}
}
}

Usage

import com.aerospike.client.sdk.Cluster;
import com.aerospike.client.sdk.ClusterDefinition;
import java.util.Map;
import java.util.Optional;
try (Cluster cluster = new ClusterDefinition("localhost", 3000).connect()) {
SessionStore store = new SessionStore(cluster, 3600); // 1 hour TTL
// Save session
store.save("sess-abc123", Map.of(
"userId", "user-1",
"role", "admin",
"loginTime", System.currentTimeMillis()
));
// Get session
Optional<Map<String, Object>> session = store.get("sess-abc123");
System.out.println("Loaded session: " + session.orElse(Map.of()));
// Extend session on activity
store.touch("sess-abc123");
// Logout
store.delete("sess-abc123");
}

Complete example

import com.aerospike.client.sdk.AerospikeException;
import com.aerospike.client.sdk.Cluster;
import com.aerospike.client.sdk.ClusterDefinition;
import com.aerospike.client.sdk.DataSet;
import com.aerospike.client.sdk.RecordResult;
import com.aerospike.client.sdk.RecordStream;
import com.aerospike.client.sdk.ResultCode;
import com.aerospike.client.sdk.Session;
import com.aerospike.client.sdk.policy.Behavior;
import java.util.Map;
import java.util.Optional;
public class SessionStoreExample {
static class SessionStore {
private final Session session;
private final DataSet sessions;
private final int ttlSeconds;
SessionStore(Cluster cluster, int ttlSeconds) {
this.session = cluster.createSession(Behavior.DEFAULT);
this.sessions = DataSet.of("test", "sessions");
this.ttlSeconds = ttlSeconds;
}
void save(String sessionId, Map<String, Object> data) {
try {
session.upsert(sessions.id(sessionId))
.bin("data").setTo(data)
.expireRecordAfterSeconds(ttlSeconds)
.execute();
} catch (AerospikeException e) {
// Some local/dev server policies disallow TTL updates (FailForbidden).
if (e.getResultCode() != ResultCode.FAIL_FORBIDDEN) {
throw e;
}
session.upsert(sessions.id(sessionId))
.bin("data").setTo(data)
.execute();
}
}
Optional<Map<String, Object>> get(String sessionId) {
RecordStream stream = session.query(sessions.id(sessionId)).execute();
try {
return stream.getFirst()
.filter(RecordResult::isOk)
.map(RecordResult::recordOrThrow)
.map(r -> (Map<String, Object>) r.bins.get("data"));
} finally {
stream.close();
}
}
void touch(String sessionId) {
try {
session.touch(sessions.id(sessionId))
.expireRecordAfterSeconds(ttlSeconds)
.execute();
} catch (AerospikeException e) {
if (e.getResultCode() != ResultCode.FAIL_FORBIDDEN) {
throw e;
}
}
}
void delete(String sessionId) {
session.delete(sessions.id(sessionId)).execute();
}
}
public static void main(String[] args) {
try (Cluster cluster = new ClusterDefinition("localhost", 3000).connect()) {
SessionStore store = new SessionStore(cluster, 3600); // 1 hour TTL
String sessionId = "sess-complete-1";
store.save(sessionId, Map.of(
"userId", "user-1",
"role", "admin",
"loginTime", System.currentTimeMillis()
));
Optional<Map<String, Object>> loaded = store.get(sessionId);
System.out.println("Loaded session: " + loaded.orElse(Map.of()));
store.touch(sessionId);
store.delete(sessionId);
}
}
}

Expected output

Loaded session: {user_id=user-1, role=admin, login_time=<timestamp>}

Performance tips

  1. Use Behavior.DEFAULT as a safe starting point for session workloads
  2. Set appropriate TTL to auto-expire inactive sessions
  3. Use touch() to extend TTL on user activity without rewriting data

Next steps

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?