Configuration

Configuration guide

Every Aeron Cache configuration option in one place: the Homebrew all-in-one install's backend.env, the environment variables behind them, and the Helm chart values for Kubernetes.

How configuration works

Aeron Cache is configured with environment variables. Everything else is a delivery mechanism for them:

You run it withWhere you set optionsGood for
Homebrew (all-in-one)~/.aeron-cache/backend.envLocal desktop use and trying things out
Kubernetes / Helmvalues.yaml per chart, rendered into a ConfigMapClusters, staging and production
From sourceThe .env files in the repo’s config/ directoryDevelopment

The same option has the same name everywhere. cacheMode in a Helm chart becomes CACHE_MODE in the pod, and is the CACHE_MODE line in backend.env. So once you know an option from one place, you know it in all of them.

If you have not installed anything yet, start with Getting started.

Pick a deployment mode

The biggest decision is which shape you deploy. It sets CACHE_MODE and decides which other options matter.

ModeCACHE_MODENodesReplicatedUse it for
All-in-oneRAFT1, everything in one processSingle-node RAFTLaptops, demos, local development
ClusterRAFT3 (StatefulSet), plus separate interface appsYes, via RAFTProduction, high availability
EphemeralSINGLE1No, no consensusPure caching where losing state on restart is fine

All three expose the same command surface and the same APIs, so moving from one to another does not change your client code.

Homebrew (all-in-one)

brew tap bhf/aeroncache
brew install aeron-cache
aeron-cache

On first run a default config is written to ~/.aeron-cache/backend.env. Edit it and restart aeron-cache. Lines are plain NAME=value.

Choosing interfaces

The REST API and cluster tools are always on. The streaming gateways are switchable:

VariableDefaultWhat it controls
WEBSOCKET_GATEWAY_ENABLEDtrueWebSocket gateway (uni- and bidirectional streaming)
SSE_GATEWAY_ENABLEDtrueServer-Sent Events gateway
AERON_GATEWAY_ENABLEDfalseNative Aeron SBE gateway
GATEWAY_TRANSPORT_MEDIAudpudp or ipc. Only read when the Aeron gateway is enabled

A disabled gateway is not started at all, so the matching UI streaming features will not connect.

For example, to enable the Aeron gateway over IPC and turn SSE off:

AERON_GATEWAY_ENABLED=true
GATEWAY_TRANSPORT_MEDIA=ipc
SSE_GATEWAY_ENABLED=false

Near cache

An optional read-ahead cache inside the HTTP, WebSocket (including the bidirectional socket), SSE and Aeron gateway interfaces. When on, the interface subscribes to the cluster, keeps a warm local copy of the caches it touches, and serves reads from it. It is transparent to clients (same URIs, no client change); see the API page for what is served from it and the management endpoints.

VariableDefaultWhat it controls
NEAR_CACHE_ENABLEDfalsetrue serves HTTP reads and WebSocket/SSE/bidi/Aeron-gateway hydration snapshots from the near cache. Read by each interface, so set it for the ones you want

Because it is read-ahead, a read of a warm key can briefly lag a concurrent write. Leave it off if you need strongly consistent reads.

Other all-in-one options

VariableDefaultWhat it controls
CACHE_MODERAFTRAFT (replicated) or SINGLE (ephemeral)
DYNAMIC_CACHE_CREATIONfalseWhether caches can be created on the fly
MONOLITH_EMBEDDED_DRIVERtruetrue starts an embedded Aeron media driver. false attaches to an external aeronmd already running at AERON_DIR

JVM options and heap size

To pass JVM flags such as a larger heap, set JAVA_TOOL_OPTIONS in backend.env:

JAVA_TOOL_OPTIONS=-Xmx4g

Several flags go in one value, separated by spaces, for example JAVA_TOOL_OPTIONS=-Xms4g -Xmx4g. Restart aeron-cache after editing.

  • Why this works: the launcher exports every line of backend.env as an environment variable before starting Java, and the JVM reads JAVA_TOOL_OPTIONS by itself.
  • Not JAVA_OPTS: the launcher sets that variable itself, so a value in backend.env is overwritten.
  • Not command-line arguments: aeron-cache <args> passes them to the application, not to the JVM.
  • Default: with no flag, the JVM sizes the heap at a quarter of physical RAM.
  • Check it applied: the JVM logs Picked up JAVA_TOOL_OPTIONS: ... at the top of ~/.aeron-cache/aeron-cache.log.
  • Shell variable: a JAVA_TOOL_OPTIONS in your shell is replaced by the value in backend.env.
  • Native memory: Aeron’s term buffers live outside the heap, so a larger -Xmx does not change them. See term length.

Core options (all deployments)

These are shared by the cluster, interface apps and all-in-one install.

VariableHelm valueDefaultWhat it controls
CACHE_MODEcacheModeRAFT (SINGLE for ephemeral)Replicated RAFT cluster, or single unreplicated node
DYNAMIC_CACHE_CREATIONn/afalseAllow caches to be created dynamically
CLUSTER_ADDRESSESclusterAddressesthe three StatefulSet pod DNS namesComma-separated hosts of the cluster nodes
CORS_ALLOWED_ORIGINScorsAllowedOriginslocalhost ports 3000 to 3005Browser origins allowed to call the API, for example the UI
AERON_CACHE_TERM_LENGTHaeronCacheTermLength128m in the charts (application default 64k)Aeron term buffer length for cache data channels. See below
IDLE_STRATEGYidleStrategybackoffHow agent and Aeron threads wait when idle. See below

Term length and maximum value size

The maximum Aeron message size is termLength / 8. The charts set 128m so values up to roughly 16 MB work. Leave it blank to fall back to the application default of 64k, which suits small values only. The buffers are sparse, so the resident memory cost of a larger term length is small.

Idle strategy: latency versus CPU

The idle strategy decides what a thread does when it has no work. It is the main latency-versus-CPU dial.

ValueBehaviourTrade-off
backoff (default)Spins, then yields, then parksBalanced. Good default
spinBusy-spinsLowest latency, burns a full core per thread
yieldYields the CPULower CPU than spin, slightly more jitter
noopNever waitsBurns CPU. Testing only
sleep-ns[:nanos]Sleeps for a period, for example sleep-ns:1000Low CPU, higher latency
sleep-ms[:millis]Sleeps for milliseconds, for example sleep-ms:1Lowest CPU, highest latency
a class nameA fully qualified IdleStrategy class with a no-arg constructorCustom

IDLE_STRATEGY applies to every role. To tune one role, set IDLE_STRATEGY_<ROLE>, which takes priority over the global value. For example the HTTP app reads IDLE_STRATEGY_BLOCKING_PUBLISHER, IDLE_STRATEGY_CLUSTER_CLIENT_AGENT, IDLE_STRATEGY_CLUSTER_MESSAGE_PUBLISHER, IDLE_STRATEGY_AGENT_RUNNER and IDLE_STRATEGY_UNCLUSTERED. An invalid value fails fast at startup rather than when the first agent is created.

Snapshots and log purging

A RAFT cluster appends every command to a log. Snapshots let a node restart quickly, and purging discards log history older than the retained snapshots. The on-demand snapshot-and-purge endpoint always works. The options below control the scheduled run.

VariableHelm value (snapshot.*)DefaultWhat it controls
SNAPSHOT_SCHEDULE_ENABLEDscheduleEnabledfalseTurn the scheduled snapshot and purge on
SNAPSHOT_INTERVALintervalPT6HPeriod for the ALWAYS strategy (ISO-8601 duration)
SNAPSHOT_RETENTION_COUNTretentionCount2Recent snapshots whose log history is kept
SNAPSHOT_CHECK_STRATEGYcheck.strategyALWAYSWhich check gates each scheduled run. See below
SNAPSHOT_CHECK_POLL_INTERVALcheck.pollIntervalPT5MHow often the two conditional strategies are evaluated
SNAPSHOT_CHECK_STATE_CHANGE_THRESHOLDcheck.stateChangeThreshold10000State-changing operations needed before snapshotting
SNAPSHOT_CHECK_ARCHIVE_GROWTH_BYTEScheck.archiveGrowthBytes268435456 (256 MiB)Archive growth needed before snapshotting

Choose the check strategy by what you want to avoid:

SNAPSHOT_CHECK_STRATEGYSnapshots whenPick it when
ALWAYSEvery SNAPSHOT_INTERVALYou want a simple, predictable schedule
CACHE_STATE_CHANGEEnough state-changing operations have happened since the last snapshotTraffic is bursty and idle periods should not snapshot
RECORDING_GROWTHThe archive has grown enough since the last purgeYou are bounding disk use

Scheduling only applies to clustered (RAFT) runs where CLUSTER_FOLDER is set. In SINGLE mode the options are inert. Durations use ISO-8601 form, for example PT30M for 30 minutes and PT6H for 6 hours.

Kubernetes and Helm

The charts live in k8s/helm/ in the main repository. Each component is its own chart, and the Makefile there wires them together.

git clone https://github.com/bhf/aeron-cache
cd aeron-cache/
make all                  # build images into Minikube
cd k8s/helm/
make install-all          # cluster + interfaces + UI

Which chart do I need?

ChartWhat it deploysDefault replicas
aeroncache-clusterThe RAFT cluster nodes, with a clustertools sidecar3
aeroncache-ephemeralA single, unreplicated node (CACHE_MODE=SINGLE)1
aeroncache-monolithCluster node plus all interfaces in one pod1
aeroncache-httpREST interface (port 7070)1
aeroncache-wsWebSocket interface1
aeroncache-sseSSE interface1
aeroncache-gatewayNative Aeron SBE gateway (ports 7075 and 7076)1
aeroncache-http-clustertoolsCluster tools over HTTP1
aeroncache-ui-nextjsWeb UI1
aeroncache-observabilityPrometheus and Grafanan/a

Near cache in the charts

Near caching is a switch on the interface charts, not a separate chart (the old aeroncache-http-near chart and image have been removed).

ChartValueDefault
aeroncache-httpnearCacheEnabledtrue
aeroncache-wsnearCacheEnabledfalse
aeroncache-ssenearCacheEnabledfalse
aeroncache-gatewaynearCacheEnabledfalse

Each sets NEAR_CACHE_ENABLED on the container. For example: helm upgrade --install aeroncache-ws ./aeroncache-ws --set nearCacheEnabled=true. Each replica holds its own copy of the caches it has warmed, so it costs memory per replica.

Install targets

All targets run from k8s/helm/, and every install-* has a matching uninstall-*.

make install-backend              # cluster + HTTP, WS, SSE, gateway
make install-frontend             # UI only
make install-all                  # backend + UI
make install-backend-ephemeral    # single-node backend
make install-all-ephemeral        # single-node backend + UI
make install-monolith-all         # all-in-one: node + gateways + UI
make uninstall-all                # tear down

Individual components work too, for example make install-http or make install-ui.

Make flags

Flags go on the end of any install target.

FlagEffect
EXTERNAL_MEDIA_DRIVER=trueRun the Aeron C media driver (aeronmd) as a sidecar instead of embedding the Java driver
GHCR=truePull released images from ghcr.io instead of using images built into Minikube
GHCR_TAG=1.2.3With GHCR=true, pin a specific release
JAVAAGENT=trueEnable the OpenTelemetry Java agent on the interface apps so they serve Prometheus metrics
make install-all GHCR=true GHCR_TAG=1.2.3
make install-backend-ephemeral GHCR=true EXTERNAL_MEDIA_DRIVER=true

Common values

These appear in most charts. Defaults below are from the chart values.yaml files.

ValueDefaultWhat it controls
replicaCount3 for cluster, 1 otherwisePod count. Keep the cluster at an odd number for RAFT quorum
cacheModeRAFT (SINGLE for ephemeral)See core options
idleStrategybackoffSee idle strategy
aeronCacheTermLength"128m"See term length
image.repository, image.tag, image.pullPolicylocal image, latest, NeverWhich image to run. Never suits Minikube. Use IfNotPresent or Always with a registry
service.typeClusterIP (cluster), LoadBalancer (interfaces, all-in-one)How the service is exposed
ingress.enabledfalseCreate an Ingress, with className, annotations, hosts and tls
resources{}CPU and memory requests and limits. Unset by default, so set them for production
autoscaling.enabledfalseHorizontal pod autoscaling on interface apps, with min/max replicas and a CPU target
nodeSelector, tolerations, affinityemptyStandard pod placement

Cluster chart specifics

ValueDefaultWhat it controls
persistence.enabledtruePersist cluster state on a volume. Sets CACHE_DATA_DIR
persistence.size1GiVolume size
persistence.storageClass""Storage class. Empty uses the cluster default
persistence.mountPath/dataWhere the volume is mounted
service.clusterIPNoneHeadless service, so pods get stable DNS names
service.publishNotReadyAddressestrueRequired. RAFT nodes must resolve their peers before they are Ready, so without this the nodes crash-loop at bootstrap
clustertools.port7080Port for the sidecar serving liveness, readiness and Prometheus metrics
clustertools.snapshot.*see snapshotsScheduled snapshot and purge

The cluster needs /dev/shm for Aeron’s shared memory. The chart mounts a 1 GiB in-memory emptyDir there by default. Raise sizeLimit if you raise term lengths or run many streams.

All-in-one chart specifics

ValueDefaultWhat it controls
websocketEnabledtrueWebSocket gateway
sseEnabledtrueSSE gateway
aeronGatewayEnabledfalseAeron SBE gateway
gatewayTransportMedia"udp"udp or ipc
ports.http / ws / sse / clusterTools7070 / 7071 / 7072 / 7080Listener ports
corsAllowedOriginslocalhost portsAllowed browser origins

External media driver

By default each node embeds a Java media driver. Setting externalMediaDriver.enabled=true runs the C aeronmd driver as a sidecar and attaches the node to it as a pure client (LAUNCH_EMBEDDED=false).

ValueDefaultWhat it controls
externalMediaDriver.enabledfalseUse the C media driver sidecar
externalMediaDriver.aeronDir/dev/shm/aeronAeron directory shared by driver and node over the shm volume
externalMediaDriver.threadingModeSHAREDSHARED, SHARED_NETWORK or DEDICATED
externalMediaDriver.extraEnv[]Extra AERON_* variables for the driver, for example driver timeout or idle strategies
externalMediaDriver.resources{}Resources for the driver container

Build the driver image with make build-media-driver, which is part of make build-backend.

Observability

Deploy Prometheus and Grafana with make install-observability. It installs into the aeroncache namespace and ships a pre-provisioned “Aeron Cache - Interface Apps” dashboard covering JVM memory, CPU, GC and HTTP request rate and latency.

There are two sources of metrics:

  • Clustertools sidecar. Cluster and ephemeral pods carry prometheus.io/* annotations, and the sidecar serves the shared media driver counters on /prometheus at port 7080. This works with no extra flag.
  • OpenTelemetry Java agent. Optional and off by default. Pass JAVAAGENT=true to an interface-app install target, or set javaagent.enabled=true, and the app serves metrics on javaagent.metricsPort (default 9464) at javaagent.metricsPath (default /metrics). Traces and logs exporters default to none.

Sending telemetry to an external collector (OTLP)

The agent is the standard OpenTelemetry Java agent, so it reads the standard OTEL_* environment variables. The chart sets a few of them for you and passes anything in javaagent.extraEnv straight through, which is how you point it at your own collector.

ValueDefaultWhat it controls
javaagent.serviceNamechart fullnameSets OTEL_SERVICE_NAME
javaagent.tracesExporternoneSets OTEL_TRACES_EXPORTER. Use otlp to ship traces
javaagent.logsExporternoneSets OTEL_LOGS_EXPORTER. Use otlp to ship logs
javaagent.extraEnv[]Extra environment variables, such as the endpoint and protocol

To ship traces and logs to a collector while keeping the Prometheus metrics endpoint:

# values override for an interface app, e.g. aeroncache-http
javaagent:
  enabled: true
  serviceName: aeroncache-http
  tracesExporter: otlp
  logsExporter: otlp
  extraEnv:
    - name: OTEL_EXPORTER_OTLP_ENDPOINT
      value: http://otel-collector.observability.svc.cluster.local:4318
    - name: OTEL_EXPORTER_OTLP_PROTOCOL
      value: http/protobuf

Use port 4317 with grpc instead if your collector expects gRPC. Per-signal variables such as OTEL_EXPORTER_OTLP_TRACES_ENDPOINT and OTEL_EXPORTER_OTLP_HEADERS (for authenticated backends) work the same way. If the endpoint is unreachable, the agent logs export failures, so leave tracesExporter and logsExporter at none until the collector is up.

The metrics exporter is fixed to prometheus by the chart. To push metrics over OTLP as well, add OTEL_METRICS_EXPORTER to extraEnv with the value otlp or prometheus,otlp. It is appended after the chart’s own entry, so it takes precedence, but check the rendered pod spec with helm template before relying on it.

This applies to the interface apps only. The clustertools sidecar serves Prometheus metrics on port 7080 and is not configured through these values. Scrape it, or have your collector scrape it with a Prometheus receiver.

Overriding values

Use standard Helm. Either edit the chart’s environments/values.dev.yaml, or pass overrides:

helm upgrade --install aeroncache-cluster ./aeroncache-cluster \
  --namespace aeroncache \
  --set idleStrategy=backoff \
  --set persistence.size=10Gi \
  --set clustertools.snapshot.scheduleEnabled=true \
  --set clustertools.snapshot.check.strategy=RECORDING_GROWTH

Recipes

Local, minimal

Install with Homebrew and leave everything at its defaults. You get REST, WebSocket and SSE, plus the UI.

Local, native Aeron access

# ~/.aeron-cache/backend.env
AERON_GATEWAY_ENABLED=true
GATEWAY_TRANSPORT_MEDIA=ipc

IPC works when the client runs on the same machine. Use udp when it does not.

Cluster with bounded disk

# values override for aeroncache-cluster
persistence:
  size: 20Gi
clustertools:
  snapshot:
    scheduleEnabled: true
    retentionCount: 2
    check:
      strategy: RECORDING_GROWTH
      archiveGrowthBytes: 536870912   # 512 MiB

Cluster tuned for latency

idleStrategy: "spin"        # a full core per thread, so give the pods dedicated CPU
externalMediaDriver:
  enabled: true
  threadingMode: DEDICATED
resources:
  requests: { cpu: "4", memory: 4Gi }
  limits:   { cpu: "4", memory: 4Gi }

Measure before and after. Spinning trades CPU for latency, and the right answer depends on your hardware and load. See Low-GC design for the design reasoning.

Large values

Keep aeronCacheTermLength: "128m" for values up to about 16 MB. Leaving it blank drops back to 64k, which is far smaller.

Troubleshooting

SymptomLikely cause
Cluster pods crash-loop at startupservice.publishNotReadyAddresses was turned off. Nodes cannot resolve their peers
UI shows no streamingWEBSOCKET_GATEWAY_ENABLED or SSE_GATEWAY_ENABLED is false
Browser calls fail with a CORS errorThe UI’s origin is not in CORS_ALLOWED_ORIGINS
Brew install runs out of heapSet JAVA_TOOL_OPTIONS=-Xmx... in backend.env. JAVA_OPTS is ignored
Large values are rejectedAERON_CACHE_TERM_LENGTH is too small. Max message size is an eighth of it
Scheduled snapshots never runSNAPSHOT_SCHEDULE_ENABLED is false, the mode is SINGLE, or CLUSTER_FOLDER is unset
Pod will not start with an external driverThe shm volume is missing or aeronDir differs between driver and node
A GET returns a slightly stale value after a writeNEAR_CACHE_ENABLED=true: warm reads are eventually consistent. Unset it for strongly consistent reads
Pod fails on image pullimage.pullPolicy is Never but the image is not in the cluster. Build it, or use GHCR=true

Where the source of truth lives

Defaults can change between releases. The main repository is authoritative: the config/*.env files for environment variables, and k8s/helm/<chart>/values.yaml for every Helm option, each annotated with comments.