Redis Data Structures: Beyond Key-Value
Redis provides powerful data structures beyond simple key-value. After using Redis in production, here’s how to leverage each structure effectively.
Redis Data Structures
Strings
Use cases:
- Simple values
- Counters
- Caching
// Set/Get
await redis.set('user:123:name', 'John Doe');
const name = await redis.get('user:123:name');
// Counters
await redis.incr('page:views');
await redis.incrby('page:views', 10);
// Expiration
await redis.setex('session:123', 3600, 'session-data');
Lists
Use cases:
- Queues
- Timelines
- Recent items
// Push/Pop
await redis.lpush('tasks', 'task1');
await redis.rpush('tasks', 'task2');
const task = await redis.lpop('tasks');
// Get range
const tasks = await redis.lrange('tasks', 0, 10);
// Blocking pop
const task = await redis.blpop('tasks', 10); // Wait 10 seconds
Sets
Use cases:
- Unique items
- Tags
- Memberships
// Add/Remove
await redis.sadd('tags:article:123', 'javascript', 'nodejs');
await redis.srem('tags:article:123', 'javascript');
// Check membership
const isMember = await redis.sismember('tags:article:123', 'nodejs');
// Set operations
const intersection = await redis.sinter('tags:article:123', 'tags:article:456');
const union = await redis.sunion('tags:article:123', 'tags:article:456');
const difference = await redis.sdiff('tags:article:123', 'tags:article:456');
Sorted Sets
Use cases:
- Leaderboards
- Rankings
- Time-series data
// Add with score
await redis.zadd('leaderboard', 100, 'player1');
await redis.zadd('leaderboard', 200, 'player2');
// Get rank
const rank = await redis.zrank('leaderboard', 'player1');
// Get top players
const topPlayers = await redis.zrevrange('leaderboard', 0, 9, 'WITHSCORES');
// Range queries
const players = await redis.zrangebyscore('leaderboard', 100, 200);
Hashes
Use cases:
- Objects
- User profiles
- Configuration
// Set/Get fields
await redis.hset('user:123', 'name', 'John Doe');
await redis.hset('user:123', 'email', 'john@example.com');
const name = await redis.hget('user:123', 'name');
// Get all
const user = await redis.hgetall('user:123');
// Increment
await redis.hincrby('user:123', 'score', 10);
Streams
Use cases:
- Event logs
- Message queues
- Time-series
// Add to stream
await redis.xadd('events', '*', 'type', 'user.created', 'userId', '123');
// Read from stream
const events = await redis.xread('STREAMS', 'events', '0');
// Consumer groups
await redis.xgroup('CREATE', 'events', 'consumers', '0');
const events = await redis.xreadgroup('GROUP', 'consumers', 'worker1', 'STREAMS', 'events', '>');
Practical Patterns
Rate Limiting
async function rateLimit(userId, limit, window) {
const key = `rate_limit:${userId}`;
const current = await redis.incr(key);
if (current === 1) {
await redis.expire(key, window);
}
return current <= limit;
}
Caching with TTL
async function getCachedUser(userId) {
const cached = await redis.get(`user:${userId}`);
if (cached) {
return JSON.parse(cached);
}
const user = await db.getUser(userId);
await redis.setex(`user:${userId}`, 3600, JSON.stringify(user));
return user;
}
Leaderboard
class Leaderboard {
async addScore(playerId, score) {
await redis.zadd('leaderboard', score, playerId);
}
async getTopPlayers(limit = 10) {
return await redis.zrevrange('leaderboard', 0, limit - 1, 'WITHSCORES');
}
async getPlayerRank(playerId) {
return await redis.zrevrank('leaderboard', playerId);
}
}
Recent Items
async function addRecentItem(userId, itemId) {
const key = `recent:${userId}`;
await redis.lpush(key, itemId);
await redis.ltrim(key, 0, 9); // Keep only 10 items
await redis.expire(key, 86400); // 24 hours
}
async function getRecentItems(userId) {
return await redis.lrange(`recent:${userId}`, 0, 9);
}
Distributed Lock
async function acquireLock(key, ttl = 10) {
const lockKey = `lock:${key}`;
const result = await redis.set(lockKey, 'locked', 'EX', ttl, 'NX');
return result === 'OK';
}
async function releaseLock(key) {
await redis.del(`lock:${key}`);
}
Best Practices
- Choose right structure - Match use case
- Set expiration - Prevent memory leaks
- Use pipelines - Batch operations
- Monitor memory - Track usage
- Handle failures - Graceful degradation
- Use transactions - When needed
- Optimize keys - Short, consistent
- Monitor performance - Track latency
Conclusion
Redis data structures enable:
- Efficient data storage
- Fast operations
- Rich functionality
- Scalable caching
Choose the right structure for your use case. The patterns shown here handle production workloads.
Redis data structures from September 2020, covering strings, lists, sets, sorted sets, hashes, and streams.