Redis in-memory data store expertise from Netflix engineers. Use when implementing caching strategies, managing user sessions, building real-time leaderboards, pub/sub messaging, rate limiting, distributed locks, or optimizing Redis data structures (strings, hashes, sets, sorted sets, streams). Triggers on Redis, caching, session store, pub/sub, rate limiting, in-memory database, or ElastiCache.
Purpose: Master Redis for caching, sessions, real-time features, and high-performance data storage
Agent: Netflix Backend Architect Use When: Implementing caching, sessions, pub/sub, rate limiting, or real-time features
Redis (REmote DIctionary Server) is an in-memory data structure store used as a database, cache, message broker, and streaming engine.
Core Strengths:
# Strings
SET key "value"
GET key
SET counter 0
INCR counter # Atomic increment
INCRBY counter 5
DECR counter
# Expiration
SET session:123 "user_data" EX 3600 # Expire in 1 hour
EXPIRE key 60 # Set TTL to 60 seconds
TTL key # Check remaining TTL
PERSIST key # Remove expiration
# Delete
DEL key
EXISTS key
# Store user object
HSET user:1 name "John" email "[email protected]" age 30
HGET user:1 name # Get single field
HGETALL user:1 # Get all fields
HMGET user:1 name email # Get multiple fields
HINCRBY user:1 age 1 # Increment age
HDEL user:1 age # Delete field
# Push to list
LPUSH queue:tasks "task1" # Push left (front)
RPUSH queue:tasks "task2" # Push right (back)
# Pop from list
LPOP queue:tasks # Pop from front
RPOP queue:tasks # Pop from back
BLPOP queue:tasks 0 # Blocking pop (wait forever)
# Get elements
LRANGE queue:tasks 0 -1 # Get all elements
LLEN queue:tasks # Get length
# Add members
SADD tags "nodejs" "redis" "database"
# Check membership
SISMEMBER tags "nodejs" # Returns 1 if exists
# Get members
SMEMBERS tags # Get all members
SCARD tags # Get count
# Set operations
SADD set1 "a" "b" "c"
SADD set2 "b" "c" "d"
SINTER set1 set2 # Intersection: ["b", "c"]
SUNION set1 set2 # Union: ["a", "b", "c", "d"]
SDIFF set1 set2 # Difference: ["a"]
# Add members with scores
ZADD leaderboard 100 "player1" 200 "player2" 150 "player3"
# Get by rank
ZRANGE leaderboard 0 -1 WITHSCORES # All, lowest to highest
ZREVRANGE leaderboard 0 9 WITHSCORES # Top 10, highest to lowest
# Get by score
ZRANGEBYSCORE leaderboard 100 200 # Score between 100-200
# Get rank
ZRANK leaderboard "player1" # Rank (0-indexed)
ZREVRANK leaderboard "player1" # Reverse rank
# Increment score
ZINCRBY leaderboard 50 "player1" # Add 50 to player1's score
# Get score
ZSCORE leaderboard "player1"
import Redis from 'ioredis'
const redis = new Redis(process.env.REDIS_URL)
async function getUser(id: number) {
const cacheKey = `user:${id}`
// Try cache first
const cached = await redis.get(cacheKey)
if (cached) {
return JSON.parse(cached)
}
// Cache miss - fetch from database
const user = await db.user.findUnique({ where: { id } })
// Store in cache for 1 hour
await redis.setex(cacheKey, 3600, JSON.stringify(user))
return user
}
// Invalidate cache on update
async function updateUser(id: number, data: any) {
await db.user.update({ where: { id }, data })
// Clear cache
await redis.del(`user:${id}`)
}
import session from 'express-session'
import RedisStore from 'connect-redis'
import { createClient } from 'redis'
const redisClient = createClient({ url: process.env.REDIS_URL })
await redisClient.connect()
app.use(
session({
store: new RedisStore({ client: redisClient }),
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
secure: process.env.NODE_ENV === 'production',
httpOnly: true,
maxAge: 1000 * 60 * 60 * 24 * 7 // 1 week
}
})
)
// Use session
app.post('/login', (req, res) => {
req.session.userId = user.id
res.json({ success: true })
})
app.get('/profile', (req, res) => {
if (!req.session.userId) {
return res.status(401).json({ error: 'Not authenticated' })
}
// ...
})
async function checkRateLimit(ip: string): Promise<boolean> {
const key = `rate_limit:${ip}`
// Increment counter
const requests = await redis.incr(key)
// Set expiration on first request
if (requests === 1) {
await redis.expire(key, 60) // 1 minute window
}
// Check if limit exceeded
const limit = 100 // 100 requests per minute
return requests <= limit
}
// Use in middleware
app.use(async (req, res, next) => {
const allowed = await checkRateLimit(req.ip)
if (!allowed) {
return res.status(429).json({ error: 'Too many requests' })
}
next()
})
// Sliding window rate limit (more accurate)
async function slidingWindowRateLimit(key: string, limit: number, window: number) {
const now = Date.now()
const windowStart = now - window * 1000
// Remove old entries
await redis.zremrangebyscore(key, 0, windowStart)
// Count requests in current window
const count = await redis.zcard(key)
if (count >= limit) {
return false
}
// Add current request
await redis.zadd(key, now, `${now}`)
await redis.expire(key, window)
return true
}
import Redis from 'ioredis'
// Publisher
const publisher = new Redis()
// Publish message
await publisher.publish('notifications', JSON.stringify({
userId: 123,
message: 'You have a new message'
}))
// Subscriber
const subscriber = new Redis()
subscriber.subscribe('notifications', (err, count) => {
console.log(`Subscribed to ${count} channel(s)`)
})
subscriber.on('message', (channel, message) => {
console.log(`Received from ${channel}:`, message)
const data = JSON.parse(message)
// Handle notification (e.g., send to WebSocket clients)
io.to(data.userId).emit('notification', data)
})
// Unsubscribe
subscriber.unsubscribe('notifications')
import { Queue, Worker } from 'bullmq'
// Create queue
const emailQueue = new Queue('emails', {
connection: {
host: 'localhost',
port: 6379
}
})
// Add job
await emailQueue.add('send-welcome', {
email: '[email protected]',
name: 'John'
})
// Process jobs
const worker = new Worker('emails', async job => {
if (job.name === 'send-welcome') {
await sendWelcomeEmail(job.data.email, job.data.name)
}
}, {
connection: {
host: 'localhost',
port: 6379
}
})
worker.on('completed', job => {
console.log(`Job ${job.id} completed`)
})
worker.on('failed', (job, err) => {
console.error(`Job ${job.id} failed:`, err)
})
// Add score
async function updateScore(playerId: string, score: number) {
await redis.zadd('leaderboard', score, playerId)
}
// Get top 10
async function getTopPlayers(limit = 10) {
const players = await redis.zrevrange('leaderboard', 0, limit - 1, 'WITHSCORES')
// Format: [player1, score1, player2, score2, ...]
const leaderboard = []
for (let i = 0; i < players.length; i += 2) {
leaderboard.push({
playerId: players[i],
score: parseInt(players[i + 1]),
rank: i / 2 + 1
})
}
return leaderboard
}
// Get player rank
async function getPlayerRank(playerId: string) {
const rank = await redis.zrevrank('leaderboard', playerId)
const score = await redis.zscore('leaderboard', playerId)
return {
playerId,
rank: rank !== null ? rank + 1 : null,
score: score ? parseInt(score) : 0
}
}
import Redis from 'ioredis'
// Single instance for simple cases
export const redis = new Redis(process.env.REDIS_URL)
// Cluster for high availability
export const redis = new Redis.Cluster([
{ host: 'node1', port: 6379 },
{ host: 'node2', port: 6379 },
{ host: 'node3', port: 6379 }
])
# Good: Hierarchical, descriptive