| name | caching-strategies |
| description | Multi-layer caching with Redis, CDN, and browser caching for optimal application performance |
| category | backend |
| triggers | ["caching strategies","redis cache","cdn caching","cache invalidation","http caching","performance caching"] |
Caching Strategies
Implement multi-layer caching for optimal performance. This skill covers Redis patterns, CDN configuration, HTTP cache headers, and cache invalidation strategies.
Purpose
Dramatically improve application performance through strategic caching:
- Reduce database load with application caching
- Minimize latency with edge caching
- Optimize bandwidth with browser caching
- Handle cache invalidation correctly
- Implement cache-aside and write-through patterns
- Monitor cache effectiveness
Features
1. Redis Caching Patterns
import Redis from 'ioredis';
const redis = new Redis(process.env.REDIS_URL);
class CacheService {
private prefix: string;
private defaultTTL: number;
constructor(prefix: string = 'app', defaultTTL: number = 3600) {
this.prefix = prefix;
this.defaultTTL = defaultTTL;
}
private key(key: string): string {
return `${this.prefix}:${key}`;
}
async get<T>(key: string): Promise<T | null> {
const data = await redis.get(this.key(key));
return data ? JSON.parse(data) : null;
}
async set<T>(key: string, value: T, ttl?: number): Promise<void> {
const serialized = JSON.stringify(value);
await redis.setex(this.key(key), ttl || this.defaultTTL, serialized);
}
async del(key: string): Promise<void> {
await redis.del(this.key(key));
}
async exists(key: string): Promise<boolean> {
return (await redis.exists(this.key(key))) === 1;
}
async getOrSet<T>(
key: string,
factory: () => Promise<T>,
ttl?: number
): Promise<T> {
const cached = await this.get<T>(key);
if (cached !== null) return cached;
const value = await factory();
await this.set(key, value, ttl);
return value;
}
async mget<T>(keys: string[]): Promise<(T | null)[]> {
const prefixedKeys = keys.map(k => this.key(k));
const values = await redis.mget(prefixedKeys);
return values.map(v => (v ? JSON.parse(v) : null));
}
async mset<T>(entries: Array<{ key: string; value: T; ttl?: number }>): Promise<void> {
const pipeline = redis.pipeline();
for (const entry of entries) {
pipeline.setex(
this.key(entry.key),
entry.ttl || this.defaultTTL,
JSON.stringify(entry.value)
);
}
await pipeline.exec();
}
async invalidatePattern(pattern: string): Promise<number> {
const keys = await redis.keys(this.key(pattern));
if (keys.length === 0) return 0;
return redis.del(...keys);
}
}
const cache = new CacheService('users', 3600);
async function getUser(id: string): Promise<User> {
return cache.getOrSet(`user:${id}`, () => db.user.findUnique({ where: { id } }));
}
2. Cache-Aside Pattern
class UserRepository {
private cache: CacheService;
constructor() {
this.cache = new CacheService('users', 3600);
}
async findById(id: string): Promise<User | null> {
const cached = await this.cache.get<User>(`${id}`);
if (cached) {
metrics.increment('cache.hit', { type: 'user' });
return cached;
}
metrics.increment('cache.miss', { type: 'user' });
const user = await db.user.findUnique({ where: { id } });
if (user) {
await this.cache.(, user);
} {
..(, , );
}
user;
}
(: , : <>): <> {
user = db..({ : { id }, data });
..();
user;
}
(: ): <> {
db..({ : { id } });
..();
}
}
getWithLock<T>(
: ,
: <T>,
: =
): <T> {
cache = ();
lockKey = ;
cached = cache.<T>(key);
(cached !== ) cached;
acquired = redis.(lockKey, , , , );
(!acquired) {
( (r, ));
(key, factory, ttl);
}
{
cached = cache.<T>(key);
(cached !== ) cached;
value = ();
cache.(key, value, ttl);
value;
} {
redis.(lockKey);
}
}
3. Write-Through & Write-Behind
class WriteThroughCache<T> {
constructor(
private cache: CacheService,
private repository: Repository<T>
) {}
async create(entity: T): Promise<T> {
const saved = await this.repository.create(entity);
await this.cache.set(this.getKey(saved), saved);
return saved;
}
async update(id: string, data: Partial<T>): Promise<T> {
const updated = await this.repository.update(id, data);
await this.cache.set(this.getKey(updated), updated);
return updated;
}
(: T): {
;
}
}
<T> {
: <{ : ; : T }> = [];
: .;
() {
. = ( .(), flushIntervalMs);
}
(: , : T): <> {
..(key, data);
..({ key, data });
}
(): <> {
(.. === ) ;
batch = ..(, );
{
..(batch.( b.));
} (error) {
..(...batch);
.(, error);
}
}
(): <> {
(.);
.();
}
}
4. HTTP Caching Headers
interface CacheOptions {
maxAge?: number;
sMaxAge?: number;
staleWhileRevalidate?: number;
staleIfError?: number;
private?: boolean;
noStore?: boolean;
mustRevalidate?: boolean;
}
function cacheControl(options: CacheOptions) {
return (req: Request, res: Response, next: NextFunction) => {
const directives: string[] = [];
if (options.noStore) {
directives.push('no-store');
} else {
if (options.private) {
directives.push('private');
} else {
directives.push('public');
}
if (options.maxAge !== undefined) {
directives.push(`max-age=${options.maxAge}`);
}
(options. !== ) {
directives.();
}
(options. !== ) {
directives.();
}
(options. !== ) {
directives.();
}
(options.) {
directives.();
}
}
res.(, directives.());
();
};
}
app.(
,
({ : }),
express.()
);
app.(
,
({ : }),
profileHandler
);
app.(
,
({
: ,
: ,
: ,
}),
productsHandler
);
etag ;
app.(, (req, res) => {
product = (req..);
(!product) {
res.().({ : });
}
body = .(product);
tag = (body);
res.(, tag);
res.(, );
(req.[] === tag) {
res.().();
}
res.(product);
});
5. CDN Configuration
{
"headers": [
{
"source": "/api/public/(.*)",
"headers": [
{
"key": "Cache-Control",
"value": "public, s-maxage=60, stale-while-revalidate=86400"
}
]
},
{
"source": "/_next/static/(.*)",
"headers": [
{
"key": "Cache-Control",
"value": "public, max-age=31536000, immutable"
}
]
}
]
}
const cacheRules = {
rules: [
{
expression: '(http.request.uri.path matches "^/api/public/")',
action: 'set_cache_settings',
action_parameters: {
edge_ttl: {
mode: 'override_origin',
default: 300,
},
browser_ttl: {
mode: 'override_origin',
default: 60,
},
},
},
{
expression: '(http.request.uri.path matches "^/static/")',
action: 'set_cache_settings',
action_parameters: {
cache: true,
edge_ttl: { mode: , : * },
},
},
],
};
(): <> {
(
,
{
: ,
: {
: ,
: ,
},
: .({ : urls }),
}
);
}
(): <> {
(
,
{
: ,
: {
: ,
: ,
},
: .({ tags }),
}
);
}
6. Cache Invalidation
import { EventEmitter } from 'events';
class CacheInvalidator extends EventEmitter {
private cache: CacheService;
constructor() {
super();
this.cache = new CacheService();
this.setupListeners();
}
private setupListeners(): void {
this.on('user:updated', async (userId: string) => {
await this.cache.del(`user:${userId}`);
await this.cache.invalidatePattern(`user:${userId}:*`);
});
this.on('user:deleted', async (userId: string) => {
await this..();
});
.(, (: ) => {
..();
product = db..({ : { : productId } });
(product) {
..();
}
});
.(, () => {
redis.();
});
}
}
invalidator = ();
{
(: , : ): <> {
product = db..({ : { id }, data });
invalidator.(, id);
product;
}
}
{ } ;
(, () => {
cache.();
}).();
(, () => {
products = ();
cache.(, products, );
}).();
7. Multi-Layer Caching
import LRUCache from 'lru-cache';
class MultiLayerCache<T> {
private l1: LRUCache<string, T>;
private l2: CacheService;
constructor(options: {
l1MaxSize: number;
l1TTL: number;
l2Prefix: string;
l2TTL: number;
}) {
this.l1 = new LRUCache({
max: options.l1MaxSize,
ttl: options.l1TTL * 1000,
});
this.l2 = new CacheService(options.l2Prefix, options.l2TTL);
}
async get(key: string): Promise<T | null> {
const l1Value = this.l1.get(key);
if (l1Value !== undefined) {
metrics.();
l1Value;
}
l2Value = ..<T>(key);
(l2Value !== ) {
metrics.();
..(key, l2Value);
l2Value;
}
metrics.();
;
}
(: , : T, ?: , ?: ): <> {
..(key, value, { : l1TTL ? l1TTL * : });
..(key, value, l2TTL);
}
(
: ,
: <T>,
?: ,
?:
): <T> {
cached = .(key);
(cached !== ) cached;
value = ();
.(key, value, l1TTL, l2TTL);
value;
}
(: ): <> {
..(key);
..(key);
}
}
userCache = <>({
: ,
: ,
: ,
: ,
});
(): < | > {
userCache.(
,
db..({ : { id } })
);
}
Use Cases
1. API Response Caching
function apiCache(options: {
ttl: number;
keyGenerator?: (req: Request) => string;
condition?: (req: Request) => boolean;
}) {
const cache = new CacheService('api');
return async (req: Request, res: Response, next: NextFunction) => {
if (req.method !== 'GET' || (options.condition && !options.condition(req))) {
return next();
}
const key = options.keyGenerator?.(req) || req.originalUrl;
const cached = await cache.get<{ body: any; headers: Record<string, string> }>(key);
if (cached) {
Object.entries(cached.headers).forEach(([k, v]) => res.set(k, v));
res.set('X-Cache', );
res.(cached.);
}
originalJson = res..(res);
res. = {
cache.(key, { body, : res.() }, options.);
res.(, );
(body);
};
();
};
}
app.(, ({ : }), getProducts);
2. Session Caching
import session from 'express-session';
import RedisStore from 'connect-redis';
app.use(session({
store: new RedisStore({ client: redis }),
secret: process.env.SESSION_SECRET!,
resave: false,
saveUninitialized: false,
cookie: {
secure: process.env.NODE_ENV === 'production',
httpOnly: true,
maxAge: 24 * 60 * 60 * 1000,
},
}));
Best Practices
Do's
- Cache close to the user - Browser > CDN > App > Database
- Use appropriate TTLs - Balance freshness vs. performance
- Implement cache warming - Pre-populate for critical paths
- Monitor hit rates - Target > 90% for hot data
- Plan invalidation - Know when and how to invalidate
- Use consistent hashing - For distributed caches
Don'ts
- Don't cache sensitive data in shared caches
- Don't forget cache key namespacing
- Don't ignore cache stampede scenarios
- Don't cache errors with long TTLs
- Don't skip monitoring
- Don't assume cache is always available
Cache Strategy Checklist
## Cache Implementation Checklist
### Design
- [ ] Identified cacheable data
- [ ] Defined appropriate TTLs
- [ ] Planned invalidation strategy
- [ ] Considered cache layers
### Implementation
- [ ] Added cache-aside logic
- [ ] Implemented stampede prevention
- [ ] Set up monitoring
- [ ] Added cache headers
### Operations
- [ ] Monitoring hit/miss ratios
- [ ] Alerting on cache failures
- [ ] Regular cache analysis
- [ ] Invalidation testing
Related Skills
- redis - Redis operations
- performance-profiling - Measuring cache impact
- api-architecture - API caching patterns
Reference Resources