Scale: Redis-backed rate limiting + presence TTLs#76
Merged
Conversation
Harden the multi-process (REDIS_URL) path so the things that were per-process become correct cluster-wide. Rate limiting: - registerRateLimit now takes an optional Redis URL. With Redis, the per-IP fixed window is a shared INCR+PEXPIRE counter (PTTL for the reset header), so the limit holds across every process behind a load balancer. A Redis hiccup fails OPEN — a limiter outage must never take the API down. In-memory limiter unchanged when Redis is absent. Presence durability: - The Redis roster moves from a plain hash to a sorted set scored by expiry (member = cid) plus a cid→identity hash, with each process heartbeating its own members to push their expiry forward. If a process dies without a graceful leave, its members simply age out — no ghosts in the roster. members() evicts expired entries on read. (Redis 7.0 has no per-field hash TTL, hence the zset.) TTL/heartbeat are configurable for tests. Verification: 62/62 SQLite (in-memory limiter path unchanged), 44/44 Postgres 16, and the Redis suite (3/3): cross-process fan-out, a shared rate-limit window that blocks across two instances, and a crashed process's presence aging out. Docs updated (.env.example, README, /docs).
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.
What
Next-roadmap item 2/2 (operator side). Hardens the multi-process (
REDIS_URL) path so two things that were per-process become correct cluster-wide: per-IP rate limiting and the presence roster.How
Rate limiting (
src/rate-limit.ts)registerRateLimitnow takes an optional Redis URL. With Redis, the per-IP fixed window is a sharedINCR+PEXPIREcounter (PTTLfor the reset header), so the limit holds across every process behind a load balancer. Fails open on a Redis error — a limiter outage must never take the API down. The in-memory limiter is unchanged when Redis is absent.Presence durability (
src/modules/realtime/transport.ts)cid) + acid → identityhash, with each process heartbeating its own members to push their expiry forward. If a process dies without a graceful leave, its members simply age out — no ghosts in the roster;members()evicts expired entries on read. (Redis 7.0 has no per-field hash TTL, hence the zset.) TTL/heartbeat are configurable, defaulting to 30s/10s.Neither changes the API, and the single-process default is untouched.
Verification
npm run test:redis, against a realredis-server): cross-process fan-out; a shared rate-limit window where the 4th request is429even though it lands on the second instance (proving the counter is shared); and a "crashed" process's presence member aging out of the roster after its TTL.verifyjob already runstest:redisagainst theredis:7service. Docs updated (.env.example, README,/docs).This completes the two next-roadmap items we lined up (public realtime #75 + this). Happy to keep going down the roadmap — schemas, cursor pagination,
/metrics+/ready, orCREATE INDEX CONCURRENTLYare the next candidates.https://claude.ai/code/session_018efxvWw3MRjdtvE5xgBqya
Generated by Claude Code