feat: fan out socket.io events across instances via Redis adapter - #352
Merged
Conversation
Multiple API replicas each run an isolated in-memory socket.io server, so an event emitted on one instance never reaches clients connected to the others — live updates silently break and the UI only refreshes manually. Add an opt-in Redis adapter: when REDIS_URL is set, all instances share a Redis pub/sub backplane so emits fan out to every connected client. Without REDIS_URL the default in-memory adapter is used, so single-instance and local setups are unchanged.
pashidlos
requested changes
Jul 17, 2026
pashidlos
left a comment
Member
There was a problem hiding this comment.
Requested changes
1. Docker / CI
- Add a
redisservice todocker-compose.yml - Pass
REDIS_URL=redis://redis:6379intoapi depends_on+ healthcheck so acceptance (and the compose-backed job) actually boot the Redis adapter path
2. Env example
- Document
REDIS_URLin.env(same style as other optional vars):
# Multi-instance socket.io fan-out (optional). Unset = in-memory adapter.
# REDIS_URL=redis://localhost:63793. Tests
- Unit:
RedisIoAdapter— connect wirescreateAdapter,createIOServerattaches it; error handler / failed connect if practical with mockedredis+@socket.io/redis-adapter - E2E or acceptance: with
REDIS_URLset (compose Redis), assert API comes up and a basic socket emit/receive still works- Cross-replica fan-out is optional follow-up (needs 2 API replicas)
Addresses review feedback on the socket.io Redis adapter: - docker-compose: add a `redis` service (with healthcheck) and pass `REDIS_URL=redis://redis:6379` to `api` via `depends_on`, so the acceptance run actually boots the Redis adapter path - .env: document the optional `REDIS_URL` variable - unit test (RedisIoAdapter): verifies connect wires `createAdapter`, `createIOServer` attaches it, error handlers log instead of crashing, and a failed connect rejects - acceptance test: with `REDIS_URL` set, a socket.io client connects to the running API Error handlers and startup logging were already part of the original PR.
Collaborator
Author
|
Addressed in aa4678b 🙌 1. Docker / CI
2. Env example
3. Tests
Error handlers and startup logging were already in the original PR ( |
Move the REDIS_URL bootstrap wiring out of main.ts (which unit tests can't import) into a static RedisIoAdapter.use(app, url), so the enable/skip logic is covered by unit tests. This raises coverage on new code above the quality gate. main.ts now just calls the helper.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Problem
EventsGatewayuses a plain in-memory socket.io server (this.server.emit(...)). With a single instance that reaches every client, but in a multi-replica deployment each replica has its own isolated socket server:emitonly reaches clients on B.(In production this also shows up as repeated
400 Bad Requeston/socket.io/?...&transport=polling&sid=...— the polling handshake session created on one replica isn't known to the next replica the load balancer picks.)Change
Add an opt-in Redis adapter (
@socket.io/redis-adapter):REDIS_URLis set, all instances share a Redis pub/sub backplane, so events emitted on any instance fan out to clients connected to every instance.REDIS_URLis not set, the default in-memory adapter is used — single-instance and local setups are completely unchanged.New files/deps:
src/redis-io.adapter.ts—RedisIoAdapter(extends NestIoAdapter, attaches the Redis adapter increateIOServer).src/main.ts— wires the adapter behind theREDIS_URLguard.@socket.io/redis-adapter,redis.Deployment notes (infra, alongside this change)
For multi-replica correctness the Redis adapter is necessary but not sufficient — the reverse proxy also needs:
ip_hash) so the polling handshake stays on one replica, and/or/socket.io/(proxy_http_version 1.1+Upgrade/Connectionheaders).Testing
npm run build, lint and unit/e2e tests pass.REDIS_URL(in-memory adapter path).