caching-strategy
Implement efficient caching strategies using Redis, Memcached, CDN, and cache invalidation patterns. Use when optimizing application performance, reducing database load, or improving response times.
$ 설치
git clone https://github.com/aj-geddes/useful-ai-prompts /tmp/useful-ai-prompts && cp -r /tmp/useful-ai-prompts/skills/caching-strategy ~/.claude/skills/useful-ai-prompts// tip: Run this command in your terminal to install the skill
SKILL.md
name: caching-strategy description: Implement efficient caching strategies using Redis, Memcached, CDN, and cache invalidation patterns. Use when optimizing application performance, reducing database load, or improving response times.
Caching Strategy
Overview
Implement effective caching strategies to improve application performance, reduce latency, and decrease load on backend systems.
When to Use
- Reducing database query load
- Improving API response times
- Handling high traffic loads
- Caching expensive computations
- Storing session data
- CDN integration for static assets
- Implementing distributed caching
- Rate limiting and throttling
Caching Layers
┌─────────────────────────────────────────┐
│ Client Browser Cache │
├─────────────────────────────────────────┤
│ CDN Cache │
├─────────────────────────────────────────┤
│ Application Memory Cache │
├─────────────────────────────────────────┤
│ Distributed Cache (Redis) │
├─────────────────────────────────────────┤
│ Database │
└─────────────────────────────────────────┘
Implementation Examples
1. Redis Cache Implementation (Node.js)
import Redis from 'ioredis';
interface CacheOptions {
ttl?: number; // Time to live in seconds
prefix?: string;
}
class CacheService {
private redis: Redis;
private defaultTTL = 3600; // 1 hour
constructor(redisUrl: string) {
this.redis = new Redis(redisUrl, {
retryStrategy: (times) => {
const delay = Math.min(times * 50, 2000);
return delay;
},
maxRetriesPerRequest: 3
});
this.redis.on('connect', () => {
console.log('Redis connected');
});
this.redis.on('error', (error) => {
console.error('Redis error:', error);
});
}
/**
* Get cached value
*/
async get<T>(key: string): Promise<T | null> {
try {
const value = await this.redis.get(key);
if (!value) return null;
return JSON.parse(value) as T;
} catch (error) {
console.error(`Cache get error for key ${key}:`, error);
return null;
}
}
/**
* Set cached value
*/
async set(
key: string,
value: any,
options: CacheOptions = {}
): Promise<boolean> {
try {
const ttl = options.ttl || this.defaultTTL;
const serialized = JSON.stringify(value);
if (ttl > 0) {
await this.redis.setex(key, ttl, serialized);
} else {
await this.redis.set(key, serialized);
}
return true;
} catch (error) {
console.error(`Cache set error for key ${key}:`, error);
return false;
}
}
/**
* Delete cached value
*/
async delete(key: string): Promise<boolean> {
try {
await this.redis.del(key);
return true;
} catch (error) {
console.error(`Cache delete error for key ${key}:`, error);
return false;
}
}
/**
* Delete multiple keys by pattern
*/
async deletePattern(pattern: string): Promise<number> {
try {
const keys = await this.redis.keys(pattern);
if (keys.length === 0) return 0;
await this.redis.del(...keys);
return keys.length;
} catch (error) {
console.error(`Cache delete pattern error for ${pattern}:`, error);
return 0;
}
}
/**
* Get or set pattern - fetch from cache or compute and cache
*/
async getOrSet<T>(
key: string,
fetchFn: () => Promise<T>,
options: CacheOptions = {}
): Promise<T> {
// Try to get from cache
const cached = await this.get<T>(key);
if (cached !== null) {
return cached;
}
// Fetch and cache
const value = await fetchFn();
await this.set(key, value, options);
return value;
}
/**
* Implement cache-aside pattern with stale-while-revalidate
*/
async getStaleWhileRevalidate<T>(
key: string,
fetchFn: () => Promise<T>,
options: {
ttl: number;
staleTime: number;
}
): Promise<T> {
const cacheKey = `cache:${key}`;
const timestampKey = `cache:${key}:timestamp`;
const [cached, timestamp] = await Promise.all([
this.get<T>(cacheKey),
this.redis.get(timestampKey)
]);
const now = Date.now();
const age = timestamp ? now - parseInt(timestamp) : Infinity;
// Return cached if fresh
if (cached !== null && age < options.ttl * 1000) {
return cached;
}
// Return stale while revalidating in background
if (cached !== null && age < options.staleTime * 1000) {
// Background revalidation
fetchFn()
.then(async (fresh) => {
await this.set(cacheKey, fresh, { ttl: options.ttl });
await this.redis.set(timestampKey, now.toString());
})
.catch(console.error);
return cached;
}
// Fetch fresh data
const fresh = await fetchFn();
await Promise.all([
this.set(cacheKey, fresh, { ttl: options.ttl }),
this.redis.set(timestampKey, now.toString())
]);
return fresh;
}
/**
* Increment counter with TTL
*/
async increment(key: string, ttl?: number): Promise<number> {
const count = await this.redis.incr(key);
if (count === 1 && ttl) {
await this.redis.expire(key, ttl);
}
return count;
}
/**
* Check if key exists
*/
async exists(key: string): Promise<boolean> {
const result = await this.redis.exists(key);
return result === 1;
}
/**
* Get remaining TTL
*/
async ttl(key: string): Promise<number> {
return await this.redis.ttl(key);
}
/**
* Close connection
*/
async disconnect(): Promise<void> {
await this.redis.quit();
}
}
// Usage
const cache = new CacheService('redis://localhost:6379');
// Simple get/set
await cache.set('user:123', { name: 'John', age: 30 }, { ttl: 3600 });
const user = await cache.get('user:123');
// Get or set pattern
const posts = await cache.getOrSet(
'posts:recent',
async () => {
return await database.query('SELECT * FROM posts ORDER BY created_at DESC LIMIT 10');
},
{ ttl: 300 }
);
// Stale-while-revalidate
const data = await cache.getStaleWhileRevalidate(
'expensive-query',
async () => await runExpensiveQuery(),
{ ttl: 300, staleTime: 600 }
);
2. Cache Decorator (Python)
import functools
import json
import hashlib
from typing import Any, Callable, Optional
from redis import Redis
import time
class CacheDecorator:
def __init__(self, redis_client: Redis, ttl: int = 3600):
self.redis = redis_client
self.ttl = ttl
def cache_key(self, func: Callable, *args, **kwargs) -> str:
"""Generate cache key from function name and arguments."""
# Create deterministic key from function and arguments
key_parts = [
func.__module__,
func.__name__,
str(args),
str(sorted(kwargs.items()))
]
key_string = ':'.join(key_parts)
key_hash = hashlib.md5(key_string.encode()).hexdigest()
return f"cache:{func.__name__}:{key_hash}"
def __call__(self, func: Callable) -> Callable:
@functools.wraps(func)
def wrapper(*args, **kwargs):
# Generate cache key
cache_key = self.cache_key(func, *args, **kwargs)
# Try to get from cache
cached = self.redis.get(cache_key)
if cached:
print(f"Cache HIT: {cache_key}")
return json.loads(cached)
# Cache miss - execute function
print(f"Cache MISS: {cache_key}")
result = func(*args, **kwargs)
# Store in cache
self.redis.setex(
cache_key,
self.ttl,
json.dumps(result)
)
return result
# Add cache invalidation method
def invalidate(*args, **kwargs):
cache_key = self.cache_key(func, *args, **kwargs)
self.redis.delete(cache_key)
wrapper.invalidate = invalidate
return wrapper
# Usage
redis = Redis(host='localhost', port=6379, db=0)
cache = CacheDecorator(redis, ttl=300)
@cache
def get_user_profile(user_id: int) -> dict:
"""Fetch user profile from database."""
print(f"Fetching user {user_id} from database...")
# Simulate database query
time.sleep(1)
return {
'id': user_id,
'name': 'John Doe',
'email': 'john@example.com'
}
# First call - cache miss
profile = get_user_profile(123) # Takes 1 second
# Second call - cache hit
profile = get_user_profile(123) # Instant
# Invalidate cache
get_user_profile.invalidate(123)
3. Multi-Level Cache
interface CacheLevel {
get(key: string): Promise<any>;
set(key: string, value: any, ttl?: number): Promise<void>;
delete(key: string): Promise<void>;
}
class MemoryCache implements CacheLevel {
private cache = new Map<string, { value: any; expiry: number }>();
async get(key: string): Promise<any> {
const item = this.cache.get(key);
if (!item) return null;
if (Date.now() > item.expiry) {
this.cache.delete(key);
return null;
}
return item.value;
}
async set(key: string, value: any, ttl: number = 60): Promise<void> {
this.cache.set(key, {
value,
expiry: Date.now() + ttl * 1000
});
}
async delete(key: string): Promise<void> {
this.cache.delete(key);
}
clear(): void {
this.cache.clear();
}
}
class RedisCache implements CacheLevel {
constructor(private redis: Redis) {}
async get(key: string): Promise<any> {
const value = await this.redis.get(key);
return value ? JSON.parse(value) : null;
}
async set(key: string, value: any, ttl: number = 3600): Promise<void> {
await this.redis.setex(key, ttl, JSON.stringify(value));
}
async delete(key: string): Promise<void> {
await this.redis.del(key);
}
}
class MultiLevelCache {
private levels: CacheLevel[];
constructor(levels: CacheLevel[]) {
this.levels = levels; // Ordered from fastest to slowest
}
async get<T>(key: string): Promise<T | null> {
for (let i = 0; i < this.levels.length; i++) {
const value = await this.levels[i].get(key);
if (value !== null) {
// Backfill faster caches
for (let j = 0; j < i; j++) {
await this.levels[j].set(key, value);
}
return value as T;
}
}
return null;
}
async set(key: string, value: any, ttl?: number): Promise<void> {
// Set in all cache levels
await Promise.all(
this.levels.map(level => level.set(key, value, ttl))
);
}
async delete(key: string): Promise<void> {
await Promise.all(
this.levels.map(level => level.delete(key))
);
}
}
// Usage
const cache = new MultiLevelCache([
new MemoryCache(),
new RedisCache(redis)
]);
// Get from fastest available cache
const data = await cache.get('user:123');
// Set in all caches
await cache.set('user:123', userData, 3600);
4. Cache Invalidation Strategies
class CacheInvalidation {
constructor(private cache: CacheService) {}
/**
* Time-based invalidation (TTL)
*/
async setWithTTL(key: string, value: any, seconds: number): Promise<void> {
await this.cache.set(key, value, { ttl: seconds });
}
/**
* Tag-based invalidation
*/
async setWithTags(
key: string,
value: any,
tags: string[]
): Promise<void> {
// Store value
await this.cache.set(key, value);
// Store tag associations
for (const tag of tags) {
await this.cache.redis.sadd(`tag:${tag}`, key);
}
}
async invalidateByTag(tag: string): Promise<number> {
// Get all keys with this tag
const keys = await this.cache.redis.smembers(`tag:${tag}`);
if (keys.length === 0) return 0;
// Delete all keys
await Promise.all(
keys.map(key => this.cache.delete(key))
);
// Delete tag set
await this.cache.redis.del(`tag:${tag}`);
return keys.length;
}
/**
* Event-based invalidation
*/
async invalidateOnEvent(
entity: string,
id: string,
event: 'create' | 'update' | 'delete'
): Promise<void> {
const patterns = [
`${entity}:${id}`,
`${entity}:${id}:*`,
`${entity}:list:*`,
`${entity}:count`
];
for (const pattern of patterns) {
await this.cache.deletePattern(pattern);
}
}
/**
* Version-based invalidation
*/
async setVersioned(
key: string,
value: any,
version: number
): Promise<void> {
const versionedKey = `${key}:v${version}`;
await this.cache.set(versionedKey, value);
await this.cache.set(`${key}:version`, version);
}
async getVersioned(key: string): Promise<any> {
const version = await this.cache.get<number>(`${key}:version`);
if (!version) return null;
return await this.cache.get(`${key}:v${version}`);
}
}
5. HTTP Caching Headers
import express from 'express';
const app = express();
// Cache-Control middleware
function cacheControl(maxAge: number, options: {
private?: boolean;
noStore?: boolean;
noCache?: boolean;
mustRevalidate?: boolean;
staleWhileRevalidate?: number;
} = {}) {
return (req: express.Request, res: express.Response, next: express.NextFunction) => {
const directives: string[] = [];
if (options.noStore) {
directives.push('no-store');
} else if (options.noCache) {
directives.push('no-cache');
} else {
directives.push(options.private ? 'private' : 'public');
directives.push(`max-age=${maxAge}`);
if (options.staleWhileRevalidate) {
directives.push(`stale-while-revalidate=${options.staleWhileRevalidate}`);
}
}
if (options.mustRevalidate) {
directives.push('must-revalidate');
}
res.setHeader('Cache-Control', directives.join(', '));
next();
};
}
// Static assets - long cache
app.use('/static', cacheControl(31536000), express.static('public'));
// API - short cache with revalidation
app.get('/api/data',
cacheControl(60, { staleWhileRevalidate: 300 }),
(req, res) => {
res.json({ data: 'cached for 60s' });
}
);
// Dynamic content - no cache
app.get('/api/user/profile',
cacheControl(0, { private: true, noCache: true }),
(req, res) => {
res.json({ user: 'always fresh' });
}
);
// ETag support
app.get('/api/resource/:id', async (req, res) => {
const resource = await getResource(req.params.id);
const etag = generateETag(resource);
res.setHeader('ETag', etag);
// Check if client has current version
if (req.headers['if-none-match'] === etag) {
return res.status(304).end();
}
res.json(resource);
});
function generateETag(data: any): string {
return require('crypto')
.createHash('md5')
.update(JSON.stringify(data))
.digest('hex');
}
Best Practices
✅ DO
- Set appropriate TTL values
- Implement cache warming for critical data
- Use cache-aside pattern for reads
- Monitor cache hit rates
- Implement graceful degradation on cache failure
- Use compression for large cached values
- Namespace cache keys properly
- Implement cache stampede prevention
- Use consistent hashing for distributed caching
- Monitor cache memory usage
❌ DON'T
- Cache everything indiscriminately
- Use caching as a fix for poor database design
- Store sensitive data without encryption
- Forget to handle cache misses
- Set TTL too long for frequently changing data
- Ignore cache invalidation strategies
- Cache without monitoring
- Store large objects without consideration
Cache Strategies
| Strategy | Description | Use Case |
|---|---|---|
| Cache-Aside | Application checks cache, loads from DB on miss | General purpose |
| Write-Through | Write to cache and DB simultaneously | Strong consistency needed |
| Write-Behind | Write to cache, async write to DB | High write throughput |
| Refresh-Ahead | Proactively refresh before expiry | Predictable access patterns |
| Read-Through | Cache loads from DB automatically | Simplified code |
Resources
Repository

aj-geddes
Author
aj-geddes/useful-ai-prompts/skills/caching-strategy
25
Stars
1
Forks
Updated3d ago
Added5d ago