|
2 | 2 |
|
3 | 3 | All notable changes to Docker Dash are documented here. |
4 | 4 |
|
| 5 | +## [6.17.0] - 2026-04-22 — "HA mode preview — Redis-backed rate limiter + cluster foundation" |
| 6 | + |
| 7 | +**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. |
| 8 | + |
| 9 | +Full background and architecture: [`plans/research-ha-mode-optional.md`](plans/research-ha-mode-optional.md) + [`plans/deep-spec-ha-mode.md`](plans/deep-spec-ha-mode.md) (local/gitignored). |
| 10 | + |
| 11 | +### Added — `src/services/cluster.js` HA abstraction |
| 12 | + |
| 13 | +New service module ([`src/services/cluster.js`](src/services/cluster.js)) that every HA-eligible subsystem imports. Standalone mode: every method is a cheap no-op or falls through to in-process state (zero runtime overhead). HA mode: lazy-connects to Redis via `REDIS_URL`. |
| 14 | + |
| 15 | +Public API: |
| 16 | +- `cluster.isHa()` / `cluster.nodeId()` — mode introspection |
| 17 | +- `cluster.redis()` — ioredis client in HA, null in standalone |
| 18 | +- `cluster.rateLimitTick(key, maxReqs, windowMs)` — returns `{ allowed, remaining, retryAfterSec }` |
| 19 | +- `cluster.publish(ch, payload)` / `cluster.subscribe(ch, handler)` — stubbed in v6.17.0, wired in v7.0.0-alpha.1 |
| 20 | +- `cluster.isLeader()` — returns `true` in v6.17.0 (stub), real election in v7.0.0-rc.1 |
| 21 | + |
| 22 | +### Added — Redis-backed rate limiter |
| 23 | + |
| 24 | +Extracted the existing in-memory `Map`-based limiter into `src/services/rate-limiter-memory.js` (sliding window, same semantics as before). New HA path in `cluster.rateLimitTick` uses Redis `INCR` + `PEXPIRE` (fixed window — 2× looser at bucket boundaries, documented trade-off in `docs/features/ha-mode.md` §"Rate-limiter semantics"). |
| 25 | + |
| 26 | +`src/middleware/rateLimit.js` rewritten to delegate. **Fail-open on Redis errors** — a mid-request Redis outage lets the request through with a `warn` log, prioritizing availability over strict quota. |
| 27 | + |
| 28 | +### Added — `docker-compose --profile ha` + `redis:7-alpine` service |
| 29 | + |
| 30 | +Opt-in HA profile in [`docker-compose.yml`](docker-compose.yml): |
| 31 | +```bash |
| 32 | +docker compose --profile ha up -d |
| 33 | +# Then .env: DD_MODE=ha, REDIS_URL=redis://redis:6379 |
| 34 | +``` |
| 35 | + |
| 36 | +Redis configured with: |
| 37 | +- `--save 60 1000` — snapshot persistence on ≥1000 writes / 60s |
| 38 | +- `--maxmemory 128mb --maxmemory-policy allkeys-lru` — hard cap |
| 39 | +- `no-new-privileges:true` — matches the rest of the compose security posture |
| 40 | +- No exposed ports — only reachable via the Docker network |
| 41 | + |
| 42 | +### Added — Tests (23 new, all pass via `ioredis-mock` — no real Redis needed) |
| 43 | + |
| 44 | +- [`src/__tests__/rate-limiter-memory.test.js`](src/__tests__/rate-limiter-memory.test.js) — 9 tests covering sliding-window semantics, key isolation, expiration, cleanup |
| 45 | +- [`src/__tests__/cluster.test.js`](src/__tests__/cluster.test.js) — 14 tests: 8 standalone (all methods no-op correctly) + 6 HA (Redis path via `jest.doMock('ioredis')` → `ioredis-mock`) |
| 46 | + |
| 47 | +**Test suite: 843/55 → 866/57.** |
| 48 | + |
| 49 | +### Added — `docs/features/ha-mode.md` |
| 50 | + |
| 51 | +Operator reference. Covers: what HA changes, enabling, architecture, Redis keys, rate-limiter semantics, failure modes, monitoring, when NOT to use HA mode, rollback procedure. |
| 52 | + |
| 53 | +### Changed — Dependencies |
| 54 | + |
| 55 | +- `ioredis ^5.10.1` added as **`optionalDependencies`** (not `dependencies`). Standalone installs don't pull it. |
| 56 | +- `ioredis-mock ^8.13.1` added as `devDependencies` for unit tests. |
| 57 | +- `npm audit` clean (0 vulnerabilities). |
| 58 | + |
| 59 | +### ⚠️ v6.17.0 Preview Limitations (loudly documented) |
| 60 | + |
| 61 | +**Don't run multi-replica in HA mode yet.** Every replica runs every cron job → duplicate daily backups, concurrent `VACUUM` (DB corruption risk), N× certificate scans, N× secret rotation checks. This is fixed in v7.0.0-rc.1 via leader election. |
| 62 | + |
| 63 | +**WS broadcasts still per-replica.** User connected to replica A misses events emitted by replica B. Fixed in v7.0.0-alpha.1 via Redis pub/sub. |
| 64 | + |
| 65 | +Single-replica HA mode today is only useful for operational drill — wiring sticky-session load balancers, Prometheus scrape of Redis, Grafana dashboards — before rolling out true multi-replica in v7.0. |
| 66 | + |
| 67 | +### BACKLOG F30 — partial close |
| 68 | + |
| 69 | +Shipped: cluster abstraction + Redis rate limiter + `--profile ha` + docs. |
| 70 | +Remaining for v7.0: WS pub/sub (v7.0.0-alpha.1), cron leader election (v7.0.0-rc.1), failover runbook (v7.0.0 stable). |
| 71 | + |
| 72 | +### Rollback |
| 73 | + |
| 74 | +Single-commit revert. `ioredis` becomes an unused `optionalDependencies` entry (harmless). `--profile ha` becomes a no-op profile. |
| 75 | + |
| 76 | +### Production readiness |
| 77 | + |
| 78 | +Unchanged at 9.7/10 this release. v6.17.0 is about enabling a new deployment mode for enterprise users, not about closing residual standalone gaps. Scorecard moves only when v7.0 stable lands with real multi-replica support + failover tests. |
| 79 | + |
| 80 | +### Files touched |
| 81 | + |
| 82 | +- `src/services/cluster.js` (new, ~110 LOC) |
| 83 | +- `src/services/rate-limiter-memory.js` (new, ~50 LOC — extracted from middleware) |
| 84 | +- `src/middleware/rateLimit.js` — rewritten to delegate via cluster (~45 LOC, was ~55) |
| 85 | +- `src/__tests__/cluster.test.js` (new, 14 tests) |
| 86 | +- `src/__tests__/rate-limiter-memory.test.js` (new, 9 tests) |
| 87 | +- `docker-compose.yml` — `redis:7-alpine` service behind `--profile ha` |
| 88 | +- `docs/features/ha-mode.md` (new) |
| 89 | +- `package.json` — `ioredis` → `optionalDependencies`, `ioredis-mock` → `devDependencies` |
| 90 | +- `BACKLOG.md` — F30 updated with partial-close status |
| 91 | +- `README.md` / `SECURITY.md` / `CONTRIBUTING.md` — test counts + new Feature Reference link |
| 92 | + |
| 93 | +### Tests |
| 94 | + |
| 95 | +- **866 passing + 4 skipped / 57 suites** |
| 96 | +- Lint: 0 warnings / 0 errors |
| 97 | +- `npm audit`: 0 vulnerabilities |
| 98 | + |
| 99 | +--- |
| 100 | + |
5 | 101 | ## [6.16.1] - 2026-04-22 — "Testing 8.5 → 9.5, Documentation 9 → 9.5 (production readiness 9.5 → 9.7)" |
6 | 102 |
|
7 | 103 | Pure test + docs release. No runtime code changes. Closes two of the three remaining gaps to 10/10 production readiness. |
|
0 commit comments