Redis is volatile with TTL. Source of truth for money, stock and permission: Postgres (postgres). Queues and retries: background-jobs (same Redis, different key contract).
Only introduce Redis if the product already has it (or the brief asks for it). Do not cache to “be fast” without hit rate and a clear key.
Non-negotiable rules
- Every key has a prefix (
app:env:…) and TTL (or an explicit persistence policy, rare). - Cached value is derived. Invalidate or use a short TTL on the canonical write.
- Do not store PII/secrets without a short TTL and without going to the log.
GETmiss → compute →SETwith TTL. Stampede:SET NX/ short lock / singleflight in the app.- Tenant/user in the key. Not a global
cache:users.
What it is for (and is not for)
| Use | Ok | No |
|---|---|---|
| Read cache | HTML fragment, idempotent GET, session | Balance, “already paid”, business unique |
| Lock | Single job, short idempotency | Replacing unique in Postgres |
| Rate limit | Auth, expensive APIs (api-design) |
Sole defense of authz |
| Session | If the stack already uses a Redis store | Mirroring the entire user without expiring |
Bull/Sidekiq broker: the lib's keys. Do not mix cache: with bull: / queue:.
Keys and TTL
{app}:{env}:{tenant}:{resource}:{id}:{variant}
- Stable serialize (canonical JSON). Version in the key if the shape changes (
:v2). - TTL aligned with freshness (seconds to hours). “Forever” only with invalidation on write and runbook.
- Eviction: accept miss. Do not treat Redis down as a 500 on a cacheable page — degrade to origin.
Stampede and invalidation
- Hot key: lock
SET lock:… NX EX 5or coalescing in the process. - Write: delete/update the known keys in the same unit of work as the DB (after commit). Do not delete
*. - Null cache: cache “does not exist” with a short TTL for hammered 404s.
Rate limit and lock
- Sliding/fixed window or token bucket — whatever the repo already has.
- Response
429+Retry-After(api-design). Fail closed on auth, fail open on page cache if the product already works that way. - Lock: random value +
DELonly if the value matches (Lua/compare). TTL on the lock always.
Ops
- Image pinned in compose (
docker-compose). AOF/RDB persistence only if the use is session/queue, not pure cache. - ACL/password; not Redis open on the internet.
- Observe hit ratio, evictions, memory.
KEYS *forbidden in prod (SCAN).
Anti-patterns
- Redis as the order/payment database
- Cache without TTL “we'll invalidate later”
- Stampede: 2k
SELECTidentical on miss FLUSHALLon deploy- Caching user A's authenticated response under a key without user id
- Using the same key for cache and for job payload
Integration with other skills
- SQL origin:
postgres+ repo ORM - Workers:
background-jobs - HTTP 429:
api-design - Auth session store:
authentication-authorization - Go-live:
production-readiness
Completion criteria
- Prefixed key + TTL; tenancy in the key
- Invalidation on write or honest TTL
- Miss/stampede handled; Redis down doesn't corrupt money
- Queue ≠ cache (namespaces)
- No eternal PII