Caching
One-line summary: A cache stores copies of expensive-to-fetch data in fast storage so most requests are served in microseconds instead of hitting the database.
๐งฉ Core Conceptsโ
A cache is a high-speed data layer that stores a subset of data โ typically the most frequently or recently accessed โ so future requests are served faster. Caching trades a little memory and staleness risk for huge gains in latency, throughput, and reduced load on databases.
Why Cache?โ
- Reduce latency โ memory reads are ~1000ร faster than disk/DB.
- Increase throughput โ serve more requests with the same backend.
- Reduce cost & load โ fewer expensive DB/API calls, relieving read replicas.
Two key metrics: hit ratio (fraction served from cache) and miss penalty (cost of a miss).
๐๏ธ Cache Layersโ
Caching happens at every tier of a system:
flowchart LR
Client[Client / Browser Cache] --> CDN[CDN Edge Cache]
CDN --> App[Application / In-Memory Cache]
App --> DC[Distributed Cache<br/>Redis / Memcached]
DC --> DB[(Database + Query Cache)]
| Layer | Example | Caches |
|---|---|---|
| Client | Browser cache, mobile app | Static assets, API responses |
| CDN | Cloudflare, CloudFront | Images, CSS/JS, video, edge content |
| Application | Local in-process, Guava | Hot objects near the app |
| Distributed | Redis, Memcached | Shared cache across app servers |
| Database | Query/buffer cache | Recent query results, pages |
๐ Caching Strategiesโ
flowchart TD
R[Read Strategies] --> CA[Cache-Aside]
R --> RT[Read-Through]
W[Write Strategies] --> WT[Write-Through]
W --> WB[Write-Back]
W --> WA[Write-Around]
| Strategy | How It Works | Pros | Cons |
|---|---|---|---|
| Cache-Aside (lazy) | App checks cache; on miss, loads from DB and populates cache | Only caches what's used; resilient | First request is slow; stale risk |
| Read-Through | Cache library loads from DB on miss automatically | Simple app code | Needs cache provider support |
| Write-Through | Write to cache and DB synchronously | Cache always fresh | Higher write latency |
| Write-Back (write-behind) | Write to cache, flush to DB async later | Fast writes, absorbs bursts | Risk of data loss before flush |
| Write-Around | Write straight to DB, bypass cache | Avoids caching write-once data | Recent writes miss the cache |
๐ก Cache-aside + write-through is the most common combo for read-heavy web apps.
๐๏ธ Eviction Policiesโ
Caches are size-bounded, so they must evict entries when full:
| Policy | Evicts | Best For |
|---|---|---|
| LRU (Least Recently Used) | The item unused for the longest time | General-purpose; temporal locality |
| LFU (Least Frequently Used) | The least-accessed item | Stable popularity distributions |
| FIFO (First In, First Out) | The oldest inserted item | Simple, order-based workloads |
๐ See a full, worked LRU cache implementation here: LRU Cache.
โณ TTL (Time-To-Live)โ
A TTL sets an expiry on each entry so stale data auto-evicts:
- Short TTL โ fresher data, lower hit ratio.
- Long TTL โ higher hit ratio, more staleness.
- Add jitter to TTLs so many keys don't expire simultaneously (avoids thundering herd).
๐ชฒ Cache Invalidationโ
"There are only two hard things in Computer Science: cache invalidation and naming things."
Keeping the cache in sync with the source of truth:
- TTL / expiry โ let entries age out (simplest, eventually consistent).
- Write-through / write invalidation โ update or delete the cache key on every write.
- Event-based โ publish change events (e.g., via message queues) to invalidate keys.
- Versioned keys โ embed a version/hash in the key so new data uses a new key.
๐ฅ Thundering Herdโ
When a popular key expires (or the cache restarts), many concurrent requests miss at once and stampede the database.
flowchart TD
K[Hot key expires] --> M[1000s of misses]
M --> DB[(Database overload)]
Mitigations:
- Request coalescing / locking โ only one request recomputes; others wait.
- Stale-while-revalidate โ serve stale data while refreshing in the background.
- TTL jitter โ spread expirations over time.
- Cache warming โ pre-populate hot keys before traffic.
๐ Distributed Cachesโ
When one machine's memory isn't enough or the cache must be shared across app servers, use a distributed cache:
| Redis | Memcached | |
|---|---|---|
| Data types | Rich (strings, hashes, lists, sets, sorted sets) | Strings/blobs only |
| Persistence | โ Optional (RDB/AOF) | โ In-memory only |
| Replication | โ Built-in | โ (client-side) |
| Best for | Complex data, pub/sub, leaderboards | Simple, high-throughput KV cache |
- Data is partitioned across nodes using consistent hashing.
- Can be replicated for availability.
๐ฐ๏ธ CDN (Content Delivery Network)โ
A CDN caches static (and cacheable dynamic) content at edge servers geographically close to users, cutting latency and origin load.
flowchart LR
U1[User US] --> E1[Edge US]
U2[User EU] --> E2[Edge EU]
E1 -->|miss| O[(Origin Server)]
E2 -->|miss| O
- Great for images, video, CSS/JS, and downloads.
- Uses TTLs, cache-control headers, and purge APIs for invalidation.
๐ง Trade-offs / When to Useโ
- โ Use caching for read-heavy, tolerant-to-staleness data with high reuse (hot keys).
- โ Avoid / be careful with rapidly changing data, strong-consistency requirements, or low-reuse data (low hit ratio wastes memory).
- Every cache adds a consistency vs freshness trade-off โ pick strategy, TTL, and invalidation to match your correctness needs (see Consistency Models and CAP Theorem).
Interview Questionsโ
- How would you design a caching layer for a social feed to balance freshness and latency?
- Explain cache invalidation strategies and when you'd pick event-based invalidation over TTL-only.
- How do you mitigate and detect the thundering herd problem at scale?
Production Checklistโ
- Measure hit ratio, miss penalty, and memory usage per cache instance
- Add TTL jitter and request coalescing to prevent stampedes
- Backup critical cached state if persistence is enabled (AOF/RDB in Redis)
- Monitor eviction rates, CPU, and network latency to the cache cluster
Testing & Monitoringโ
- Simulate cache restart + expiry events to observe system behavior
- Load test with realistic access patterns and hot keys
- Create synthetic traffic to verify cache warming and invalidation flows
๐ Related Topicsโ
- โ Use caching for read-heavy, tolerant-to-staleness data with high reuse (hot keys).
- โ Avoid / be careful with rapidly changing data, strong-consistency requirements, or low-reuse data (low hit ratio wastes memory).
- Every cache adds a consistency vs freshness trade-off โ pick strategy, TTL, and invalidation to match your correctness needs (see Consistency Models and CAP Theorem).
๐ Related Topicsโ
- LRU Cache โ full implementation of the LRU eviction policy
- Databases โ the backing store caches protect
- Replication โ read replicas complement caching
- Sharding โ consistent hashing for distributed caches
- CAP Theorem โ consistency vs availability trade-offs
- Consistency Models โ staleness and consistency guarantees
- Scalability โ caching as a scaling lever
โ Back to System Design ยท ยฉ sparshjaswal