|$ curl https://forge-ai.dev/api/markdown?path=docs/system-design/caching
$cat docs/caching.md
updated Recently·30 min read·published

Caching

System DesignIntermediate🎯Free Tools
Introduction

Caching stores copies of frequently accessed data in fast, temporary storage closer to the consumer. It is one of the highest-impact techniques in system design: a well-placed cache can reduce latency by orders of magnitude and protect databases from thundering herds.

Caching is also one of the hardest techniques to get right. Stale data, cache misses, invalidation races, and stampeding herds can introduce subtle bugs. The art of caching lies in choosing what to cache, where to cache it, and how to keep it consistent.

This guide covers caching layers from browser to database, caching patterns, invalidation strategies, eviction policies, and failure handling. By the end, you will know how to design a cache that accelerates your system without becoming a liability.

Caching Layers

Caches exist at multiple layers between the user and the origin datastore. Each layer has different latency, capacity, and consistency characteristics. The closer a cache is to the user, the faster it is and the less load it places on origin systems.

cache-layers.txt
TEXT
1User
2
3 ├─▶ Browser cache (fastest, private, short TTL)
4
5 ├─▶ CDN edge cache (regional, shared, static assets)
6
7 ├─▶ Reverse proxy cache (Varnish / Nginx, full responses)
8
9 ├─▶ Application cache (Redis / Memcached, objects)
10
11 ├─▶ Database buffer pool (frequently read pages)
12
13 └─▶ Origin datastore (source of truth)
LayerTypical TTLScopeBest For
BrowserMinutes to yearsSingle userStatic assets, immutable files
CDNHours to daysRegional usersImages, JS, CSS, video
Reverse proxySeconds to minutesAll users, per URLIdempotent GET responses
ApplicationMinutes to hoursAll users, per objectDatabase objects, computed results
DatabaseManaged by engineInstance-localHot pages and indexes

info

Cache at the highest layer that can satisfy the request. A CDN cache hit avoids your data center entirely. An application cache hit avoids a database query. Each miss cascades down to the next layer.
Content Delivery Networks

A Content Delivery Network (CDN) caches static content at geographically distributed edge locations. When a user requests an asset, the CDN serves it from the nearest edge, reducing latency and offloading origin servers.

Modern CDNs also support dynamic content acceleration, edge computing, and security features such as DDoS mitigation and WAF. For system design interviews, focus on cache invalidation, origin shielding, and TTL strategies.

cdn.txt
TEXT
1CDN architecture:
2
3User in Tokyo ──▶ CDN edge in Tokyo ──▶ Origin in Virginia
4
5 └── cache hit: serve immediately
6 └── cache miss: fetch from origin, cache, serve
7
8Best practices:
9- Use versioned filenames (app.v2.js) for long TTLs
10- Set Cache-Control headers explicitly
11- Use origin shield to reduce origin load
12- Purge by tag or URL when content changes

best practice

Name immutable assets with content hashes so they can be cached forever. Never cache HTML entry points with long TTLs unless you have a robust invalidation strategy; use short TTLs and stale-while-revalidate instead.
Caching Patterns

How the application interacts with the cache and database determines consistency, latency, and write behavior. The four main patterns are cache-aside, read-through, write-through, and write-behind.

Cache-Aside (Lazy Loading)

The application checks the cache first. On a miss, it reads from the database, populates the cache, and returns the result. This is the most common pattern because it is simple and gives the application full control over cache keys and TTLs.

cache-aside.txt
TEXT
1Cache-aside read:
2
31. app.get(key) from cache
42. if hit: return value
53. if miss:
6 value = db.query(key)
7 cache.set(key, value, ttl)
8 return value
9
10Cache-aside write:
11
121. db.update(key, value)
132. cache.delete(key)
143. next read repopulates cache

Read-Through

The cache itself knows how to fetch missing data from the database. The application always talks to the cache. Read-through simplifies application code but couples the cache to the data model.

Write-Through

Data is written to the cache and the database at the same time. Reads are fast because the cache is always up to date. The downside is that writes are slower and cache capacity limits write throughput.

Write-Behind (Write-Back)

Data is written to the cache first and flushed to the database asynchronously. This maximizes write throughput but risks data loss if the cache fails before persistence. It is common in high-throughput analytics and logging systems.

PatternRead LatencyWrite LatencyConsistency
Cache-asideFast on hitFast (DB only + invalidation)Eventual, simple
Read-throughFast on hitSame as DBEventual
Write-throughFastSlower (cache + DB)Strong
Write-behindFastFastestRisk of loss

best practice

Cache-aside is the safest default for most web applications. It keeps cache logic in the application, makes debugging easier, and avoids tight coupling between the cache and the database.
Cache Invalidation

There are only two hard things in computer science: cache invalidation and naming things. Invalidation keeps cached data consistent with the source of truth. The three main strategies are TTL expiration, active invalidation, and write-time update.

invalidation.txt
TEXT
1Invalidation strategies:
2
31. TTL (Time-To-Live)
4 - Set an expiration on each key
5 - Simple, but data can be stale until TTL expires
6 - Best for data where brief staleness is acceptable
7
82. Active invalidation
9 - Delete or update cache key on write
10 - Keeps cache fresh but adds write latency
11 - Risk: race conditions between read and write
12
133. Write-time update
14 - Update cache at the same time as database
15 - Used in write-through caching
16 - Cache and DB stay in sync

warning

Avoid the cache-invalidation race condition where a read repopulates a stale value after a write invalidated the key. Use a short deletion window, locking, or versioned cache values to prevent it.
Eviction Policies

When a cache reaches capacity, it must evict existing entries to make room. The eviction policy determines which entries to remove. The right policy depends on access patterns and the cost of a miss.

PolicyBehaviorBest For
LRUEvict least recently usedGeneral workload, temporal locality
LFUEvict least frequently usedStable popularity distributions
FIFOEvict oldest insertedStreaming data
TTL-basedEvict expired entries firstTime-sensitive data
RandomEvict random entrySimple, low overhead

info

Redis uses a near-LRU approximation by default. For most workloads, LRU or a sampled approximation is good enough. Only switch to LFU if you have long-tail access patterns and need to protect moderately popular items from one-time spikes.
Failure Modes

Caches fail too. A cache outage can suddenly redirect all traffic to the database, causing a cascading failure. Understanding failure modes helps you build resilient systems.

Cache Stampede

A cache stampede occurs when many requests simultaneously hit a missing cache key, all trying to recompute or refetch the value. This can overwhelm the database. Solutions include per-process locking, probabilistic early expiration, and single-flight request coalescing.

singleflight.txt
TEXT
1Stampede mitigation with single-flight:
2
3value = cache.get(key)
4if not value:
5 value = singleflight.do(key, () => db.query(key))
6 cache.set(key, value, ttl)
7
8The single-flight pattern ensures only one
9request computes the value; others wait.

Cache Penetration

Cache penetration happens when requests repeatedly ask for keys that do not exist. Since the cache cannot store a null result, every request hits the database. Store a sentinel value for missing keys with a short TTL, or validate keys before lookup.

Cache Avalanche

A cache avalanche occurs when many keys expire at the same time, causing a flood of database queries. Mitigate by adding jitter to TTLs, using staggered expiration, and pre-warming critical keys.

warning

Always design your system to survive a total cache failure. The database must be able to serve peak load even if every cache miss happens at once. If it cannot, the cache is a single point of failure in disguise.
Redis at Scale

Redis is the most common in-memory data structure store used for caching. It supports strings, hashes, lists, sets, sorted sets, bitmaps, and streams. Its sub-millisecond latency makes it ideal for hot data and real-time use cases.

FeatureUse Case
Strings with TTLSimple key-value cache
HashesCaching object fields
Sets / Sorted SetsLeaderboards, unique visitors
BitmapsDaily active user tracking
StreamsEvent sourcing, logs
🔥

pro tip

Enable Redis persistence only if losing the cache on restart is unacceptable. For pure caches, running without persistence gives higher throughput and simpler recovery: repopulate from the database on restart.
$Blueprint — Engineering Documentation·Section ID: SD-CACHE-01·Revision: 1.0