Redis — Practical
Redis — Practical patterns
Section titled “Redis — Practical patterns”Cache-aside in Node.js
Section titled “Cache-aside in Node.js”async function getUser(id: string) { const k = `user:${id}`; const cached = await redis.get(k); if (cached) return JSON.parse(cached); const u = await db.users.find(id); if (u) await redis.set(k, JSON.stringify(u), 'EX', 300); return u;}
async function updateUser(id: string, patch: any) { const u = await db.users.update(id, patch); await redis.del(`user:${id}`); return u;}Distributed lock
Section titled “Distributed lock”import crypto from 'crypto';
async function lock(key: string, ttl = 30) { const token = crypto.randomUUID(); const ok = await redis.set(`lock:${key}`, token, 'NX', 'EX', ttl); return ok ? token : null;}
const RELEASE = `if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1])else return 0end`;
async function unlock(key: string, token: string) { return redis.eval(RELEASE, 1, `lock:${key}`, token);}Token-bucket rate limiter (Lua)
Section titled “Token-bucket rate limiter (Lua)”local data = redis.call('HMGET', KEYS[1], 'tokens', 'ts')local tokens = tonumber(data[1]) or tonumber(ARGV[3])local ts = tonumber(data[2]) or tonumber(ARGV[1])local elapsed = tonumber(ARGV[1]) - tstokens = math.min(tonumber(ARGV[3]), tokens + elapsed * tonumber(ARGV[2]))local allowed = 0if tokens >= 1 then tokens = tokens - 1; allowed = 1 endredis.call('HMSET', KEYS[1], 'tokens', tokens, 'ts', ARGV[1])redis.call('EXPIRE', KEYS[1], 60)return allowedSliding window counter
Section titled “Sliding window counter”async function allow(userId: string, limit = 100, windowSec = 60) { const now = Date.now(); const k = `rl:${userId}`; const cutoff = now - windowSec * 1000; const m = redis.multi() .zremrangebyscore(k, 0, cutoff) .zadd(k, now, `${now}-${crypto.randomUUID()}`) .zcard(k) .expire(k, windowSec); const [, , [, count]] = await m.exec(); return count <= limit;}Pub/Sub
Section titled “Pub/Sub”const sub = redis.duplicate();await sub.subscribe('orders:created');sub.on('message', (chan, msg) => console.log(chan, msg));await redis.publish('orders:created', JSON.stringify({ id: 1 }));Streams (consumer group)
Section titled “Streams (consumer group)”XADD orders * id 1 amount 100XGROUP CREATE orders processors $ MKSTREAMXREADGROUP GROUP processors w1 COUNT 10 BLOCK 5000 STREAMS orders >XACK orders processors <id>XPENDING orders processorsLeaderboard
Section titled “Leaderboard”ZADD board 100 aliceZADD board 250 bobZRANGE board 0 9 WITHSCORES REVZRANK board aliceZINCRBY board 10 aliceHealth & observability
Section titled “Health & observability”redis-cli INFO replicationredis-cli INFO memoryredis-cli INFO statsredis-cli SLOWLOG GET 10redis-cli --bigkeysredis-cli --latencyUseful settings
Section titled “Useful settings”maxmemory 4gbmaxmemory-policy allkeys-lruappendonly yesappendfsync everysectcp-keepalive 300KEYS *in prod (useSCAN).- Storing huge values (>1MB) — split or use external store.
- Long-running Lua scripts — block Redis.
- Treating Redis as primary DB without persistence + replication.
- Connection-per-request — use a pool.
Clients
Section titled “Clients”- Node:
ioredis(cluster, pipelining),redisv4. - Python:
redis-py. - Go:
go-redis. - Java:
lettuce,jedis.