Skip to content

Caching


Caching is the process of storing frequently accessed data in a faster storage layer to reduce access time and decrease load on the primary data source.

Without Caching With Caching
=============== ==============
Request Request
| |
v v
+------+ +------+
| DB | (100ms) |Cache | (1ms)
+------+ +------+
|
v (if miss)
+------+
| DB |
+------+
Result: Up to 100x faster response!
BenefitImpact
Reduced Latencyms instead of seconds
Reduced LoadFewer database queries
Improved ThroughputHandle more requests
Cost SavingsFewer database resources

Client-Side Cache
=================
+--------------------------------------------------+
| User's Browser |
+--------------------------------------------------+
| +----------+ |
| | Local | - HTTP Cache |
| | Storage | - LocalStorage |
| +----------+ - IndexedDB |
| - Service Workers |
+--------------------------------------------------+
What's cached:
- Static assets (CSS, JS, images)
- API responses
- User preferences
CDN Caching
===========
User Request
|
v
+-----------------+
| CDN Edge |
| Location | (Check cache first)
+-----------------+
|
| (Cache miss)
v
+-----------------+
| Origin |
| Server |
+-----------------+
Cached:
- Static content
- Images, videos
- CSS, JavaScript
Application Cache
=================
+--------------------------------------------------+
| Application Server |
+--------------------------------------------------+
| |
| +------------------+ +------------------+ |
| | In-Memory | | Distributed | |
| | Cache | | Cache | |
| | (Local) | | (Redis/Mem- | |
| | | | cached) | |
| +------------------+ +------------------+ |
| |
+--------------------------------------------------+
Examples:
- In-memory: Guava Cache, Ehcache
- Distributed: Redis, Memcached
Database Caching
=================
+--------------------------------------------------+
| Database |
+--------------------------------------------------+
| |
| +------------------+ +------------------+ |
| | Query Cache | | Buffer Pool | |
| | (SQL results) | | (Data pages) | |
| +------------------+ +------------------+ |
| |
+--------------------------------------------------+
Example: MySQL InnoDB Buffer Pool

Cache-Aside Pattern
===================
Application Cache Database
| | |
| 1. Check cache | |
|------------------------>| |
| 2. Cache miss | |
|<------------------------| |
| | 3. Query DB |
| |---------------------->|
| | 4. Return data |
| |<----------------------|
| 5. Store in cache | |
|------------------------>| |
| | |
| 6. Return data | |
|<------------------------| |
Implementation:
```python
def get_data(key):
data = cache.get(key)
if data is None:
data = db.query(key)
cache.set(key, data)
return data
```
Pros: Simple, Only requested data is cached
Cons: Cache miss penalty, Stale data possible
Write-Through Pattern
=====================
Application Cache Database
| | |
| 1. Write data | |
|------------------------>| |
| 2. Write to DB | |
| |---------------------->|
| 3. Confirm | |
|<------------------------| |
| | |
Pros: Always consistent, Fast reads after write
Cons: Slower writes, Wasted resources for unwritten data
Write-Behind Pattern
====================
Application Cache Database
| | |
| 1. Write data | |
|------------------------>| |
| 2. Acknowledge | |
|<------------------------| |
| | (Async) |
| | 3. Write to DB |
| |---------------------->|
Pros: Fast writes, Batch DB operations
Cons: Risk of data loss if cache fails
Refresh-Ahead Pattern
=====================
TTL: 60 seconds
Refresh: 80% of TTL (48 seconds)
Timeline:
0s 10s 20s 30s 40s 48s 50s 60s
|-----|-----|-----|-----|-----|-----|-----|
| | | | | | | |
Data Data Data Data Data REFRESH Data Data
cached cached
Pros: Reduces cache miss latency
Cons: Complex to implement

PolicyDescriptionUse Case
LRU (Least Recently Used)Evict least recently accessedGeneral purpose
LFU (Least Frequently Used)Evict least frequently accessedPopular items
FIFO (First In First Out)Evict oldestSimple cases
TTL (Time To Live)Evict after expirationTime-sensitive data
RandomEvict randomTesting
LRU (Least Recently Used)
==========================
Access Order: A -> B -> C -> D -> E -> A
Before:
+---+---+---+---+
| A | B | C | D | (Full)
+---+---+---+---+
Access E (new):
+---+---+---+---+
| B | C | D | E | (A evicted)
+---+---+---+---+

TTL Implementation
===================
Cache Entry:
+----------------------------------+
| Key: "user:123" |
| Value: {name: "John", ...} |
| TTL: 300 seconds |
| Created: 2024-01-15 10:00:00 |
+----------------------------------+
TTL Strategies:
+------------------+----------+------------------------+
| Data Type | TTL | Reason |
+------------------+----------+------------------------+
| User session | 24 hours | Session expiry |
| Product catalog | 1 hour | Inventory changes |
| Configuration | 1 day | Infrequently changes |
| API response | 5 min | Real-time data needed |
| Static assets | 1 week | CDN caching |
+------------------+----------+------------------------+

Cache Invalidation
==================
"There are only two hard things in computer science:
cache invalidation and naming things."
- Phil Karlton
Problem: How to keep cache consistent with database?
Scenarios:
1. Update data -> Invalidate cache
2. Delete data -> Invalidate cache
3. Data expires -> Natural expiration
Strategy 1: Delete on Write
===========================
User updates profile -> Delete cached profile
|
v
Next read -> Cache miss -> Fetch from DB -> Cache again
Strategy 2: Event-Based Invalidation
====================================
Database -> Event -> Cache Service
Update Bus |
Invalidate
Cache
Strategy 3: Versioning
====================
Key: "user:123:v2"
Update -> Increment version (v2)
Old clients still use v1 (eventually expires)
New clients use v2

Redis Cluster
=============
Client
|
v
+------------------+
| Redis Proxy | (Route requests)
+------------------+
|
+---+---+---+---+---+
| | | | | |
v v v v v v
P0 P1 P2 P3 P4 P5 (16384 hash slots)
| | | | | |
v v v v v v
S0 S1 S0 S1 S0 S1 (Primary/Replica)
Data Distribution:
Key "user:123" -> Hash -> Slot -> Node
FeatureMemcachedRedis
Data TypesStringsStrings, Lists, Sets, Hashes, Sorted Sets
PersistenceNoRDB, AOF
ClusteringMemcached SaaSNative Cluster
PerformanceVery FastFast
MemoryLRUCustom
Use CaseSimple cachingCaching + more

Cache Miss Types
================
1. Cold Start / Cold Cache
- First request after deployment
- All requests hit database
- Solution: Pre-warm cache
2. Eviction
- Old data removed to make room
- Normal behavior
- Solution: Adjust cache size
3. Key Not Found
- Data never cached
- Solution: Check cache logic
Cache Stampede Problem
======================
1. Many requests hit cache miss simultaneously
2. All query the database at the same time
3. Database overwhelmed
Timeline:
Request1 ----> Cache Miss -> DB Query
Request2 ----> Cache Miss -> DB Query
Request3 ----> Cache Miss -> DB Query
...
Request100 --> Cache Miss -> DB Query
|
v
Database
Crashes!
Solution: Distributed Lock or Request Coalescing
================================================
Request1 ----> Cache Miss -> Lock -> DB Query -> Cache Set -> Release
Request2 ----> Cache Miss -> Wait -> Wait -> Wait -> Cache Hit!
Request3 ----> Cache Miss -> Wait -> Wait -> Wait -> Cache Hit!
Thundering Herd
===============
Same as Cache Stampede but on cache EXPIRY
TTL expires for key "popular_product"
All 1000 users request simultaneously
All hit database at same time
Solution: Jitter + Extension
============================
TTL = 60 seconds
Random jitter = 0-20 seconds
Actual TTL = 60-80 seconds (random)
Each instance has different TTL
Reduces simultaneous expiry
Stale Data Problem
==================
User changes password
Cache still has old password
User can't login until cache expires
Solutions:
1. Write-through (update cache on write)
2. Event-driven invalidation
3. Short TTL for sensitive data
4. Versioned cache keys

Multi-Level Cache Architecture
===============================
Request
|
v
+--------------------------------------------------+
| L1 Cache (Local) |
| - In-memory, fast |
| - Small size (GB) |
+--------------------------------------------------+
| (miss)
v
+--------------------------------------------------+
| L2 Cache (Distributed) |
| - Redis/Memcached |
| - Larger size (TB) |
+--------------------------------------------------+
| (miss)
v
+--------------------------------------------------+
| Database |
| - Persistent storage |
| - Slowest |
+--------------------------------------------------+
Performance:
L1: 0.1ms
L2: 1ms
DB: 100ms

DoDon’t
Use for read-heavy workloadsUse for frequently changing data
Set appropriate TTLsCache without TTL
Monitor cache hit rateCache everything
Handle cache failures gracefullyLet cache failure crash app
Pre-warm after deploymentStart with empty cache
Use consistent hashing in clustersCache without eviction policy

Key caching concepts:

  1. Choose the right strategy - Cache-aside, write-through, or write-behind
  2. Set appropriate TTLs - Balance freshness with performance
  3. Handle invalidation - Keep cache consistent with database
  4. Prevent stampedes - Use locks or request coalescing
  5. Monitor metrics - Hit rate, miss rate, latency
  6. Plan for failures - Cache can fail; design gracefully

Next: Chapter 6: CAP Theorem & Distributed Systems