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. Rich, convenient UI interactions make planning, agent execution, and project work approachable and easy to navigate, creating an engaging development experience.
Explore an engaging development experience through rich, convenient UI interactions.
- SmartOpenSpec workflows turn intent into an executable path, from proposal to design, tasks, and a shipped change.
- EfficientMulti-agent execution keeps research, implementation, and review moving in parallel, across several repositories and the Agent CLIs you already use.
- InterestingRich, convenient UI interactions make planning, agent execution, and project work approachable and easy to navigate.
Download for Windows
Share HagiCode
Turn ideas into shipped software.
HagiCode is an agentic coding workspace. OpenSpec workflows plan the change, multi-agent execution carries it out, and Hero Scenario makes the long run visible, so ideas become shipped software.
- Smart
- OpenSpec workflows turn intent into a path from proposal to shipped change.
- Efficient
- Multi-agent execution runs research, build, and review in parallel.
- Fun
- Rich UI interactions make planning, agent work, and projects easier to follow.
© 2026 HagiCode | Powered By hagilight-starlight@0.21.0 | Branch: main | Commit: de60bcd | Built:

