Caching Strategies with Redis
Redis is an in-memory data store that enables high-performance caching. Learn caching strategies, Redis data structures, and patterns for improving application performance.
Challenge
Section titled “Challenge”Implement Redis caching strategies to reduce database load, improve response times, and build scalable distributed systems.
Explanation
Section titled “Explanation”Why Redis?
Section titled “Why Redis?”In-Memory Storage: Lightning-fast data access (microseconds) Atomic Operations: Thread-safe operations without locks Data Structures: Strings, Lists, Sets, Hashes, Sorted Sets, Streams Persistence: Optional durability with RDB snapshots and AOF logs Replication: Master-slave replication for high availability Pub/Sub: Real-time message delivery
Caching Strategies
Section titled “Caching Strategies”Cache-Aside (Lazy Loading): Load data into cache on demand Write-Through: Write to both cache and database simultaneously Write-Behind: Asynchronously write to database Cache Invalidation: TTL, event-based, or manual invalidation
Code Examples
Section titled “Code Examples”1. Basic Redis Connection
Section titled “1. Basic Redis Connection”const redis = require('redis');
const client = redis.createClient({ host: 'localhost', port: 6379, db: 0, retry_strategy: (options) => { if (options.error && options.error.code === 'ECONNREFUSED') { return new Error('End of retry'); } if (options.total_retry_time > 1000 * 60 * 60) { return new Error('Retry time exhausted'); } if (options.attempt > 10) { return undefined; } return Math.min(options.attempt * 100, 3000); }});
client.on('error', (err) => console.log('Redis Error', err));client.on('connect', () => console.log('Redis Connected'));
module.exports = client;2. Cache-Aside Pattern
Section titled “2. Cache-Aside Pattern”const redis = require('redis');const client = redis.createClient();
async function getCachedUser(userId) { // Try to get from cache const cached = await client.get(`user:${userId}`); if (cached) { return JSON.parse(cached); }
// If not in cache, fetch from database const user = await fetchUserFromDatabase(userId);
// Store in cache with TTL (1 hour) await client.setEx(`user:${userId}`, 3600, JSON.stringify(user));
return user;}
async function fetchUserFromDatabase(userId) { // Simulate database query return { id: userId, name: 'John Doe', email: 'john@example.com' };}3. Write-Through Caching
Section titled “3. Write-Through Caching”async function updateUserWithCache(userId, userData) { // Update database const updatedUser = await db.users.update(userId, userData);
// Update cache simultaneously await client.setEx( `user:${userId}`, 3600, JSON.stringify(updatedUser) );
return updatedUser;}
async function deleteUserWithCacheInvalidation(userId) { // Delete from database await db.users.delete(userId);
// Invalidate cache await client.del(`user:${userId}`);}4. Redis Data Structures
Section titled “4. Redis Data Structures”// Strings - Key-value pairsawait client.set('session:abc123', JSON.stringify(sessionData));const session = JSON.parse(await client.get('session:abc123'));
// Lists - Ordered collectionsawait client.rPush('activity:log', 'User logged in');const logs = await client.lRange('activity:log', 0, -1);
// Sets - Unique collectionsawait client.sAdd('active:users', userId1, userId2);const activeUsers = await client.sMembers('active:users');
// Hashes - Field-value pairsawait client.hSet('user:123', { name: 'John', email: 'john@example.com' });const user = await client.hGetAll('user:123');
// Sorted Sets - Ranked collectionsawait client.zAdd('leaderboard', { score: 100, member: 'user1' });const topUsers = await client.zRevRange('leaderboard', 0, 9);
// Streams - Append-only logsawait client.xAdd('events', '*', { type: 'login', userId: '123' });5. Distributed Session Management
Section titled “5. Distributed Session Management”const session = require('express-session');const RedisStore = require('connect-redis').default;const redis = require('redis');
const client = redis.createClient();client.connect();
app.use( session({ store: new RedisStore({ client }), secret: process.env.SESSION_SECRET, resave: false, saveUninitialized: false, cookie: { secure: process.env.NODE_ENV === 'production', httpOnly: true, maxAge: 24 * 60 * 60 * 1000, // 24 hours }, }));6. Rate Limiting with Redis
Section titled “6. Rate Limiting with Redis”async function isRateLimitExceeded(userId, limit = 100, windowMs = 60000) { const key = `rate:${userId}`;
const current = await client.incr(key);
if (current === 1) { // Set expiration on first request await client.expire(key, Math.ceil(windowMs / 1000)); }
return current > limit;}
app.use(async (req, res, next) => { const exceeded = await isRateLimitExceeded(req.user?.id, 100);
if (exceeded) { return res.status(429).json({ error: 'Rate limit exceeded' }); }
next();});Best Practices
Section titled “Best Practices”-
Set Appropriate TTL
- Balance between freshness and cache hit rate
- Use different TTLs for different data types
- Implement cache invalidation strategies
-
Handle Cache Misses
- Implement graceful degradation
- Prevent cache stampede (thundering herd)
- Use locks or callbacks for heavy operations
-
Monitor Cache Performance
- Track hit/miss ratios
- Monitor memory usage
- Measure eviction rates
-
Security
- Encrypt sensitive cached data
- Use password authentication
- Restrict network access
-
High Availability
- Use Redis Sentinel for failover
- Implement Redis Cluster for scaling
- Regular backups and disaster recovery
Real-World Applications
Section titled “Real-World Applications”- Session Storage: Distributed session management
- Rate Limiting: API rate limiting and throttling
- Real-Time Analytics: Event counters and aggregations
- Feed Caching: Social media feeds and recommendations
- Task Queues: Background job processing