You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
release: v6.17.1 — HA Phase 3 (WebSocket pub/sub via Redis)
Cross-replica WS events now work. User on replica A receives
events emitted by replica B through Redis pub/sub on single
channel ddash:pubsub.
cluster.js:
- Real publish/subscribe replacing v6.17.0 stubs
- Envelope = { nodeId, appChannel, payload }
- Subscriber filters self-echo by nodeId (loop-safe)
- Separate subscriber client (ioredis requires it)
- Best-effort publish, silent-drop on malformed JSON,
subscribe errors logged but never crash WS
ws/index.js:
- broadcast() + broadcastAll() now publish to Redis AND
deliver locally. New _localBroadcast helpers called
directly by publisher AND indirectly by cluster subscriber
when relaying from other replicas.
- New subscribe at attach() — cross-replica messages
routed to _localBroadcast without re-publishing.
Tests: 866 → 871 (+5 real behavior tests, replacing 1 stub):
- publish envelope + nodeId
- subscribe filters self-echo
- subscribe receives other-node messages
- channel routing
- multi-handler fan-out
- malformed envelope silent-drop
⚠️ Still DON'T run multi-replica in HA mode — this release
actually makes it WORSE (duplicate event streams × cross-replica
broadcast = 2× delivery). Fixed in v6.17.2 via leader election.
Lint 0/0. All 871 tests pass via ioredis-mock.
Cross-replica WebSocket events now work. User connected to replica A **now receives** events emitted by replica B (alerts, container state changes, log lines) through Redis pub/sub. Before this, multi-replica HA deploys had silent event delivery gaps.
8
+
9
+
### Implementation
10
+
11
+
[`src/services/cluster.js`](src/services/cluster.js) — replaced the v6.17.0 pub/sub stubs with a real implementation:
12
+
13
+
-**Single Redis channel**`ddash:pubsub` carries all application-level pub/sub traffic. App-level channel routing happens in the subscriber callback. Simpler than per-channel Redis subscriptions for the ~3-5 app channels we'll end up with.
14
+
-**Envelope** includes `{ nodeId, appChannel, payload }`. Subscriber filters out messages where `envelope.nodeId === NODE_ID` — prevents deliver-twice-locally loop when a replica publishes its own broadcasts.
15
+
-**Separate subscriber client** — ioredis requires the subscribe state to run on a dedicated connection (subscribers can't issue other commands). `_subClient` is lazy-connected on first `subscribe()` call; `_redis` (publisher) stays for `publish()` + all rate-limiter ops.
16
+
-**Best-effort publish** — errors logged + swallowed. Local delivery is the primary path; cross-replica is eventually-consistent. An unreachable Redis mid-message doesn't break WS for the publishing replica.
17
+
-**Malformed envelopes silently dropped** — a corrupted message on the shared channel must not crash the subscriber. Tested.
-`broadcast(type, data, channel)` now publishes to `ws:broadcast` on Redis AND delivers locally. Local delivery is immediate; cross-replica arrives within Redis's pub/sub latency (sub-ms on a healthy localhost Redis).
22
+
-`broadcastAll(type, data)` — same pattern.
23
+
- New `_localBroadcast` / `_localBroadcastAll` helpers — called directly by the publishing replica AND by the cluster subscriber when relaying from other replicas.
24
+
- New subscribe at `attach()`: `cluster.subscribe('ws:broadcast', payload → _localBroadcast…)`. Delivers cross-replica messages to local clients without re-publishing (loop-safe by the nodeId filter in cluster.js).
25
+
- Log line now shows cluster mode + nodeId: `WebSocket server attached { mode: 'standalone', nodeId: 'standalone' }` or `{ mode: 'ha', nodeId: '<uuid>' }`.
26
+
27
+
### Tests — 6 new cluster tests (871 total)
28
+
29
+
-`publish sends envelope with nodeId to Redis pub/sub channel` — spy on `redis.publish`, assert channel + envelope shape
30
+
-`subscribe filters out self-published messages` — loop-prevention
31
+
-`subscribe receives messages from OTHER node IDs` — cross-replica delivery (simulated foreign node via direct Redis publish with a different nodeId)
32
+
-`subscribe routes to the correct app channel` — routing logic
33
+
-`multiple handlers on the same channel all fire` — fan-out
34
+
-`malformed envelope JSON is silently dropped` — robustness
35
+
36
+
All 871 tests pass via `ioredis-mock` — still no real Redis required in CI.
37
+
38
+
### Still remaining for v7.0.0
39
+
40
+
-**v6.17.2** — Cron / SSH tunnel / Docker event stream **leader election** via Redis `SET NX PX`. Current limitation: running 2+ replicas in HA mode runs every cron job on every replica (duplicate backups, concurrent `VACUUM`). v6.17.1 **makes this worse** because WS events now propagate cross-replica, so duplicate Docker event stream in HA mode would deliver every event twice to connected users. **Don't run multi-replica yet.**
-**871 passing / 4 skipped / 57 suites** (was 866 / 57 in v6.17.0; +6 Phase 3 tests, test count unchanged from v6.17.0 by replacing 1 stub-assertion test with 6 real-behavior tests — net +5 actually, so 871 is correct).
**Opt-in HA** — closes BACKLOG F30 partially. `DD_MODE=ha` + Redis unlocks cross-replica rate limiting; the rest of the HA story (WS pub/sub, cron leader election) lands in v7.0.0. Standalone users: **zero impact** — default unchanged, `ioredis` is in `optionalDependencies` (not `dependencies`), no new env vars required.
**Zero dependencies to deploy** — just Docker. No external database, no Redis, no build step. Current version: **v6.17.0**
29
+
**Zero dependencies to deploy** — just Docker. No external database, no Redis, no build step. Current version: **v6.17.1**
30
30
31
-
**New in v6.17.0:** Optional HA mode via `DD_MODE=ha` + Redis. Preview only — Redis-backed rate limiter + cluster abstraction shipped; WS pub/sub + cron leader election land in v7.0. See [docs/features/ha-mode.md](docs/features/ha-mode.md).
31
+
**New in v6.17.x:** Optional HA mode via `DD_MODE=ha` + Redis. v6.17.0 shipped cluster abstraction + Redis rate limiter. **v6.17.1 adds cross-replica WS broadcasts via Redis pub/sub.** Cron leader election lands in v6.17.2 (real multi-replica safe). See [docs/features/ha-mode.md](docs/features/ha-mode.md).
title: 'HA Phase 3 — WebSocket pub/sub via Redis',
16
+
changes: [
17
+
{type: 'feature',text: 'Cross-replica WS broadcasts — user on replica A now receives events emitted by replica B (alerts, container state, log lines). Uses Redis pub/sub on a single channel ddash:pubsub. Before: silent event delivery gap in HA mode. Now: sub-millisecond cross-replica delivery on a healthy localhost Redis.'},
18
+
{type: 'improvement',text: 'Loop-safe by nodeId filter — publisher replica receives its own echo back and discards it. Local delivery still happens exactly once via direct _localBroadcast in the broadcast() call itself; cross-replica arrives via the subscriber callback.'},
19
+
{type: 'improvement',text: 'Separate subscriber Redis client (ioredis requires it — subscribed connections can\'t issue other commands). Lazy-connects on first subscribe. Standalone mode: still completely no-op, zero Redis connection.'},
{type: 'fix',text: '⚠️ DO NOT run multi-replica in HA mode yet. This release makes the situation WORSE than v6.17.0 because WS events now propagate cross-replica, so duplicate Docker event streams (one per replica) would deliver every event twice. Fixed in v6.17.2 via leader election on the event stream.'},
0 commit comments