Redis Production Configuration Guide
Overview
Section titled “Overview”Redis is an optional, soft dependency in OmniRoute — the application degrades gracefully (in-memory fallbacks) when Redis is unavailable. In production, tuning Redis reduces latency for four distinct workloads:
| Workload | Driver | Client Factory | Key Pattern |
|---|---|---|---|
| Rate limiting | rateLimiter.ts |
getRedisClient() — lazy ioredis singleton |
<prefix>rl:* Lua‑atomic rate limit windows |
| Auth cache | apiKeys.ts |
Reuses rateLimiter’s client |
<prefix>auth:api_key:<sha256> with TTL |
| Quota store | redisQuotaStore.ts |
Separate getRedisClient(url) singleton |
<prefix>quota:* configurable per-instance |
| Warmup circuit breaker | redisCircuitBreakerStore.ts |
Separate client in circuitBreakerFactory.ts |
<prefix>warmup:cb:<connectionId> |
All four workloads share one namespace prefix so OmniRoute can co-exist with other apps on a
single Redis instance (e.g. 127.0.0.1:6379). See Key Namespacing.
Current Configuration (Code Defaults)
Section titled “Current Configuration (Code Defaults)”| Setting | Value | Where |
|---|---|---|
REDIS_URL env var |
redis://redis:6379 (compose), optional |
rateLimiter.ts:5, .env.example |
REDIS_KEY_PREFIX env var |
omniroute: (default) |
rateLimiter.ts, redisQuotaStore.ts, redisCircuitBreakerStore.ts, .env.example |
QUOTA_STORE_REDIS_URL env var |
separate, can differ from REDIS_URL |
quota/storeFactory.ts |
QUOTA_STORE_DRIVER |
"sqlite" (default), "redis" optional |
quota/storeFactory.ts |
ioredis maxRetriesPerRequest |
3 |
rateLimiter.ts client creation |
enableReadyCheck |
not set (ioredis default: true) |
— |
lazyConnect |
not set (ioredis default: false) |
— |
retryStrategy |
not set (ioredis default: 200ms base, exponential) | — |
| TLS / password / DB index | not configured | — |
| Sentinel / Cluster | not configured — standalone single-node only | — |
Key Namespacing
Section titled “Key Namespacing”OmniRoute shares a Redis instance with whatever else runs on the host. Without a namespace,
keys like auth:api_key:<sha256> or rl:* could collide with keys from other applications
using the same Redis (this instance runs Redis on 127.0.0.1:6379 alongside other services).
Set REDIS_KEY_PREFIX to a non-empty string to prefix every OmniRoute key:
# .env — all OmniRoute keys become omniroute:rl:*, omniroute:auth:*, omniroute:quota:*, omniroute:warmup:cb:*REDIS_KEY_PREFIX=omniroute:- Default:
omniroute:(applied whenREDIS_KEY_PREFIXis unset or blank). - Applied to: rate limiter + auth cache (shared
ioredisclient viakeyPrefix) and the quota store (KEY_PREFIX = "${REDIS_KEY_PREFIX}quota") and the warmup circuit breaker (KEY_PREFIX = "${REDIS_KEY_PREFIX}warmup:cb:"). - Changing the prefix when keys already exist in Redis orphans the old keys (they expire
via TTL / LRU). Safe to change; no migration needed. The one exception is a warmup
circuit-breaker key for a connection marked forbidden: it is persisted without a TTL, so
list leftovers with
redis-cli --scan --pattern '<old-prefix>warmup:cb:*'and delete them. - ioredis
keyPrefixautomatically prepends the prefix on writes and strips it on reads, so application code never sees the prefix.
Recommended Production Tuning
Section titled “Recommended Production Tuning”1. Connection Pool / Client Options (ioredis Redis constructor)
Section titled “1. Connection Pool / Client Options (ioredis Redis constructor)”The current code creates a single new Redis(url) with no custom options. For production
multi‑replica deployments, pass a client factory in the code or wrap getRedisClient():
const redis = new Redis(REDIS_URL, { maxRetriesPerRequest: null, // no retry limit; let retryStrategy decide enableReadyCheck: true, // verify server is ready before accepting calls lazyConnect: true, // don't connect on construction; wait for first call retryStrategy: (times) => { if (times > 10) return null; // give up after 10 retries → reconnect later return Math.min(times * 200, 5000); // 200ms, 400ms, …, 5s cap }, enableAutoPipelining: true, // coalesce concurrent commands into one TCP write keepAlive: 10000, // TCP keep‑alive every 10s});Key trade-offs:
maxRetriesPerRequest: null+retryStrategy— preferred for production so transient Redis restarts don’t immediately fail every request. The in-memory fallback incheckRateLimit()absorbs the failure path.lazyConnect: true— avoids a startup dependency on Redis being up before the server begins accepting connections.enableAutoPipelining: true— reduces round-trips for concurrent rate-limit checks; beneficial at >50 RPS on a single connection.
2. Redis Server Configuration (redis.conf)
Section titled “2. Redis Server Configuration (redis.conf)”# Memorymaxmemory 80% # leave room for OS page cachemaxmemory-policy allkeys-lru # evict stale auth cache entries under pressure
# Persistence (optional — OmniRoute is crash‑safe without it)save 300 1 # snapshot at least every 5 min if ≥1 key changedappendonly no # AOF not needed; data is regeneratableappendfsync no # no fsync overhead (RDB is sufficient)
# Networkingtimeout 0 # no idle disconnecttcp-keepalive 300 # 5 min keep‑alivetcp-backlog 511 # connection backlog for bursty load
# Performancehz 10 # default; 100 for latency‑sensitiveactivedefrag yes # auto‑defragment when fragmentation >10%Trade-off for maxmemory-policy allkeys-lru: Auth cache entries may be evicted under
memory pressure. This is safe — setCachedApiKey always re-populates on miss, and the
SQLite fallback is authoritative. The rate-limiter Lua script creates small keys that are
short-lived by design.
3. Docker Compose Settings
Section titled “3. Docker Compose Settings”The prod compose (docker-compose.prod.yml) uses redis:8.6.2-alpine. Add:
redis: image: redis:8.6.2-alpine command: [ "redis-server", "--maxmemory", "512mb", "--maxmemory-policy", "allkeys-lru", "--activedefrag", "yes", "--save", "300 1", ] healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 3s retries: 3 start_period: 5s4. Multi‑Instance / Scaling Considerations
Section titled “4. Multi‑Instance / Scaling Considerations”Single Redis for all replicas — the rate-limiter Lua script depends on a single authoritative key space. Multiple Redis instances behind replicas would lose atomicity and double the budget. Use a single Redis (or Redis Sentinel cluster with failover) for all application replicas.
Connection count: Each application replica opens 2 TCP connections to Redis (rate limiter client + quota store client). At 10 replicas → 20 connections, well within a default Redis instance’s 10k connection ceiling.
5. Monitoring
Section titled “5. Monitoring”Expose via health-check endpoint:
// src/app/api/monitoring/health/route.ts already calls rateLimiter functions// Add Redis-specific checks:// 1. PING latency via ioredis .ping()// 2. Memory usage via INFO memory// 3. Connection count via INFO clients// 4. Hit rate for maxmemory-policy (evicted_keys / keyspace_hits)Key metrics to watch:
- Evicted keys / sec — if persistently non-zero, increase
maxmemory - Blocked clients — non-zero suggests slow Lua scripts or high contention
- Rejected connections — connection limit hit; rare at 20 connections
Architecture Diagram
Section titled “Architecture Diagram”flowchart LR subgraph App["App Replica"] RL[rateLimiter.ts] AK[apiKeys.ts] QS[redisQuotaStore.ts] end RL -- "REDIS_URL" --> R1[(Redis\nshared)] AK -- "reuses RL's client" --> R1 QS -- "QUOTA_STORE_REDIS_URL" --> R2[(Redis\nquota store)] R1 --> R2 -- "can be same instance" --> R1References
Section titled “References”| File | Purpose |
|---|---|
src/shared/utils/rateLimiter.ts |
Primary Redis client, Lua rate-limit script, in-memory fallback |
src/lib/db/apiKeys.ts |
Auth cache — Redis→SQLite fallback |
src/lib/quota/redisQuotaStore.ts |
Separate Redis client for optional quota store |
src/lib/quota/storeFactory.ts |
Switches between sqlite and redis quota drivers |
docker-compose.prod.yml |
Prod Redis container (image redis:8.6.2-alpine) |
.env.example |
Redis env vars documentation |
src/app/api/local/redis/ |
API routes for dev container orchestration |
bin/cli/commands/redis.mjs |
CLI commands for dev container orchestration |
HagiCode
HagiCode is an agentic coding workspace: structured workflows, multi-agent execution, and Hero Dungeon views turn ideas into shipped software.
Turn ideas into polished, usable software with a smarter, faster, and more enjoyable agentic coding workflow.

- SmartStructured workflows turn intent into an executable path from idea to shipped change.
- EfficientMulti-agent workflows keep research, implementation, and review moving in parallel.
- FunHero Dungeon interfaces make long coding sessions visual, collaborative, and rewarding.