Capabilities/Data & Search

Caching Strategies

Cache-aside, write-through, invalidation patterns, and distributed caching with Redis

cachingRedisinvalidationperformancedistributed

Learning lens

Start with judgment, not a universal prescription.

This page is a learning model for understanding the capability. It does not claim exact production internals, exact costs, or an infinite-scale architecture.

Evidence

Conceptual pattern

0->1 default

Start with the primary database when quality and volume allow it; add dedicated data infrastructure when query shape demands it.

Cost drivers

index sizewrite amplificationstorage growthreindexing and backfills

Watch for breakpoints

  • -queries harm transactional workloads
  • -freshness targets conflict with indexing cost
  • -ranking quality needs a dedicated pipeline

Learn first

Concepts behind this architecture

The Problem

Database queries are expensive — 5-50ms each, limited by connection pools, and contention under load. Caching stores frequently accessed data in fast in-memory stores (Redis, Memcached) to serve reads in <1ms. But caching introduces the hardest problem in computer science: cache invalidation. When does the cache become stale? How do you update it? What happens during cache failures? The wrong caching strategy can cause stale data bugs, thundering herds on cache misses, and inconsistency between cache and database that’s impossible to debug.

Architectural Approaches

Cache-Aside (Lazy Loading)

The application manages the cache explicitly. On read: check cache first, if miss then query the database and store the result in cache. On write: update the database and invalidate (delete) the cache entry. The next read re-populates the cache. The most common pattern.

Pros

  • +Simple to implement — application has full control
  • +Only caches data that is actually requested (demand-driven)
  • +Cache failure is graceful — falls back to database
  • +Works with any cache backend (Redis, Memcached, local)

Cons

  • −First request after invalidation is slow (cache miss → DB query)
  • −Risk of stale data if invalidation fails
  • −Thundering herd: many requests hit the database on popular cache miss
  • −Requires careful TTL tuning per data type

Write-Through Cache

Every write goes to both the cache and the database simultaneously. The cache is always up-to-date. Reads always hit the cache. Eliminates stale data at the cost of slower writes.

Pros

  • +Cache is always consistent with the database
  • +No cache misses for recently written data
  • +Simpler mental model — cache mirrors the database

Cons

  • −Slower writes — must write to two systems
  • −Caches data that may never be read (wasteful)
  • −More complex failure handling — what if cache write succeeds but DB fails?
  • −Not suitable for high-write, low-read workloads

Write-Behind (Write-Back) Cache

Writes go to the cache first and are asynchronously flushed to the database in batches. Provides the fastest write performance but risks data loss if the cache fails before flushing.

Pros

  • +Fastest write performance — only memory write on hot path
  • +Batch writes to database improve throughput
  • +Absorbs write spikes without database pressure

Cons

  • −Risk of data loss — cached writes not yet flushed
  • −Complex consistency model — cache is ahead of database
  • −Database reads may return stale data
  • −Failure recovery is complex — must replay un-flushed writes

Architecture

Loading diagram...
Caching Strategies — High-level architecture

Key Components

Cache Manager

Middleware layer that intercepts data access calls and implements the chosen caching strategy (cache-aside, write-through, etc.). Handles cache key generation, serialization, TTL management, and error handling. Provides a unified API regardless of the underlying cache backend.

L1 In-Process Cache

Application-local LRU cache for the hottest data. Zero network latency. Limited by process memory (typically 100MB-1GB). Not shared across instances — each instance has its own L1. Best for immutable or rarely-changing data (configuration, feature flags).

L2 Distributed Cache (Redis)

Shared cache accessible by all application instances. Redis Cluster for high availability and horizontal scaling. Stores serialized objects with TTL. Supports rich data structures (hashes, sorted sets, sets) for complex caching patterns.

Cache Invalidation Publisher

Listens for data change events (via CDC, application events, or database triggers) and publishes cache invalidation messages. Ensures all cache layers (L1 across all instances + L2) are invalidated when source data changes.

Cache Warming Service

Pre-populates the cache with known hot data during startup or after a cache flush. Prevents the thundering herd problem on a cold cache. Runs as a background job that queries the database for frequently accessed records.

Cache Metrics Collector

Tracks hit rate, miss rate, eviction rate, latency, and memory usage per cache key prefix. Critical for tuning TTLs and identifying cache efficiency problems. Alert on hit rate drops below threshold (e.g., <80% indicates a problem).

Data Model

Loading diagram...
Caching Strategies — Entity relationship diagram

Cache Invalidation Patterns

“There are only two hard things in Computer Science: cache invalidation and naming things.” — Phil Karlton

TTL-based expiry: Set a time-to-live on every cache entry. After TTL expires, the next read triggers a cache miss and re-fetch. Simple but creates a staleness window equal to the TTL. Use shorter TTLs (30-60s) for frequently changing data, longer (5-60min) for stable data.

Event-driven invalidation: When data changes, publish an event that triggers cache deletion. Most accurate but requires reliable event delivery. If the invalidation event is lost, the cache stays stale until TTL expires.

Version-based invalidation: Include a version number in the cache key: user:123:v7. When data changes, increment the version. Old cache entries naturally expire via TTL. Never serves stale data but wastes cache space with old versions.

Tag-based invalidation: Tag cache entries with categories. Invalidate all entries with a given tag. Example: tag all product-related caches with product:456. When product 456 changes, invalidate all entries tagged with it. Redis doesn’t support this natively — implement with sets tracking keys per tag.

Stampede prevention: When a popular cache entry expires, hundreds of requests simultaneously hit the database. Solutions:

  • Lock-based: First request acquires a lock, fetches from DB, populates cache. Others wait on the lock.
  • Probabilistic early expiry: Each request has a small chance of refreshing before TTL. Spreading refreshes over time.
  • Background refresh: A background worker refreshes popular entries before they expire.

Multi-Layer Caching

Production systems typically use multiple cache layers, each with different latency and capacity characteristics:

L1 — In-process cache (0.001ms, 100MB):

  • Node.js: lru-cache package
  • Java: Caffeine or Guava Cache
  • Go: groupcache or ristretto
  • Best for: configuration, feature flags, frequently accessed immutable data
  • Invalidation: TTL + pub/sub notification to all instances

L2 — Distributed cache (0.5-2ms, 10-100GB):

  • Redis Cluster or Memcached
  • Shared across all application instances
  • Best for: session data, user profiles, API response caching
  • Invalidation: direct DELETE on write, TTL as safety net

L3 — CDN / Edge cache (0-50ms, terabytes):

  • Cloudflare, CloudFront, Fastly
  • Best for: static assets, public API responses, HTML pages
  • Invalidation: cache tags, surrogate keys, or purge API

Cache hierarchy flow: Request → L1 check → L2 check → L3 check → Origin (database). Each layer handles the miss and populates the cache for the next request. A well-tuned hierarchy achieves 95%+ hit rate at L1+L2, reducing database load dramatically.

Consistency challenge: L1 caches across different instances can be inconsistent (Instance A invalidated, Instance B hasn’t). Use short L1 TTLs (5-15 seconds) and pub/sub invalidation to minimize the inconsistency window.

Cache Key Design

A good cache key strategy prevents collisions, enables efficient invalidation, and makes debugging easier.

Key format: {prefix}:{entity}:{id}:{variant}

  • Prefix: service or module name for namespacing
  • Entity: the data type (user, product, feed)
  • ID: unique identifier
  • Variant: optional qualifier (locale, version, view)

Examples:

  • api:user:123:profile — User 123’s profile
  • api:user:123:feed:page:1 — User 123’s feed, page 1
  • api:product:456:en — Product 456 in English
  • api:search:q=shoes:page=1:sort=price — Search results cache

Hashing long keys: For complex queries, hash the parameters: api:search:{md5(params)}. Keep a reverse mapping for debugging.

Key expiry patterns:

  • User-specific data: 5-15 min TTL (changes on user action)
  • Catalog data: 1-24 hour TTL (changes infrequently)
  • Computed aggregations: 1-5 min TTL (expensive to recompute)
  • Feature flags: 30-60 second TTL (needs freshness)

Avoid hot keys: If one key gets disproportionate traffic (e.g., a viral post), it creates a hotspot on a single Redis shard. Mitigate with key replication (store copies with random suffixes) or L1 caching with very short TTL.

Real-World Examples

Facebook

Operates the world’s largest Memcached deployment (trillions of cache lookups per day). Uses a multi-region cache hierarchy with regional pools and cross-region invalidation via McRouter. Implemented “lease” mechanism to prevent thundering herds.

Twitter

Redis cluster caching timeline data (fan-out on write). Each user’s timeline is a Redis sorted set. Cache warming fills timelines for users who have been inactive. L1 in-process cache for hot configuration data.

Stack Overflow

Famously serves millions of requests with minimal hardware using aggressive multi-layer caching. L1 in-process cache + L2 Redis + HTTP output caching. Most page renders hit zero database queries.

Cloudflare

CDN-level caching with the Tiered Cache feature: edge PoP → regional tier → origin shield → origin. Cache Tags enable granular purging. Workers KV provides a key-value cache at the edge with eventual consistency.

Architectural Tradeoffs

Cache-aside vs write-through

Pros

  • +Cache-aside: only caches what's read, simpler failure handling
  • +Write-through: always consistent, no stale reads
  • +Cache-aside: more efficient for read-heavy, write-light workloads

Cons

  • −Cache-aside: stale data possible, cache miss penalty on first read
  • −Write-through: slower writes, caches unrequested data
  • −Write-through: complex failure handling if one write succeeds and other fails

Redis vs Memcached

Pros

  • +Redis: rich data structures, persistence, pub/sub, Lua scripting
  • +Memcached: simpler, multi-threaded (better per-node throughput)
  • +Redis: cluster mode with automatic sharding and failover

Cons

  • −Redis: single-threaded per shard (mitigated by io-threads in v6+)
  • −Memcached: no persistence, no data structures beyond key-value
  • −Redis: more complex configuration and operational overhead

Short TTL (seconds) vs long TTL (hours)

Pros

  • +Short: data stays fresh, inconsistency window is small
  • +Long: higher hit rate, less database load
  • +Per-entity TTL tuning provides the best balance

Cons

  • −Short: more cache misses, higher database load
  • −Long: stale data served for longer, harder to invalidate reliably
  • −Per-entity: more complex configuration and monitoring

Learn Caching Strategies

Copy a learning context prompt for an AI assistant. It keeps assumptions visible and asks for tradeoffs, cost drivers, failure modes, and what to measure before adding complexity.

Explore More Capabilities