Skip to content

Database Performance: Redis Cache-Aside Pattern

CoreConceptAugust 3, 20269 min read

Primary relational databases (like PostgreSQL or MySQL) execute disk I/O and query compilation for every read query. As application concurrency grows to thousands of requests per second, executing repeated expensive SQL queries for static or slow-changing data exhausts database CPU utilization and connection pools, leading to latency spikes and outages.

Caching offloads read traffic to high-speed in-memory data stores like Redis. The Cache-Aside (Lazy Loading) pattern is the industry standard caching strategy: application code inspects the cache first, reads from the database only on a cache miss, and populates the cache for future queries. This guide details Cache-Aside implementation, cache invalidation, cache stampede prevention, and TTL jittering.

Redis Cache-Aside pattern sequence flow and stampede prevention architecture
Redis Cache-Aside pattern sequence flow and stampede prevention architecture

Mental Model: Cache-Aside (Lazy Loading) Sequence Flow

In the Cache-Aside pattern, the application code sits between the cache and the database, managing data population explicitly.

When a read request arrives: 1. Application queries Redis (GET user:1001). 2. Cache Hit: If Redis returns data, the application returns it immediately (sub-2ms response). 3. Cache Miss: If Redis returns null, application queries primary SQL database (SELECT * FROM users WHERE id = 1001), writes result to Redis (SETEX user:1001 3600 payload), and returns response.

Only requested data is cached, keeping Redis memory footprint small. For caching comparison benchmarks, review redis vs memcached caching deep dive and optimizing postgresql query performance explain analyze.

Redis Cache-Aside sequence flow on cache hit, cache miss, and mutex lock stampede protection
Redis Cache-Aside sequence flow on cache hit, cache miss, and mutex lock stampede protection

Quick reference

  • Application explicitly orchestrates data fetching between Redis cache and SQL database.
  • Cache hits return data in sub-2 milliseconds, shielding primary databases from read spikes.
  • Lazy loading ensures Redis stores only active, frequently requested domain data.
  • Cache failures or Redis outages do not break application functionality (graceful fallback).
  • Set explicit Time-To-Live (TTL) durations to prevent stale data accumulation in RAM.

Remember this

Implement Cache-Aside lazy loading to query Redis first and fallback to SQL databases on cache misses.

Managing Cache Invalidation: Write-Through vs Cache Invalidation on Mutation

Phil Karlton famously noted: "There are only two hard things in Computer Science: cache invalidation and naming things."

When application data is updated or deleted (UPDATE users SET name = 'Jane'), the Redis cache becomes stale. You must choose an invalidation strategy: - Delete on Mutation (Recommended): Upon executing SQL UPDATE or DELETE, immediately delete the Redis key (DEL user:1001). The next read query misses and repopulates fresh database data. - Write-Through: Application updates database AND writes new data to Redis atomically.

Deleting keys on mutation is preferred over updating keys because deleting avoids race conditions when concurrent writes occur.

Quick reference

  • Delete keys (DEL key) on SQL mutation rather than attempting key updates to avoid race conditions.
  • Write-Through caching updates database and cache atomically but increases write latency.
  • Set conservative TTLs (e.g., 1 hour) as a safety net against missed invalidation events.
  • Use Redis Pub/Sub or CDC (Change Data Capture via Debezium) to automate cache invalidation.
  • Log cache hit ratios (hits / (hits + misses)) to measure caching effectiveness in Grafana.

Remember this

Delete Redis keys upon database mutations to maintain strict cache freshness without race conditions.

Preventing Cache Stampedes with Distributed Locks & Probabilistic Early Expiration

A Cache Stampede (Thundering Herd) occurs when a high-traffic cache key (e.g., homepage product banner) expires or is invalidated under 10,000 req/sec load.

Suddenly, 10,000 concurrent requests miss the cache simultaneously and execute the expensive SQL query against the database at the exact same millisecond, overloading database CPU and connection pools.

Fix Cache Stampedes using Distributed Mutex Locks: when a cache miss occurs, the worker acquires a short-lived Redis lock (SET lock:key token NX PX 5000). Only the single lock-holding worker queries the database and populates the cache; all other requests wait 50ms and retry reading the cache.

Redis Cache-Aside sequence flow on cache hit, cache miss, and mutex lock stampede protection
Redis Cache-Aside sequence flow on cache hit, cache miss, and mutex lock stampede protection

Quick reference

  • Cache Stampedes occur when high-traffic expired keys trigger simultaneous database queries.
  • Distributed locks (SET NX PX) ensure only one thread queries the database on cache misses.
  • Waiting threads poll the cache after short sleep intervals (50ms) to receive populated data.
  • Probabilistic Early Expiration (XFetch algorithm) recomputes cache keys before expiration.
  • Prevents catastrophic database CPU spikes during high-concurrency traffic bursts.

Remember this

Deploy Redis mutex locks (SET NX) to ensure a single worker recomputes expired keys during traffic spikes.

Mitigating Cache Penetration & Cache Avalanche with Bloom Filters & TTL Jitter

Two common production caching failure modes require proactive defenses:

1. Cache Penetration: Attackers query non-existent IDs (id = -9999). Every request misses Redis and hits the database. Mitigate by caching null placeholder values (SETEX key 60 "null") or deploying a Bloom Filter to reject invalid IDs before querying Redis. 2. Cache Avalanche: Hundreds of thousands of keys are initialized at midnight with identical 24-hour TTLs. At midnight the next day, all keys expire simultaneously, crashing the database. Mitigate by adding TTL Jitter (randomizing TTLs: 3600 + random(0, 300) seconds).

Quick reference

  • Cache Penetration occurs when non-existent keys bypass cache and hit database continuously.
  • Mitigate penetration by caching short-lived null placeholders or using Bloom Filters.
  • Cache Avalanche occurs when large key batches expire simultaneously, causing massive DB spikes.
  • Add TTL Jitter (randomizing expiration times by 5-10%) to stagger key expirations evenly.
  • Monitor Redis memory usage and configure maxmemory-policy volatile-lru for safe RAM eviction.

Remember this

Cache null values to block penetration attacks and apply TTL Jitter to prevent simultaneous cache avalanches.

Key takeaway

To test Redis Cache-Aside, launch a local Redis instance (redis-server). Benchmark query latency using autocannon with and without Redis caching, and observe sub-2ms response times.

Share:

Related Articles

In-memory caching is an essential component of high-throughput web architectures, reducing database read load and accele

Read

A Redis GET can be constant-time and still miss its latency target when a large Lua script is ahead of it, the client op

Read

At the heart of every database system lies a Storage Engine that determines how data is written to disk, indexed, and re

Read

Keep learning

Follow a structured path or browse all courses to go deeper.