The middleware rate limiter (middleware.ts) keeps counts in a per-process in-memory Map. That's correct for a single replica (and documented as such next to railway.toml), but across >1 replica each process has its own counters — the effective limit multiplies by replica count, and counts reset on every cold start.
Introduce a small store interface with the current in-memory implementation as the default, plus an optional Redis/Upstash-backed implementation selected by env, so horizontal deploys keep a single global limit.
Acceptance
- A
RateLimitStore interface; in-memory default behaves exactly as today.
- Optional Redis impl behind an env var (e.g.
RATE_LIMIT_REDIS_URL).
- Docs note when each applies.
Pointers
middleware.ts (the hits Map + rateLimited()).
The middleware rate limiter (
middleware.ts) keeps counts in a per-process in-memoryMap. That's correct for a single replica (and documented as such next torailway.toml), but across >1 replica each process has its own counters — the effective limit multiplies by replica count, and counts reset on every cold start.Introduce a small store interface with the current in-memory implementation as the default, plus an optional Redis/Upstash-backed implementation selected by env, so horizontal deploys keep a single global limit.
Acceptance
RateLimitStoreinterface; in-memory default behaves exactly as today.RATE_LIMIT_REDIS_URL).Pointers
middleware.ts(thehitsMap +rateLimited()).