Redis Patterns
Battle-tested Upstash KV patterns for rate limiting, caching, sessions, and distributed state in serverless environments.
Rate Limiter
Sliding-window rate limiter using sorted sets. Atomically evict stale entries and count active requests in a single pipeline.
const ok = await redis.zadd(
`ratelimit:${key}`, { score: now, member: now + '-' + crypto.randomUUID() }
);
await redis.zremrangebyscore(`ratelimit:${key}`, 0, now - windowMs);
const count = await redis.zcard(`ratelimit:${key}`);
await redis.expire(`ratelimit:${key}`, Math.ceil(windowMs / 1000));
return count <= limit;Cache-Aside
Check Redis first; on miss, fetch from origin, populate cache with TTL. Use JSON serialization for structured data.
let data = await redis.get(`cache:${cacheKey}`);
if (!data) {
data = await fetchFromOrigin();
await redis.set(`cache:${cacheKey}`, JSON.stringify(data), { ex: 300 });
} else {
data = JSON.parse(data);
}Distributed Lock
Acquire a lock with NX and a unique token. Release only if the token matches to prevent accidental unlocks.
const token = crypto.randomUUID();
const acquired = await redis.set(`lock:${resource}`, token, { nx: true, ex: 10 });
if (!acquired) return null;
try { /* critical section */ }
finally {
const script = 'if redis.call("get",KEYS[1])==ARGV[1] then return redis.call("del",KEYS[1]) end';
await redis.eval(script, [`lock:${resource}`], [token]);
}Session Store
Store session blobs keyed by session ID with sliding expiration. Refresh TTL on every access.
const session = await redis.get(`session:${sid}`);
if (session) await redis.expire(`session:${sid}`, 3600);
return session ? JSON.parse(session) : null;Idempotency Keys
Deduplicate payment or mutation requests. Store the response under the idempotency key after first processing.
const existing = await redis.get(`idem:${key}`);
if (existing) return JSON.parse(existing);
const result = await processRequest();
await redis.set(`idem:${key}`, JSON.stringify(result), { ex: 86400 });
return result;All patterns assume an Upstash Redis client initialized with @upstash/redis. For production, wrap every call in try/catch with exponential backoff on connection failures.