Step 4
The five roles of Redis
25 min
The five roles of Redis
Redis is often asked "what is it?" for good reason. It plays five distinct parts.
1. Cache
async function getUser(id: number) {
const cached = await redis.get(`user:${id}`);
if (cached) return JSON.parse(cached);
const u = await db.query("SELECT * FROM users WHERE id=$1", [id]);
await redis.setex(`user:${id}`, 300, JSON.stringify(u));
return u;
}
TTL is mandatory.
2. Sessions
const sid = crypto.randomUUID();
await redis.setex(`sess:${sid}`, 3600, JSON.stringify({ userId, role }));
res.cookies.set("session", sid, { httpOnly: true, maxAge: 3600 });
Easy invalidation via DEL; every request hits Redis.
3. Rate limit
const bucket = Math.floor(Date.now() / 60000);
const k = `rl:${ip}:${bucket}`;
const count = await redis.incr(k);
if (count === 1) await redis.expire(k, 120);
if (count > 60) return 429;
4. Pub/Sub
redis.publish("notifications", JSON.stringify({ userId, message }));
const sub = redis.duplicate();
await sub.subscribe("notifications");
sub.on("message", (_, raw) => { /* broadcast via WS */ });
Fan-out events to multiple app instances.
5. Distributed lock
async function withLock(key: string, ttl: number, fn: () => Promise<void>) {
const acquired = await redis.set(`lock:${key}`, "1", "EX", ttl, "NX");
if (!acquired) throw new Error("locked");
try { await fn(); } finally { await redis.del(`lock:${key}`); }
}
6. Separating instances (optional)
redis-cache— lossy OKredis-session— losing logs users outredis-queue— job queue
Start with one for MVP.
7. Data structures
| Type | Use |
|---|---|
| String | cache, counters |
| Hash | field-level object updates |
| List | queue, recent-N |
| Set | unique values (likes, follows) |
| Sorted Set | rankings, timelines |
| Stream | event log (light Kafka alternative) |
8. Memory
maxmemory 2gb
maxmemory-policy allkeys-lru
For cache, allkeys-lru evicts the least recently used.
9. Gotchas
- Missing TTL → memory growth
- Large values → network/memory waste. Put big blobs in S3.
- No pool → per-request connect is slow (ioredis handles pool)
- Pub/Sub treated as persistent queue → messages dropped
10. When you don't need Redis
- Small traffic, DB is fast enough
- Single server (no locks / pub-sub required)
- Next.js
unstable_cache+ ISR replaces cache
Add Redis when measured bottlenecks demand it.
Closing
Treating Redis as "a fast DB" wastes memory. Pick the role and match the data structure.
Next
- 05-three-layer-cache