From harness-claude
Designs and implements server-side caching strategies—cache-aside, read-through, write-through, write-behind—with Redis and Memcached; covers multi-tier architectures, serialization, and consistency for high-read workloads.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Design and implement server-side caching strategies — cache-aside, read-through, write-through, and write-behind patterns with Redis and Memcached, multi-tier caching architectures, serialization optimization, and distributed cache consistency.
Implements multi-tier caching with Redis, in-memory caches, and CDN layers using cache-aside patterns, TTLs, and invalidation to reduce database load and improve read performance.
Provides caching strategies (Cache-Aside, Read-Through, Write-Through, Write-Behind), invalidation approaches, multi-level caching, and Redis data structures for audits and generation.
Advises on cache strategies, invalidation patterns, and distributed caching. Detects Redis/Memcached/in-memory usage, analyzes access patterns, designs layers, troubleshoots stale data and stampedes.
Share bugs, ideas, or general feedback.
Design and implement server-side caching strategies — cache-aside, read-through, write-through, and write-behind patterns with Redis and Memcached, multi-tier caching architectures, serialization optimization, and distributed cache consistency.
Choose a caching pattern. Select based on read/write ratio and consistency requirements:
Cache-Aside (Lazy Loading):
Read: App → Cache → [miss] → DB → Write to Cache → Return
Write: App → DB → Invalidate Cache
Read-Through:
Read: App → Cache → [miss] → Cache fetches from DB → Return
Write: App → DB → Invalidate Cache
Write-Through:
Read: App → Cache → [miss] → DB → Return
Write: App → Cache → Cache writes to DB → Return
Write-Behind (Write-Back):
Read: App → Cache → [miss] → DB → Return
Write: App → Cache → Return (async write to DB later)
Implement cache-aside pattern. The most common server-side caching pattern:
async function getUser(userId) {
const cacheKey = `user:${userId}`;
// Try cache first
const cached = await redis.get(cacheKey);
if (cached) {
return JSON.parse(cached);
}
// Cache miss: fetch from database
const user = await db.users.findById(userId);
// Write to cache with TTL
await redis.setex(cacheKey, 3600, JSON.stringify(user));
return user;
}
async function updateUser(userId, data) {
// Write to database
await db.users.update(userId, data);
// Invalidate cache (not update — avoids race conditions)
await redis.del(`user:${userId}`);
}
Choose between Redis and Memcached.
| Feature | Redis | Memcached |
|---|---|---|
| Data structures | Strings, hashes, lists, sets, sorted sets, streams | Strings only |
| Persistence | RDB snapshots, AOF log | None (pure cache) |
| Replication | Built-in primary/replica | None native |
| Memory efficiency | Slightly higher overhead per key | Lower overhead, slab allocator |
| Max value size | 512MB | 1MB (default) |
| Multi-threaded | Single-threaded (event loop) + I/O threads (Redis 6+) | Multi-threaded |
| Use case | Complex data, pub/sub, persistence needed | Simple key-value, maximum throughput |
Default recommendation: Redis for most applications (richer feature set). Memcached for simple key-value with very high throughput requirements.
Implement multi-tier caching. Combine in-process (L1) and distributed (L2) caches:
const lruCache = new Map(); // L1: in-process, ~1000 entries
const LRU_MAX = 1000;
const LRU_TTL = 30000; // 30 seconds
async function getCached(key) {
// L1: check in-process cache (sub-microsecond)
const l1 = lruCache.get(key);
if (l1 && Date.now() - l1.timestamp < LRU_TTL) {
return l1.value;
}
// L2: check Redis (~0.5-1ms)
const l2 = await redis.get(key);
if (l2) {
const value = JSON.parse(l2);
lruCache.set(key, { value, timestamp: Date.now() });
if (lruCache.size > LRU_MAX) {
const firstKey = lruCache.keys().next().value;
lruCache.delete(firstKey);
}
return value;
}
return null; // Cache miss — caller fetches from DB
}
Optimize serialization. JSON is human-readable but not the most efficient. For high-throughput caching:
Set appropriate TTLs. TTL prevents stale data accumulation and bounds memory growth:
Handle cache failures gracefully. The cache is not the source of truth — the database is:
async function getUserWithFallback(userId) {
try {
const cached = await redis.get(`user:${userId}`);
if (cached) return JSON.parse(cached);
} catch (error) {
// Cache is down — fall through to database
logger.warn('Cache unavailable, falling back to DB', { error });
}
return db.users.findById(userId);
}
In a distributed system, cache consistency depends on the invalidation pattern:
Instagram uses Redis for caching user timelines, storing 300 million user sessions with sub-millisecond read latency. Their architecture uses consistent hashing across a Redis cluster to distribute keys evenly. Each Redis node handles ~100,000 operations per second. They use Redis hashes for user profile data (storing fields individually rather than serializing the entire profile), enabling partial reads and updates without deserializing the full object. Session data uses Redis strings with 24-hour TTL for automatic cleanup.
GitHub reduced database load by 50% by implementing a three-tier caching strategy for repository metadata: (1) per-process LRU cache (L1, ~5MB per process, 10-second TTL) handles repeated reads within a single request lifecycle, (2) Redis cluster (L2, 100GB total) handles cross-process caching with 5-minute TTL, (3) CDN edge caching (L3) handles public repository pages with 60-second TTL. A single page view for a popular repository like torvalds/linux hits L1 for repeated reads of the same repo metadata during template rendering, L2 for repo stats and contributor data, and L3 for the rendered HTML.
Caching without TTL. Without TTL, cache entries live forever. Stale data accumulates, eventually consuming all available memory. Redis will evict keys using its eviction policy (default: noeviction, which rejects new writes when memory is full). Always set explicit TTLs.
Serializing entire ORM objects. ORM objects include metadata, lazy-loaded relation proxies, and internal state. A User ORM object might serialize to 5KB when the actual data is 500 bytes. Extract plain data objects before caching.
Using cache as primary data store without persistence. Redis can persist data (RDB/AOF), but cache instances are often configured without persistence for performance. If the cache restarts, all data is lost. Never use a non-persistent cache as the sole store for important data.
Not handling cache failures gracefully. If every request requires cache and cache goes down, the entire application fails. Implement circuit breakers and database fallback for when cache is unavailable.