Why start here
Aeron Cache is a low-latency, highly-available key-value and counter store built on Aeron, Agrona, and SBE. In production it runs as a RAFT-clustered replicated state machine; on your laptop it runs as a single-process monolith that bundles a cluster node, the HTTP/WebSocket/SSE interfaces, and a web UI. That monolith is all you need to go from nothing to a working cache in a few minutes — and everything you learn here maps one-to-one onto the clustered deployment.
By the end of this tutorial you will have the backend running locally, created your first cache, and written and read an item three ways — through the Rust CLI, raw HTTP, and a client library — plus touched counters and per-item TTL.
Prerequisites
- macOS or Linux with Homebrew.
- A terminal. For the HTTP walkthrough,
curl. For the client-library step, either a JDK 21+ (Java) or Node.js 18+ (TypeScript). - Ports 3000 (UI), 7070 (HTTP), and 7071 (WebSocket) free on localhost.
1. Install and run the monolith
Tap the formula and install. Running aeron-cache launches the monolith plus the UI.
brew tap bhf/aeron-cache
brew install aeron-cache
aeron-cache
On a successful start you will see the banner:
Initializing Aeron Cache...
___ __________ ____ _ __ _________ ________ _________
/ | / ____/ __ \/ __ \/ | / / / ____/ | / ____/ / / / ____/
/ /| | / __/ / /_/ / / / / |/ / / / / /| |/ / / /_/ / __/
/ ___ |/ /___/ _, _/ /_/ / /| / / /___/ ___ / /___/ __ / /___
/_/ |_/_____/_/ |_|\____/_/ |_/ \____/_/ |_\____/_/ /_/_____/
🌲 https://github.com/bhf/aeron-cache 🌲
✅ Aeron Cache is running!
UI : http://localhost:3000
Backend : PID 6221
Config : /home/user1/.aeron-cache
Log : /home/user1/.aeron-cache/aeron-cache.log
Press Ctrl+C to stop.
Verify it worked: open http://localhost:3000 — the web UI loads and connects to the backend.
Configuration
On first run, a default config is written to ~/.aeron-cache/backend.env. The HTTP REST interface is always on; the streaming gateways are toggled with environment variables:
| Interface | Variable | Default |
|---|---|---|
| WebSocket gateway | WEBSOCKET_GATEWAY_ENABLED | true |
| SSE gateway | SSE_GATEWAY_ENABLED | true |
| Aeron (SBE) gateway | AERON_GATEWAY_ENABLED | false |
A disabled gateway is never started, so the matching UI streaming features will not connect. To enable the native Aeron gateway over IPC and turn SSE off, for example, add to ~/.aeron-cache/backend.env and restart:
AERON_GATEWAY_ENABLED=true
GATEWAY_TRANSPORT_MEDIA=ipc
SSE_GATEWAY_ENABLED=false
Other useful knobs in the same file: MONOLITH_EMBEDDED_DRIVER (default true; set false to attach an external aeronmd), DYNAMIC_CACHE_CREATION (default false), and CACHE_MODE (default RAFT).
2. Create a cache and put/get an item — three ways
Caches are created explicitly. We’ll make one called quickstart, write the key greeting, and read it back.
(a) The Rust CLI
Install the CLI from its own tap (full details on CLI guide):
brew tap bhf/aeron-cache-cli
brew install aeron-cache-cli
aeron-cache create quickstart
aeron-cache insert quickstart greeting "hello aeron"
aeron-cache get quickstart greeting
Expected output from the final command:
greeting = hello aeron
Verify it worked: aeron-cache list-caches shows quickstart with an item count of at least 1.
(b) Raw HTTP with curl
The REST API lives under /api/v1 on port 7070. Create, put, then get:
curl -s -X POST http://localhost:7070/api/v1/cache \
-H 'Content-Type: application/json' \
-d '{"cacheId":"quickstart-http"}'
curl -s -X POST http://localhost:7070/api/v1/cache/quickstart-http \
-H 'Content-Type: application/json' \
-d '{"key":"greeting","value":"hello over http"}'
curl -s http://localhost:7070/api/v1/cache/quickstart-http/greeting
The final call returns:
{"cacheId":"quickstart-http","key":"greeting","value":"hello over http","operationStatus":"SUCCESS"}
Verify it worked: the response carries "operationStatus":"SUCCESS" and the value you wrote. Note that business outcomes ride in operationStatus (SUCCESS, UNKNOWN_CACHE, UNKNOWN_KEY, CACHE_EXISTS, …) rather than always surfacing as HTTP error codes.
(c) A client library
The embedded clients give you the same surface in Java, TypeScript, Python, and Rust — plus a local mirror for network-free reads (see embedded clients). Here it is in Java and TypeScript.
var baseUrl = "http://localhost:7070";
var wsUrl = "ws://localhost:7071";
AeronCacheClient client = new AeronCacheClient(baseUrl, wsUrl);
client.createCache("quickstart-java");
EmbeddedAeronCache cache = client.getCache("quickstart-java");
cache.subscribe(event -> {}, true); // hydrate a live local mirror
cache.put("greeting", "hello from java");
System.out.println(cache.getLocal("greeting")); // -> hello from java
const baseUrl = "http://localhost:7070";
const wsUrl = "ws://localhost:7071";
const client = new AeronCacheClient(baseUrl, wsUrl);
await client.createCache("quickstart-ts");
const cache = new EmbeddedAeronCache(client, "quickstart-ts");
cache.subscribe(() => {}, undefined, undefined, true); // hydrate a live local mirror
await cache.put("greeting", "hello from ts");
console.log(cache.getLocal("greeting")); // -> hello from ts (sync local read)
Verify it worked: getLocal returns the value you just wrote — served from the client’s in-memory mirror, kept in sync over the WebSocket stream.
3. Counters
Alongside string caches, Aeron Cache has dedicated counter caches whose values are 64-bit integers, with atomic increment / decrement / set. With the CLI:
aeron-cache create-counter-cache metrics
aeron-cache put-counter metrics requests 10
aeron-cache increment-counter metrics requests 5
aeron-cache get-counter metrics requests
Expected output from get-counter:
requests = 15
Verify it worked: the value is 15 (10 + 5). Omit the amount on increment-counter to step by 1.
4. Per-item TTL (and cancelling it)
Items can carry a time-to-live in milliseconds. The TTL schedules a removal timer that you can cancel to keep the entry alive — the basis for session/presence stores that extend on activity.
aeron-cache insert-timed sessions user:42 active 60000
aeron-cache cancel-removal sessions user:42
aeron-cache get sessions user:42
Verify it worked: after cancel-removal, get still returns active once the 60-second window has passed, because the scheduled removal was cancelled. You can inspect pending timers across all caches with aeron-cache stats and the library getTimers call.
Where to next
- embedded clients — the polyglot embedded clients: local mirrors, bulk ops, JSON merge patch, bidirectional WebSocket, and the native Aeron gateway.
- CLI guide — the full Rust CLI reference.
- streaming subscriptions — keyed and patch-mode subscriptions, and how the subscribe ack acts as a barrier.
- comparison — how Aeron Cache’s model lines up against other caches.
Takeaway: one brew install gives you the same API — HTTP, WebSocket, CLI, and libraries — that you’ll run in a RAFT cluster later. Nothing you built here has to change when you scale out.