Redis Caching Patterns
Explore practical caching strategies with Redis, including cache-aside, write-through, TTL management, and patterns for real-world applications.
DatabaseIntermediate15 min read
AI Summary
Quick context for Redis Caching Patterns
Redis Caching Patterns is a intermediate guide in Database. Explore practical caching strategies with Redis, including cache-aside, write-through, TTL management, and patterns for real-world applications.. Key topics: caching, patterns, performance, redis.
## Why Cache with Redis?
Redis is an in-memory data store that excels at caching because of its sub-millisecond response times, rich data structures, and built-in expiration mechanisms. A well-designed caching layer can reduce database load by orders of magnitude and dramatically improve application response times.
The key to effective caching is choosing the right pattern for your use case. Each pattern has trade-offs in terms of consistency, complexity, and performance.
## Cache-Aside Pattern
The most common caching pattern. The application checks the cache first; on a miss, it reads from the database and populates the cache.
```typescript
import Redis from "ioredis";
const redis = new Redis();
async function getUser(userId: string) {
const cacheKey = `user:${userId}`;
// Check 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: userId } });
// Populate cache with 1-hour TTL
await redis.setex(cacheKey, 3600, JSON.stringify(user));
return user;
}
```
Cache-aside is ideal when reads far exceed writes and you can tolerate brief staleness after updates.
## Write-Through Pattern
Data is written to both the cache and database simultaneously. This ensures the cache is always up to date but adds write latency.
```typescript
async function updateUser(userId: string, data-removed: Partial) {
const cacheKey = `user:${userId}`;
// Write to database first
const user = await db.user.update({ where: { id: userId }, data });
// Update cache immediately
await redis.setex(cacheKey, 3600, JSON.stringify(user));
return user;
}
```
## Cache Invalidation Strategies
Cache invalidation is one of the hardest problems in computer science. When data changes, you must decide how to handle stale cached values.
### TTL-Based Expiration
Set a time-to-live on cached entries. Simple but may serve stale data until expiration.
```typescript
// Different TTLs for different data types
await redis.setex("session:abc123", 1800, JSON.stringify(session)); // 30 minutes
await redis.setex("product:456", 86400, JSON.stringify(product)); // 24 hours
await redis.setex("config:app", 604800, JSON.stringify(config)); // 7 days
```
### Event-Driven Invalidation
Invalidate cache entries when relevant events occur, ensuring freshness without polling.
```typescript
// When an order is placed, invalidate related caches
async function placeOrder(order: Order) {
await db.order.create({ data-removed: order });
// Invalidate affected caches
await redis.del(`user:${order.userId}:orders`);
await redis.del(`product:${order.productId}:stock`);
await redis.del(`cart:${order.userId}`);
}
```
### Tag-Based Invalidation
Group related cache keys with tags so you can invalidate entire categories at once.
```typescript
async function cacheWithTags(
key: string,
value: unknown,
tags: string[],
ttl: number
) {
await redis.setex(key, ttl, JSON.stringify(value));
// Associate key with tags
for (const tag of tags) {
await redis.sadd(`tag:${tag}`, key);
await redis.expire(`tag:${tag}`, ttl);
}
}
async function invalidateTag(tag: string) {
const keys = await redis.smembers(`tag:${tag}`);
if (keys.length > 0) {
await redis.del(...keys);
}
await redis.del(`tag:${tag}`);
}
```
## Preventing Cache Stampede
When a popular cache entry expires, many concurrent requests may hit the database simultaneously. Use locking or early refresh to prevent this.
```typescript
async function getWithLock(key: string, fetchFn: () => Promise, ttl: number) {
const cached = await redis.get(key);
if (cached) return JSON.parse(cached);
// Try to acquire a lock
const lockKey = `lock:${key}`;
const acquired = await redis.set(lockKey, "1", "EX", 10, "NX");
if (!acquired) {
// Another process is fetching; wait and retry
await new Promise((resolve) => setTimeout(resolve, 100));
return getWithLock(key, fetchFn, ttl);
}
try {
const value = await fetchFn();
await redis.setex(key, ttl, JSON.stringify(value));
return value;
} finally {
await redis.del(lockKey);
}
}
```
## Redis Data Structures for Caching
Beyond simple key-value pairs, Redis offers specialized data structures for different caching scenarios.
- **Sorted Sets**: Leaderboards and time-series data with scoring
- **Hashes**: Store object fields individually to update single properties without rewriting the entire object
- **Lists**: Queues and activity feeds with push/pop operations
- **Sets**: Unique collections and membership checks
```typescript
// Cache a user profile as a hash for partial updates
await redis.hset("user:123", {
name: "Alice",
email: "alice@example.com",
lastLogin: new Date().toISOString(),
});
// Update only the lastLogin field without touching others
await redis.hset("user:123", "lastLogin", new Date().toISOString());
```
## Monitoring and Metrics
Track cache hit rates to measure effectiveness. A hit rate above 80% typically indicates healthy caching.
```typescript
const info = await redis.info("stats");
const hitRate =
parseInt(info.match(/keyspace_hits:(\d+)/)[1]) /
(parseInt(info.match(/keyspace_hits:(\d+)/)[1]) +
parseInt(info.match(/keyspace_misses:(\d+)/)[1]));
console.log(`Cache hit rate: ${(hitRate * 100).toFixed(1)}%`);
```
Continue with related content
Suggested next reads and tools from the knowledge graph.
toolrelates to
Redis
In-memory data structure store for caching and real-time applications
Database#database#performance#cache
toolrelates to
Prisma
Next-generation ORM for TypeScript and JavaScript
Database#database#typescript#orm
toolrelates to
Supabase
Open-source Firebase alternative with PostgreSQL and real-time capabilities