Client facing:
GET /any/path HTTP/1.1
Host: www.example.com
Response (cache hit):
HTTP/1.1 200 OK
X-Cache: HIT
Age: 45
Cache-Control: max-age=300
Content-Type: text/html
...body...
miss:
Response (cache miss)
HTTP/1.1 200 OK
X-Cache: MISS
Cache-Control: max-age=300
...body fetched from origin...
Invalidation API (origin -> cache)
PURGE /articles/123
Host: www.example.com
Response: 204 No Content
POST /v1/cache/invalidate
Body: {"patterns":["/articles/*]. "reason": "deploy-v2.3"}
Response: {"purged_count": 1482}
Admin/Observability
GET /v1/cache/stats
Response: {
"hit_ratio":
entries
memory_used_gb
memory_total_gb
evictions_per_sec
}
The cache sits between clients and origin servers as a reverse proxy.
Client -> LB -> cache layer -> origin server
Key design decisions:
Components:
// The HTTP Vary header tells caching servers which request headers to consider when determining if a cached response can be served. It ensures that clients—like browsers—receive the correct content (such as compressed files, mobile layouts, or specific languages) even when the URL remains the same.
Cache lookup and stoarge engine
Datastructure: Hash Map + LRU/LFU hybrid (W-TinyLFU)
Standard LRU evicts recently inserted but infrequent items too slowly, and promotes one-hit wonders. LFU doesn't adapt to changing access patterns. We use W-TinyLFU (used by caffeine/varnish)
WIndow Cache (1% of cap, LRU) -> new entries land here
Main Cache (99% of cap, segmented LRU) - probation segment (20%), protected segment (80%) -> promoted form window if frequency > victim
TinyLFU Frequency Sketch (Count-Min Sketch)
Admission policy: When the window cache is full, the candidate for promotion to main cache is compared against the eviction victim from main cache using the frequency sketch. Higher frequency wins. This filters out scan pollution (one-hit content that woudl evict valuable entries)
Cache key: hash(method+Url+Vary). Vary heder means the same URL may have multiple cached variants (gzip vs bratli)
Storage layout:
2. Consistency and invalidation
Problem: Cached content can become stale. Three mechanisms, used together:
A. TTL-based expiratoin (passive)
B. Active Invalidation (push)
C. Conditional revalidation (pull)
GET /articles/123
If-None-Match: "etag-abc"
If-Modified-Since: Tue, 19 May 2026
Origin responses 304 Not Modified (no body) -> cache refreshes TTL. Saves bandwidth.
Consistency guarantee: with active invalidation + short TTLs, worst case staleness = invalidation propagation delay (100ms). For non-critical content, TTL-only if sufficient (seconds to minutes of staleness acceptable)
3. Thundering Herd Proection (origin shield/request coalescing)
Problem: A popular URL expires or gets purged. 10k concurrent requests arrive. Without protection, all 10k hit the origin simultaneously - potentially killing it.
Solution: Request coalescing (also called collapsed forwarding)
Req 1 for /popular-page -> cache miss -> lock acquired -> fetch from origin. Req 2 -> wait, etc.
...
origin responds -> store in cache -> wake all waiters -> serve from cache
Implementation:
Origin shield(optional second tier)
Key Tradeoffs
Consistent hashing: higher per node miss impact on failure vs no duplication, maximum effective capacity
W-TinyLFU vs plain LRU: slightly more cpu per access vs sig higher hit ratio 5-10% improvement
Two-tier RAM + SSD: complexity of tiered storage vs 10-50x more cap at marginal latency cost for warm content
Active invalidation via pubsub: Operational complexity vs sub-second consistency
Request coalescing: Added latency for waiters (they block) vs origin protection
Conditional revalidation (ETags): Extra round trip on expiry vs bandwidth savings (no body transfer on 304)
Consistent hash with virtual nodes: Rebalancing moves 1/N content on node add/remove vs full reshuffle