Cache-Aside Pattern (Lazy Loading)
Overview
Cache-Aside is a caching strategy where the application explicitly manages the cache as a side-channel to the primary data store. Unlike read-through or write-through caches where the cache itself mediates access to the database, cache-aside puts the application in full control of when data enters and leaves the cache.
The pattern is sometimes called "lazy loading" because cache entries are populated on demand -- data is only cached when it is first requested, not eagerly preloaded. This makes it the most widely deployed caching strategy in web applications and is the default approach used with Redis, Memcached, and most application-level caching libraries.
- Redis -- the dominant in-memory cache, sub-millisecond reads, supports TTL-based expiry
- Memcached -- simpler key-value cache, widely used for session and object caching
- Application-level caching -- in-process caches (Guava, Caffeine, lru-cache) for zero-latency lookups
- CDN edge caches -- Cloudflare, Fastly, and CloudFront use cache-aside semantics at the HTTP layer
Architecture
Client Layer
Multiple clients send read and write requests to the application server over HTTPS.
Application Server + Cache-Aside Logic
The application server contains the cache-aside logic that orchestrates all interactions between the cache and the database. On every read, it checks the cache first. On every write, it updates the database and then invalidates the cache.
Redis Cache
An in-memory data store used as the cache layer. Provides sub-millisecond reads with TTL-based automatic expiry. The cache is treated as ephemeral -- it can be cleared at any time without data loss.
PostgreSQL Database
The source of truth. All writes go to the database first. The database is durable and ACID-compliant. The cache exists only to reduce read load on the database.
Key Concepts
Cache Hit vs Cache Miss
A cache hit occurs when the requested key exists in the cache -- the database is never touched, and the response is served in under a millisecond. A cache miss occurs when the key is absent (never cached, expired, or invalidated). On a miss, the application queries the database, returns the result to the client, and writes the result to the cache so that subsequent requests become hits.
Write Strategy: Cache Invalidation (Delete-on-Write)
When data is modified, the application writes to the database first, then deletes the cache key rather than updating it. The next read will produce a cache miss and fetch the fresh value from the database. This approach is safer than updating the cache directly because it avoids race conditions where two concurrent writes could leave the cache in an inconsistent state.
Write-Through vs Write-Behind vs Invalidation
- Write-Through: the cache is updated synchronously on every write, keeping it always fresh but adding write latency.
- Write-Behind (Write-Back): writes go to the cache first and are asynchronously flushed to the database, reducing write latency but risking data loss.
- Cache Invalidation (used in cache-aside): the cache key is deleted on write, accepting one extra cache miss in exchange for simplicity and consistency.
TTL (Time-to-Live) Considerations
Every cached entry should have a TTL. Too short a TTL means frequent cache misses and higher database load. Too long a TTL means clients may read stale data. Typical values range from 30 seconds (high-frequency updates) to 24 hours (rarely changing reference data). The right TTL is a trade-off between freshness and hit rate.
Cache Stampede (Thundering Herd)
When a popular cache key expires, many concurrent requests may all experience a cache miss simultaneously and all query the database for the same data. This multiplied load can overwhelm the database. Solutions include:
- Lock-based loading: the first request acquires a distributed lock and populates the cache; other requests wait for the lock to release and then read from cache.
- Probabilistic early revalidation: each request has a small random chance of refreshing the cache before TTL expires, spreading out revalidation over time.
- Request coalescing: identical in-flight requests are deduplicated so that only one actually queries the database.
- Cache warm-up: proactively loading frequently accessed keys into the cache before they expire or before a deployment.
Other Caching Patterns for Comparison
- Read-Through: the cache itself loads data from the database on a miss, abstracting the data source from the application. Simpler application code, but the cache must know about the database.
- Write-Through: every write goes through the cache to the database synchronously. Guarantees cache freshness but increases write latency.
- Write-Behind: writes are buffered in the cache and flushed to the database asynchronously. Lower write latency, but durability depends on the cache not crashing before flush.
- Refresh-Ahead: the cache proactively refreshes entries predicted to be needed soon, reducing the chance of cache misses for hot keys.
Scenarios
- Cache Hit -- Client requests data that exists in Redis cache. The database is never touched. Response served in under 2ms.
- Cache Miss -- Client requests data not in cache. Application queries PostgreSQL, returns the result, and populates the cache with a 5-minute TTL for future requests.
- Write + Cache Invalidation -- Client updates a product price. Application writes to PostgreSQL first, then deletes the cache key. The next read fetches fresh data from the database and re-populates the cache.
- Cache Stampede -- A popular cache key expires and three clients simultaneously miss on the same key. All three query the database in parallel, demonstrating the thundering herd problem and its impact on database load.