Redis Data Structures
Overview
Redis supports multiple data structures beyond simple key-value pairs, making it versatile for various use cases.
const redisDataStructures = {
strings: 'Simple key-value pairs',
hashes: 'Field-value pairs (objects)',
lists: 'Ordered collections',
sets: 'Unordered unique collections',
sortedSets: 'Ordered unique collections with scores',
bitmaps: 'Bit-level operations',
hyperLogLogs: 'Cardinality estimation',
streams: 'Append-only log',
geospatial: 'Location data'
};1. Strings
const redis = require('redis');
const client = redis.createClient();
await client.connect();
// Basic operations
await client.set('key', 'value');
const value = await client.get('key');
// Atomic operations
await client.incr('counter');
await client.incrBy('counter', 5);
await client.decr('counter');
// Bit operations
await client.setBit('flags', 0, 1);
const bit = await client.getBit('flags', 0);
// Use cases: caching, counters, flags2. Hashes
// Store objects
await client.hSet('user:1', {
name: 'John Doe',
email: 'john@example.com',
age: '30'
});
// Get all fields
const user = await client.hGetAll('user:1');
// Get specific fields
const name = await client.hGet('user:1', 'name');
const fields = await client.hmGet('user:1', ['name', 'email']);
// Increment numeric field
await client.hIncrBy('user:1', 'loginCount', 1);
// Check field exists
const exists = await client.hExists('user:1', 'email');
// Get all keys or values
const keys = await client.hKeys('user:1');
const values = await client.hVals('user:1');
// Use cases: user profiles, session data, object storage3. Lists
// Queue (FIFO)
await client.rPush('queue', 'job1');
await client.rPush('queue', 'job2');
const job = await client.lPop('queue'); // job1
// Stack (LIFO)
await client.lPush('stack', 'item1');
await client.lPush('stack', 'item2');
const item = await client.lPop('stack'); // item2
// Get range
const items = await client.lRange('list', 0, -1); // All items
const first10 = await client.lRange('list', 0, 9);
// Get by index
const first = await client.lIndex('list', 0);
// Insert before/after
await client.lInsert('list', 'BEFORE', 'pivot', 'newValue');
// Trim list
await client.lTrim('list', 0, 99); // Keep first 100
// Blocking operations
const item = await client.blPop('queue', 5); // Wait 5 seconds
// Use cases: queues, activity feeds, recent items4. Sets
// Add members
await client.sAdd('tags:user:1', 'premium');
await client.sAdd('tags:user:1', 'verified');
await client.sAdd('tags:user:1', ['active', 'premium']); // Duplicate ignored
// Get all members
const tags = await client.sMembers('tags:user:1');
// Check membership
const isMember = await client.sIsMember('tags:user:1', 'premium');
// Remove members
await client.sRem('tags:user:1', 'verified');
// Set operations
await client.sAdd('set1', ['a', 'b', 'c']);
await client.sAdd('set2', ['b', 'c', 'd']);
const union = await client.sUnion(['set1', 'set2']); // [a,b,c,d]
const inter = await client.sInter(['set1', 'set2']); // [b,c]
const diff = await client.sDiff(['set1', 'set2']); // [a]
// Random member
const random = await client.sRandMember('tags:user:1');
const random3 = await client.sRandMemberCount('tags:user:1', 3);
// Pop random
const popped = await client.sPop('tags:user:1');
// Use cases: tags, unique visitors, social connections5. Sorted Sets
// Add members with scores
await client.zAdd('leaderboard', [
{ score: 100, value: 'player1' },
{ score: 200, value: 'player2' },
{ score: 150, value: 'player3' }
]);
// Get by rank (ascending)
const bottom3 = await client.zRange('leaderboard', 0, 2);
// Get by rank (descending)
const top3 = await client.zRange('leaderboard', 0, 2, { REV: true });
// Get by score
const highScorers = await client.zRangeByScore('leaderboard', 150, 200);
// Get rank
const rank = await client.zRank('leaderboard', 'player1');
const revRank = await client.zRevRank('leaderboard', 'player1');
// Get score
const score = await client.zScore('leaderboard', 'player1');
// Increment score
await client.zIncrBy('leaderboard', 50, 'player1');
// Count by score range
const count = await client.zCount('leaderboard', 100, 200);
// Remove by rank or score
await client.zRemRangeByRank('leaderboard', 0, 2);
await client.zRemRangeByScore('leaderboard', 0, 100);
// Use cases: leaderboards, priority queues, time-series6. Bitmaps
// Set bits
await client.setBit('online:2024-01-01', 123, 1); // User 123 online
await client.setBit('online:2024-01-01', 456, 1); // User 456 online
// Get bit
const isOnline = await client.getBit('online:2024-01-01', 123);
// Count set bits
const onlineCount = await client.bitCount('online:2024-01-01');
// Bitwise operations
await client.bitOp('AND', 'result', ['online:2024-01-01', 'online:2024-01-02']);
// Use cases: user activity tracking, feature flags7. HyperLogLog
// Add elements
await client.pfAdd('unique:visitors', ['user1', 'user2', 'user3']);
await client.pfAdd('unique:visitors', ['user1', 'user4']); // user1 counted once
// Get cardinality (approximate count)
const uniqueVisitors = await client.pfCount('unique:visitors'); // ~4
// Merge HyperLogLogs
await client.pfMerge('total:visitors', ['visitors:page1', 'visitors:page2']);
// Use cases: unique visitors, unique events (memory efficient)8. Streams
// Add to stream
await client.xAdd('events', '*', {
user: 'user1',
action: 'login',
timestamp: Date.now()
});
// Read from stream
const messages = await client.xRead([
{ key: 'events', id: '0' }
], { COUNT: 10 });
// Consumer groups
await client.xGroupCreate('events', 'processors', '0', { MKSTREAM: true });
// Read as consumer
const messages = await client.xReadGroup('processors', 'consumer1', [
{ key: 'events', id: '>' }
], { COUNT: 10 });
// Acknowledge message
await client.xAck('events', 'processors', messageId);
// Use cases: event sourcing, activity streams, message queues9. Geospatial
// Add locations
await client.geoAdd('locations', [
{ longitude: -73.97, latitude: 40.77, member: 'Central Park' },
{ longitude: -73.98, latitude: 40.76, member: 'Times Square' }
]);
// Get coordinates
const coords = await client.geoPos('locations', 'Central Park');
// Calculate distance
const distance = await client.geoDist('locations', 'Central Park', 'Times Square', 'km');
// Find nearby
const nearby = await client.geoRadius('locations', -73.97, 40.77, 5, 'km');
// Find nearby by member
const nearCentralPark = await client.geoRadiusByMember('locations', 'Central Park', 5, 'km');
// Use cases: location-based services, nearby searchReal-World Examples
Session Store
class SessionStore {
async createSession(userId, data) {
const sessionId = generateId();
await client.hSet(`session:${sessionId}`, {
userId,
...data,
createdAt: Date.now()
});
await client.expire(`session:${sessionId}`, 3600);
return sessionId;
}
async getSession(sessionId) {
return await client.hGetAll(`session:${sessionId}`);
}
}Rate Limiter
class RateLimiter {
async checkLimit(userId, limit = 100, window = 60) {
const key = `ratelimit:${userId}`;
const now = Date.now();
await client.zRemRangeByScore(key, 0, now - window * 1000);
const count = await client.zCard(key);
if (count >= limit) {
return { allowed: false };
}
await client.zAdd(key, [{ score: now, value: `${now}` }]);
await client.expire(key, window);
return { allowed: true, remaining: limit - count - 1 };
}
}Leaderboard
class Leaderboard {
async addScore(playerId, score) {
await client.zAdd('leaderboard', [{ score, value: playerId }]);
}
async getTopPlayers(count = 10) {
return await client.zRange('leaderboard', 0, count - 1, {
REV: true,
WITHSCORES: true
});
}
async getPlayerRank(playerId) {
return await client.zRevRank('leaderboard', playerId);
}
}.NET with Redis
using StackExchange.Redis;
public class RedisService
{
private readonly IDatabase _db;
// Hash operations
public async Task SetHash(string key, Dictionary<string, string> values)
{
var entries = values.Select(kv =>
new HashEntry(kv.Key, kv.Value)).ToArray();
await _db.HashSetAsync(key, entries);
}
// List operations
public async Task PushToList(string key, string value)
{
await _db.ListRightPushAsync(key, value);
}
// Set operations
public async Task AddToSet(string key, string value)
{
await _db.SetAddAsync(key, value);
}
// Sorted set operations
public async Task AddToSortedSet(string key, string member, double score)
{
await _db.SortedSetAddAsync(key, member, score);
}
}Interview Tips
- Explain structures: Strings, hashes, lists, sets, sorted sets
- Show use cases: When to use each structure
- Demonstrate operations: Add, get, remove for each type
- Discuss performance: O(1) for most operations
- Mention advanced: Streams, HyperLogLog, geospatial
- Show examples: Session store, rate limiter, leaderboard
Summary
Redis supports multiple data structures: strings (simple values), hashes (objects), lists (ordered), sets (unique), sorted sets (scored), bitmaps (bits), HyperLogLog (cardinality), streams (events), geospatial (locations). Choose structure based on use case. Most operations are O(1). Use hashes for objects, lists for queues, sets for unique items, sorted sets for rankings. Essential for building high-performance applications with Redis.
Test Your Knowledge
Take a quick quiz to test your understanding of this topic.