Cluster Mode

A single-node AuraBoot is fine for development and small deployments. Cluster mode targets production workloads where availability, throughput, and rolling upgrades matter. The platform is stateless at the application layer, so horizontal scaling is the default growth path.
Topology
+-----------------+
| Load Balancer |
+--------+--------+
|
+------------------+------------------+
| | |
+----v----+ +----v----+ +----v----+
| app-1 | | app-2 | | app-3 | (stateless JVMs)
+----+----+ +----+----+ +----+----+
| | |
+----+------------------+------------------+----+
| |
+--v-----+ +---------+ +---------+ +---------v-+
| Redis | | Kafka | | MinIO | | Postgres |
| (lock+ | | (event | | (files) | | primary + |
| cache) | | bus) | | | | standby) |
+--------+ +---------+ +---------+ +-----------+Application nodes hold no durable state. All shared state lives in Redis, Kafka, PostgreSQL, and MinIO.
Shared infrastructure
| Component | Role | Notes |
|---|---|---|
| Redis | Distributed lock (SET NX EX, ~3-minute lease by default), cache, rate limits | Needed for multi-node deployment; falls back to a JVM-local lock (single-instance safe only) when unavailable |
| Kafka | Message queue (one option for aura.mq.type: domain events, webhook dispatch, crawler and other background work) | For multi-node, choose Kafka / Redis / RabbitMQ (the default memory is single-node only) |
| PostgreSQL | Primary write node + one or more streaming standbys | Application targets primary; standbys for read-heavy analytics |
| MinIO | S3-compatible object storage | Run a 4-node cluster for production durability |
When to use
- Production traffic above what one JVM can serve.
- High availability requirements (zero-downtime rolling upgrade).
- Long-running background work (crawler, automation) that must survive node restart.
- Tenants on the same physical install needing isolation; see Tenancy.
How it works
Stateless application nodes share work via the message queue and coordinate exclusive operations via Redis locks. The PostgreSQL primary is the single write authority; read replicas exist for reporting only. A health probe at /actuator/health is wired to the load balancer; when draining a node, remove it from the load balancer first and wait for in-flight requests to bleed off before stopping it.
Session affinity is optional. Tokens are stateless JWTs, so requests can land on any node. SSE / WebSocket connections do benefit from sticky routing because the chokepoint ConversationTurnService keeps a ResponseSink registered on the node that owns the open stream.
Configuration
spring:
# Redis is optional — set the SPRING_DATA_REDIS_HOST env var to enable it (required for multi-node).
# When unset, the app runs with a local lock + in-process data sync (single-instance safe only).
data:
redis:
host: "${REDIS_HOST:localhost}"
port: "${REDIS_PORT:6379}"
datasource:
url: "jdbc:postgresql://${PG_PRIMARY}:5432/aura_boot"
hikari:
maximum-pool-size: "${HIKARI_MAX_POOL_SIZE:30}"
aura:
mq:
type: kafka # memory | redis | kafka | rabbitmq
kafka:
bootstrap-servers: "${KAFKA_BOOTSTRAP:localhost:9092}"
consumer-group: aura-group
scheduler:
engine: "${AURA_SCHEDULER_ENGINE:local}" # local (database-backed TaskScheduler) | xxlHorizontal scale knobs:
| Knob | Default | When to raise |
|---|---|---|
| App replica count | 1 | Latency p95 climbs under load |
HikariCP maximum-pool-size per node | 30 | DB pg_stat_activity shows queueing |
| Kafka partition count per topic | 6 | Consumer lag grows |
| Crawler worker count | 1 | Frontier admit rate exceeds fetch throughput |
| Redis maxmemory | 2 GB | Eviction events appear |
Rolling upgrade
# For each node, in order:
# 1) Remove the node from the load balancer backend pool first (exact command depends on your LB)
sleep 20 # drain SSE / commands
ssh "$NODE" 'systemctl restart auraboot'
curl -sS "https://$NODE/actuator/health" | jq -e '.status == "UP"' # put it back in the LB once healthyCoordinate with the load balancer so only one node is drained at a time.
Verify
- All app nodes return
UPon/actuator/healthand serve traffic. - A lock-protected resource is held by exactly one node at any instant (no double execution).
- Message-queue consumer lag for webhook delivery and crawler background work is bounded.
- Killing one app node does not produce user-visible errors after the drain window.
- PostgreSQL standby replication lag stays under one second under normal load.