Three things default to a single-process adapter. All three have a replica-safe
one; each is configuration rather than code, and none of them is on by default.
Only the rate limiter below was exercised across running replicas; the realtime
and event-bus claims are read from the adapters and their tests, not from a
topology brought up here.
Rate limiting needs a shared cache. A limit counted in one JVM is not a limit
when there are three. With Redis behind it the counter is shared — verified by
setting the limit to 5 across two replicas and sending twelve requests through
the load balancer:
/web/login 200 upstream=172.21.0.4
/web/login 200 upstream=172.21.0.4
/web/login 200 upstream=172.21.0.4
/web/login 200 upstream=172.21.0.4
/web/login 200 upstream=172.21.0.4
/web/login 429 upstream=172.21.0.4
...
/web/login 429 upstream=172.21.0.6 (1)
| 1 | A replica that had served nothing. Per-JVM counting would have answered
200 here; it answered 429 because the counter is in Redis. |
The application refuses to start with rate limiting enabled and no reachable
cache, which is the right failure — but :wagoe/cache ships under :inactive
in resources/conf/prod/config.edn. Move it to :active and point it at
Redis. That is an edit to your own config file; no environment variable does
it, which is why the compose reference deploys Redis but leaves
HTTP_RATE_LIMIT_ENABLED off.
Realtime pub/sub needs :provider :redis. Under the default :in-memory
provider a WebSocket connected to replica A does not see an event published on
replica B, and sticky sessions do not fix it — the publisher and the subscriber
are different connections. The Redis provider (BOU-85, ADR-035) fans routing
envelopes out over a pub/sub channel and keeps topic subscriptions in Redis
sets, so a broadcast reaches clients on any replica. Sockets stay node-local
under both providers; only the bus and the pub/sub manager differ.
Note what "configuration" means here: the monorepo’s wagoe.config/ig-config
does not emit :wagoe/realtime at all, so an application that wants realtime
wires the component itself against wagoe.realtime.shell.module-wiring. Getting
this wrong is quiet — a config with no :provider gets :in-memory, and the
single-node behaviour is a default rather than an error. Config shape is in that
namespace’s docstring.
The event bus needs :provider :redis-streams (BOU-93, libs/events) —
:redis-streams, not :redis, which is the cache’s and realtime’s spelling and
not this one’s. Redis Streams with consumer groups delivers an event once per
logical subscriber across replicas; the in-memory adapter delivers it only
within the publishing process.
Two ways this differs from realtime above. :wagoe/events is wired from
config — move it into :active and the component is emitted — though it is
absent from the shipped prod profile, so it is off until you add it. And it has
no default: any provider other than :redis-streams or :in-memory, including
none at all, throws Unknown event bus provider at boot. Loud rather than
quiet, which is the better of the two failures here.
Scheduled jobs are not on this list, contrary to the usual expectation.
Promotion of a due job is an atomic claim — ZREM on the scheduled sorted set,
and only the worker whose ZREM returns 1 owns it — so N workers polling
concurrently promote each due job exactly once (BOU-88). There is no recurring
or cron scheduler in the framework, so there is nothing that fires per process
at start-up either. Running more than one worker is safe; job handlers must be
idempotent for the ordinary at-least-once reason, not because of duplicate
scheduling.