Redis: Master In-Memory Data Store for Modern Applications
Guvantic Engineering
Guvantic Technical Publication
Deep dive into Redis architecture, data structures, persistence, caching patterns, and real-world use cases with production code examples.
The Problem: Database Bottlenecks
Your application is slow. Database queries take 200-500ms, and you're hitting connection limits. Users are leaving. Here's the solution.
Redis Architecture Explained
Key Architecture Decisions:
Data Structures and Their Use Cases
Strings
// Cache a JSON object
await redis.setex("user:123", 300, JSON.stringify(user));
// Counter for rate limiting
await redis.incr("api:user:123:calls");
// TTL-based expiry
await redis.set("session:token-xyz", userId, "EX", 3600);
Hashes
// Store user profile fields
await redis.hset("user:123", "name", "John", "email", "john@example.com");
await redis.hset("user:123", "lastLogin", new Date().toISOString());
// Get specific fields
const user = await redis.hgetall("user:123");
Lists (Queue Pattern)
// Push to queue
await redis.rpush("email-queue", JSON.stringify({ to: "user@example.com", subject: "Welcome" }));
// Pop from queue (blocking)
const job = await redis.blpop("email-queue", 5);
Sorted Sets (Leaderboards)
// Add score for a user
await redis.zadd("leaderboard", 1500, "user-123");
// Get top 10 scores
const top10 = await redis.zrevrange("leaderboard", 0, 9, "WITHSCORES");
// Get user rank
const rank = await redis.zrevrank("leaderboard", "user-123");
Sets (Unique Items)
// Track unique visitors
await redis.sadd("page:home:visitors", ipAddress);
// Get unique count
const count = await redis.scard("page:home:visitors");
// Check if user has liked
const hasLiked = await redis.sismember("post:456:likes", "user-123");
Caching Patterns with Code
Cache-Aside Pattern
import { Redis } from "ioredis";
import { db } from "./database";
class CacheAside {
constructor(private redis: Redis) {}
async getProduct(productId: string) {
const cacheKey = `product:${productId}`;
// 1. Check cache
const cached = await this.redis.get(cacheKey);
if (cached) {
return JSON.parse(cached);
}
// 2. Cache miss - query database
const product = await db.query("SELECT * FROM products WHERE id = ?", [productId]);
// 3. Write to cache
await this.redis.setex(cacheKey, 300, JSON.stringify(product));
return product;
}
// Invalidate on update
async updateProduct(productId: string, data: any) {
// 1. Update database
await db.query("UPDATE products SET ... WHERE id = ?", [productId, data]);
// 2. Invalidate cache
await this.redis.del(`product:${productId}`);
}
}
Write-Through Pattern
class WriteThrough {
constructor(private redis: Redis, private db: any) {}
async write(key: string, data: any) {
// 1. Write to database
await this.db.write(key, data);
// 2. Write to cache
await this.redis.setex(key, 300, JSON.stringify(data));
// 3. Return (both completed)
return data;
}
}
Cache Stampede Prevention
class CacheStampedeProtection {
private mutexes = new Map<string, Promise<any>>();
async getOrSet<T>(
key: string,
fetchFn: () => Promise<T>,
ttl = 300
): Promise<T> {
// Check cache first
const cached = await this.redis.get(key);
if (cached) {
return JSON.parse(cached);
}
// Check if another request is already fetching
if (this.mutexes.has(key)) {
return this.mutexes.get(key);
}
// Only one request will fetch data
const promise = this.fetchAndCache(key, fetchFn, ttl);
this.mutexes.set(key, promise);
try {
return await promise;
} finally {
this.mutexes.delete(key);
}
}
private async fetchAndCache<T>(
key: string,
fetchFn: () => Promise<T>,
ttl: number
) {
const data = await fetchFn();
await this.redis.setex(key, ttl, JSON.stringify(data));
return data;
}
}
Rate Limiting with Redis
Sliding Window Rate Limiter
class SlidingWindowRateLimiter {
constructor(private redis: Redis, private maxRequests: number, private windowSeconds: number) {}
async allow(userId: string): Promise<boolean> {
const key = `rate_limit:${userId}`;
const now = Date.now();
const windowStart = now - this.windowSeconds * 1000;
// Remove old requests
await this.redis.zremrangebyscore(key, 0, windowStart);
// Count current requests in window
const count = await this.redis.zcard(key);
if (count >= this.maxRequests) {
return false; // Rate limited
}
// Add current request
await this.redis.zadd(key, now, `${now}:${Math.random()}`);
// Set expiry to clean up
await this.redis.expire(key, this.windowSeconds + 10);
return true; // Allowed
}
}
// Usage
const limiter = new SlidingWindowRateLimiter(redis, 100, 60); // 100 requests per minute
app.use(async (req, res, next) => {
const userId = req.user.id;
if (!await limiter.allow(userId)) {
return res.status(429).json({ error: "Rate limit exceeded" });
}
next();
});
Distributed Locking (Redlock)
Redlock Algorithm Implementation
import { Redis } from "ioredis";
import { randomBytes } from "crypto";
class DistributedLock {
constructor(private redis: Redis, private ttl = 30000) {} // 30 seconds
async acquire(key: string): Promise<string> {
const lockId = randomBytes(16).toString("hex");
const acquired = await this.redis.set(
`lock:${key}`
lockId,
"PX",
this.ttl,
"NX", // Only set if not exists
);
if (!acquired) {
throw new Error("Could not acquire lock");
}
return lockId;
}
async release(key: string, lockId: string) {
// Use Lua script for atomic release
const script = `
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
`;
const result = await this.redis.eval(script, 1, `lock:${key}`, lockId);
if (result === 0) {
throw new Error("Lock was already released or expired");
}
}
async withLock<T>(key: string, fn: () => Promise<T>): Promise<T> {
const lockId = await this.acquire(key);
try {
return await fn();
} finally {
await this.release(key, lockId);
}
}
}
// Usage
const lock = new DistributedLock(redis);
await lock.withLock("inventory:product-123", async () => {
// Critical section - only one instance can execute this
const currentStock = await getStock("product-123");
await updateStock("product-123", currentStock - 1);
});
Persistence: RDB vs AOF
RDB Configuration (redis.conf)
# Save snapshots every 900s if at least 1 key changed
save 900 1
# Every 300s if 10 keys changed
save 300 10
# Every 60s if 10000 keys changed
save 60 10000
# Compression
rdbcompression yes
rdbchecksum yes
AOF Configuration
# Enable AOF
appendonly yes
# Always fsync (safest, slowest)
appendfsync always
# Fsync every second (default, balanced)
appendfsync everysec
# Never fsync (OS handles, fastest, riskiest)
appendfsync no
# Auto-rewrite
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
Production Checklist
Guvantic Engineering
Engineering & System Architecture Division
Engineering teams at Guvantic deliver mission-critical software, high-concurrency microservices, and international organic positioning strategies for fast-growing companies in USA, UK, and India.