How to read these patterns
The scenarios below are illustrative patterns, not case studies of named users. We describe the shape of a problem, explain why Aeron Cache’s design fits it, name the specific features and transport you would reach for, and sketch real code or config using actual API and CLI names. Treat them as starting points: the same primitives — key-value and counter caches, per-item TTL, JSON merge patch, streaming subscriptions, and four transports — recombine to cover a wide range of low-latency, high-availability workloads.
Each pattern ends with the deep dive that explains the mechanism underneath it. If you have not run the cache yet, start at Getting Started and skim embedded clients first.
1. Real-time market-data / pricing fan-out
The scenario. A pricing service computes ticks for thousands of instruments and must fan them out to many downstream consumers — risk engines, order routers, dashboards — with the smallest possible delay between a write and a consumer seeing it. Most ticks change only a field or two of a larger quote object.
Why Aeron Cache fits. Writers put or patch quote objects; subscribers receive only the deltas, not full snapshots on every tick. Patch-mode subscriptions stream PATCH_ITEM events carrying just the changed fields, which keeps payloads small on the hot path. For the lowest latency, consumers take the deltas over the native Aeron SBE transport.
Reach for. JSON Merge Patch writes, patch-mode keyed subscriptions, and the Aeron SBE gateway (or bidi WebSocket for non-JVM/Rust consumers).
// Publisher side: patch only the fields that moved this tick.
client.patchItem("quotes", "EURUSD", "{\"bid\": 1.0873, \"ts\": 1739472000123}");
// Consumer side (Aeron SBE transport, Java): subscribe in PATCH mode and react to deltas.
AeronGatewayClient gw = AeronGatewayClient.connect(aeronDir, "127.0.0.1");
gw.awaitConnected(10, TimeUnit.SECONDS);
// subscribe(cacheId, sendSnapshot, mode, key, listener); key = null means all keys.
gw.subscribe("quotes", false, SubscriptionMode.PATCH, null, event -> {
// event.getEventType() == PATCH_ITEM; getItemValue() holds only the changed fields
applyTick(event.getItemKey(), event.getItemValue());
});
See streaming subscriptions and Aeron + SBE transport.
2. Feature flags / dynamic config distribution
The scenario. A platform team rolls out feature flags and runtime config to a fleet of services. Changes must propagate in near real time, individual services must read flags with zero latency on the request path, and a change usually flips one nested field of a config document.
Why Aeron Cache fits. Store each config as a JSON object; toggle a flag with a merge patch that touches one field and leaves the rest intact (null deletes a field). Each service runs an embedded object cache that subscribes once, deep-merges incoming PATCH_ITEM deltas into its local copy, and serves reads from memory with no round-trip.
Reach for. EmbeddedObjectCache (deep-merge mirror), JSON merge patch, patch-mode streaming over WebSocket.
// Service startup: hold a local, deep-merging mirror of the config cache.
const client = new AeronCacheClient("http://localhost:7070", "ws://localhost:7071");
const flags = client.getObjectCache("feature-flags");
// Hydrate once, then deep-merge PATCH_ITEM deltas into the local copy.
flags.subscribe(() => {}, undefined, undefined, true, undefined, "patch");
// Reads are local — no network round-trip.
if (flags.getLocal("checkout")?.newFlow === true) {
useNewCheckout();
}
// Elsewhere, an operator flips one field; every mirror deep-merges the delta.
await client.patchItem("feature-flags", "checkout", JSON.stringify({ newFlow: true }));
See JSON Merge Patch.
3. Session / presence store with TTL
The scenario. A web tier needs a shared session and presence store: entries should expire automatically after inactivity, but an active user’s session should be extended rather than recreated. Presence (“who is online”) must be visible across all nodes.
Why Aeron Cache fits. Timed entries expire via cluster timers — which run on replicated cluster time, not a single node’s wall clock — so expiry is consistent across the cluster and survives failover. To keep an active session alive, cancel its scheduled removal with cancel-removal instead of rewriting the value.
Reach for. Timed entries (insert-timed / putTimedItem), cancel-removal, and a whole-cache subscription for presence fan-out.
# Create a session with a 30-minute TTL (ms).
aeron-cache create sessions
aeron-cache insert-timed sessions "sess-8f2a" '{"user":"alice","node":"web-3"}' 1800000
# User is active again — extend the session by cancelling its scheduled removal.
aeron-cache cancel-removal sessions "sess-8f2a"
TTL semantics and cluster timers are covered in RAFT consensus; the presence stream in streaming subscriptions.
4. Leaderboards / rate-limiting / metrics (counters)
The scenario. You need fast, atomic integer state: a leaderboard scoreboard, a per-client rate-limit budget, or rolling metrics counters. Updates are frequent and often arrive in bursts that should be applied together.
Why Aeron Cache fits. Counter caches store 64-bit integers with atomic increment, decrement, and set. Because the cache is a deterministic replicated state machine, counter arithmetic is consistent across the cluster. Bulk operations let you apply many increments — and even mix cache and counter ops — in a single atomic request, each op carrying its own requestId echoed on the response.
Reach for. Counter caches (increment-counter / decrement-counter / set-counter), bulk ops, timed counters for windowed limits.
# Rate-limit budget per client, refilled per window.
aeron-cache create-counter-cache rate-limits
aeron-cache set-counter rate-limits "client-42" 100
# Spend one unit per request (returns the resulting value).
aeron-cache decrement-counter rate-limits "client-42" 1
// Bulk: apply a batch of leaderboard increments atomically (HTTP POST /api/v1/cache/bulkops).
{
"requestId": "batch-1",
"operations": [
{ "operationType": "INCREMENT_COUNTER", "cacheId": "scores", "key": "alice", "counterValue": 10, "requestId": "op-1" },
{ "operationType": "INCREMENT_COUNTER", "cacheId": "scores", "key": "bob", "counterValue": 5, "requestId": "op-2" }
]
}
See RAFT consensus for why replicated counters stay consistent.
5. Edge / near caching for read-heavy services
The scenario. A read-heavy service — product catalog, entitlements, reference data — reads the same keys far more often than they change. Every read that crosses the network is latency you are paying repeatedly for data you already had.
Why Aeron Cache fits. The near cache subscribes and keeps a local read-ahead copy of a cache’s keys, so GETs resolve locally. The embedded cache pattern goes further: a local mirror (EmbeddedAeronCache for strings, EmbeddedObjectCache for JSON, EmbeddedCounterCache for int64) stays in sync via the stream, so reads never leave the process while writes still flow through the cluster.
Reach for. Near cache (read-ahead) and the embedded cache clients; a subscribe with sendSnapshot to hydrate the mirror on start.
# Read-heavy service: hold a local mirror, read with no round-trip.
async def main():
client = AeronCacheClient("http://localhost:7070", "ws://localhost:7071")
catalog = client.get_cache("catalog") # EmbeddedAeronCache
await catalog.subscribe(lambda e: None, hydrate=True) # live, hydrated mirror
# Local read — served from the in-process mirror, no network round-trip.
sku = catalog.get_local("sku-1001")
asyncio.run(main())
See streaming subscriptions for how mirrors stay live, and embedded clients for the embedded client APIs.
6. HA control-plane state that must survive node loss
The scenario. A control plane holds state that cannot be lost on a node failure — leader assignments, cluster membership, job ownership, routing tables. You need strong consistency and a clean recovery story, not a best-effort cache.
Why Aeron Cache fits. The cache is a ClusteredService on Aeron Cluster: a deterministic replicated state machine driven by a RAFT-replicated log, with periodic snapshots for fast recovery. The default deployment is a 3-node StatefulSet. If a node dies, the remaining nodes retain committed state and a new leader continues from the replicated log; a restarted node rebuilds from the latest snapshot plus the log tail.
Reach for. RAFT clustered mode (CACHE_MODE=RAFT, the default), snapshots, and the HTTP cluster-tools endpoints for operational snapshotting.
# Deploy the clustered backend on Kubernetes (3-node StatefulSet) via Helm.
make all
# from k8s/helm/:
make install-all
# Trigger a snapshot operationally (HTTP cluster tools).
curl -X POST http://localhost:7070/api/v1/snapshot
The consensus model, cluster timers, and snapshot/recovery flow are the subject of RAFT consensus. For the low-GC design that keeps the replicated state machine predictable under load, see low-GC design.
Each pattern reuses the same handful of primitives. Once you are comfortable with one, the others are mostly a matter of choosing the right transport and whether you want deltas, a local mirror, or both. Continue to the API overview for exact endpoints, or embedded clients to pick a language.