- Docs
- Data and storage
- Redis
Redis
Switch cache, queue, session, lock, and rate limit services from memory to Redis, with no code changes.
Most stateful services default to an in-memory adapter with a Redis twin: develop with no server, then switch configuration (not code) in production. Use Redis for any service two or more instances must share.
Quick start
Section titled “Quick start”Point each shared service at the same addr. cache, queue, and lock read flat keys; session, ratelimit, eventbus, and idempotency nest a redis block.
OPTIONS_ADDR=redis:6379OPTIONS_POOL_SIZE=20OPTIONS_MIN_IDLE_CONNS=2QUEUE_ADAPTER=redisQUEUE_ADDR=redis:6379LOCK_ADAPTER=redisLOCK_ADDR=redis:6379OPTIONS_REDIS_ADDR=redis:6379docker run -p 6379:6379 redis:7CACHE_ADAPTER=redis QUEUE_ADAPTER=redis go run ./cmd/myappserver listening on :8080 (cache=redis queue=redis)Env keys follow <SERVICE>_<FIELD>, matching ignores case and _, and on the flat services a url wins over addr. session, ratelimit, eventbus, and idempotency reject a malformed redis.addr in Options.Validate(); cache, queue, and lock ping Redis (3s limit) when first resolved, so a bad address fails on the first accessor call. Keep memory in tests: no Docker, and it passes goleak.VerifyTestMain. See each service in the config reference.
Resolve the services
Section titled “Resolve the services”import ( "github.com/zenta-dev/zever/config" "github.com/zenta-dev/zever/container")
c := container.New(config.Default())defer c.Close(ctx)
cache, err := c.Cache()q, err := c.Queue()locker, err := c.Lock()sess, err := c.Session()limiter, err := c.RateLimit()Register each adapter first (for example cacheredis.Register() from adapters/cache/redis), and check err after each call; accessors resolve lazily, cache, and retry after an error. c.Job() dispatches over the resolved Queue, and c.Scheduler() shares it.
Decide which services use Redis
Section titled “Decide which services use Redis”Switch per service; one service on Redis never drags the rest along.
| Service | Memory adapter | Redis role |
|---|---|---|
cache |
memory (LRU) |
Shared value store through typed codecs |
queue |
memory |
Topic messaging plus due and stale sweeps |
session |
memory |
Server-side session rows |
ratelimit |
memory (token bucket) |
Distributed counters (Lua) |
lock |
memory |
Distributed leases (default ttl 30s) |
idempotency |
memory |
Reserve-then-complete store |
eventbus |
memory (fan-out) |
Cross-process publish/subscribe |
One instance may stay on memory; with two or more, use Redis for session, lock, ratelimit, and queue, or users stick to one instance, leases split, counters diverge, and jobs duplicate.
Understand client ownership and behavior
Section titled “Understand client ownership and behavior”- Each adapter
Newowns its client;Closecloses only that one, andc.Close(ctx)dedups shared instances by pointer. queue/redissweeps due and stale claims (promoteDue,reclaimStale) at most every 250ms, adding up to 250ms recovery latency.ratelimitdenials return 429 JSON with a ceiled-secondsRetry-After; limiter errors fail open; keys must be non-empty, bounded, control-byte-free, and never echoed in errors.eventbuskeeps the memory envelope (id,payload,headers);SubscribeChanbuffers each subscriber, and slow consumers drop the newest message.cache/redisandsession/redisshare typed codecs, so keys and TTLs match the memory twins; Redis leases are distributed, memory locks process-local.- Size
pool_sizeandmin_idle_connsfor peak concurrency, and pass request contexts into cache, queue, and lock calls so Redis round trips observe deadlines. - Open without the container via
cache.Open,queue.Open, orlock.Open;Options.Validate()on the nested-redisservices rejects negative TTLs and oversize prefixes.
Troubleshooting
Section titled “Troubleshooting”Two instances serve different sessions
Cause: both run memory sessions. Fix: point both at the same Redis addr.
Rate limits diverge across replicas
Cause: memory token buckets are per process. Fix: set ratelimit.adapter: redis.
Jobs run twice after a crash
Cause: the Redis sweep reclaims stale claims within ~250ms. Fix: make handlers idempotent, keyed by job ID.
Startup rejects the address
Cause: session, ratelimit, eventbus, or idempotency found a malformed redis.addr (a scheme or path), or cache, queue, or lock could not ping Redis. Fix: use host:port; on the flat services url wins over addr.
Shutdown hangs on Redis clients
Cause: the parent context has no deadline and a client blocks (each service then gets a 5s bound). Fix: pass a timeout context to c.Close(ctx); overruns report CloseTimeoutError, panics ClosePanicError, and failures join with errors.Join.