Distributed Caching
Caching is often the difference between a system that handles 100 QPS and one that handles 1 million QPS. This module covers everything from cache fundamentals to advanced distributed caching patterns. The fundamental insight behind caching is straightforward: if you ask the same question repeatedly, remembering the answer is faster than re-computing it. It is the same reason you keep your phone number in contacts instead of looking it up in the phone book every time. But distributed caching introduces a twist that makes it one of the hardest problems in systems design: when the underlying answer changes, how do you make sure every cached copy is updated? This is the cache invalidation problem, and it is where most production cache bugs live.Track Duration: 10-14 hours
Key Topics: Cache Strategies, Consistency, Invalidation, Redis, Memcached, Cache Stampede
Interview Focus: Cache consistency, invalidation strategies, hot key handling
Key Topics: Cache Strategies, Consistency, Invalidation, Redis, Memcached, Cache Stampede
Interview Focus: Cache consistency, invalidation strategies, hot key handling
Why Caching Matters
Cache Strategies
Read Strategies
- Cache-Aside (Lazy Loading)
- Read-Through
- Refresh-Ahead
Application manages cache explicitly. Most common pattern.Pros: Simple, only caches what’s needed, cache failures don’t break reads
Cons: Cache miss = slow first read, potential for stale data
Write Strategies
- Write-Through
- Write-Behind (Write-Back)
- Write-Around
Write to cache AND database synchronously.Pros: Cache always consistent, no stale data
Cons: Higher write latency, both must succeed
Cache Invalidation
Invalidation Strategies
The Delete vs Update Debate
Cache Stampede (Thundering Herd)
When a popular cache key expires, many requests simultaneously hit the database.Stampede Prevention Strategies
- Locking (Single Flight)
- Probabilistic Early Expiry
- External Lock (Redis Lock)
- Stale-While-Revalidate
Only one request fetches, others wait.Pros: Prevents duplicate DB queries
Cons: All waiters blocked on one request
Hot Key Problem
When one key gets disproportionate traffic.Hot Key Solutions
- Local Cache (L1)
- Key Replication
- Request Coalescing
Add in-process cache before Redis.Pros: Extremely fast, reduces L2 load
Cons: Inconsistency between instances, memory usage
Advanced: Sidecar Caching & The “Mesh” Pattern
At Staff/Principal level, you must move beyond the “application-manages-cache” (Cache-Aside) model. In a microservices architecture, managing cache logic in every service leads to duplication and inconsistency.1. Sidecar Caching (e.g., Pelikan, Mcrouter)
Instead of the application connecting directly to Redis, it connects to a Local Sidecar Proxy.- Benefits:
- Language Agnostic: The sidecar handles complex logic (consistent hashing, retries, failover).
- Connection Pooling: Reduces the number of open connections to the central Redis cluster.
- Request Coalescing: The sidecar can merge multiple identical requests from the local app into one upstream request.
2. EVCache (The Netflix Pattern)
EVCache is a distributed, sharded, replicated in-memory caching system based on Memcached.- Sidecar-based: Every EC2 instance has an EVCache sidecar.
- Global Replication: Writes are replicated asynchronously to other AWS regions, allowing for Local Reads globally.
3. Layered Cache Hierarchy (EVCache vs. Redis)
Distributed Cache Architectures
Redis Cluster
Memcached vs Redis
Cache Consistency Patterns
Eventual Consistency
Double-Delete Implementation
Advanced Design Scenarios
Scenario 1: Multi-Level Caching for Global APIs
Your company exposes a global read-heavy API (product catalog, content, or configuration) with users across multiple regions. You want sub-100ms p99 latencies while still keeping a single source-of-truth database.- Cache as many layers as possible:
- Edge (CDN): cache public, cacheable responses with long TTLs and
ETag/Last-Modifiedfor validation. - Regional gateway (L1.5): shared Redis or Envoy cache per region for JSON APIs.
- App instances (L2): small in-process L1 caches for the hottest keys.
- Edge (CDN): cache public, cacheable responses with long TTLs and
- Key design:
- Include versioning in keys for global invalidation:
product:{id}:v{schema_version}. - For per-tenant data:
tenant:{tenant_id}:product:{id}:v{version}.
- Include versioning in keys for global invalidation:
- Invalidation:
- For rarely-changing data, use long TTLs + explicit purge when changes happen.
- On update, clear from inner layers first (DB → regional Redis → CDN purge API).
- CDN down or misbehaving: have monitoring that can bypass CDN and hit regional gateway directly.
- Regional cache cluster degraded: instances fall back to L1 in-process cache + direct DB reads with tighter rate limits.
- Schema changes: bump
schema_versionin cache keys to avoid mixed old/new value formats.
Scenario 2: Configuration and Feature Flag Caching
You have a central configuration/feature flag service that many microservices depend on. Latency spikes or downtime in this service must not take down the entire fleet. Requirements:- Services should start even if the config service is temporarily unavailable.
- Each service instance should cache configuration locally and refresh in the background.
- Changes should propagate within seconds, not minutes.
- Source of truth: configuration stored in a durable DB (e.g., Postgres) behind a config service.
- Distributed cache: Redis/Etcd used by the config service to cache hot config blobs (
config:service_name). - Service-local cache: each microservice maintains an in-process cache of parsed config.
- Notification channel: a message bus (Kafka, SNS/SQS, Redis Pub/Sub) carries “config changed” events.
- On startup, services block briefly to fetch essential config; thereafter they serve from local memory.
- If the config service and cache are both down, services can keep using the last known good config in memory.
- For safety-sensitive flags, include expiry timestamps so services can disable risky behavior if config has not refreshed in too long.
Scenario 3: Bounded-Staleness Caching on Top of a Strong Database
Sometimes you want strictly serializable writes but are happy to serve data up to (T) seconds old for reads. This is common for dashboards, search results, and secondary views. Goal: “Reads may be stale by at most T seconds, never more. Writes must always see their own effect.” Design:- Keep strong consistency in the primary DB (e.g., Spanner/CockroachDB/Postgres with serializable isolation).
- Layer a cache in front with staleness metadata per key.
- On write, delete cache (or write a fresh
CachedValuewith currentlast_write_time). - If you have a write-heavy workload, combine this with double-delete or CDC-backed invalidation to avoid stale repopulation races.
- You can tighten or loosen
MAX_STALENESS_SECper endpoint: 1–2s for user-facing dashboards, 30–60s for admin analytics. - Reads that cannot tolerate any staleness bypass cache entirely and always hit the DB.
Interview Questions
Q: How do you handle cache stampede for a popular product page?
Q: How do you handle cache stampede for a popular product page?
Solutions in order of sophistication:
- Single-flight: Only one DB query, others wait
- Lock with fallback: If can’t get lock, serve stale
- Probabilistic refresh: Spread expiry over time
- Stale-while-revalidate: Always serve stale, refresh async
- Always fast response (stale is OK for products)
- Background refresh prevents stampede
- Single-flight prevents duplicate refreshes
Q: When would you use cache-aside vs write-through?
Q: When would you use cache-aside vs write-through?
Cache-Aside (lazy loading):
- Read-heavy workloads
- When not all data needs to be cached
- When cache failure shouldn’t break writes
- Most common for web applications
- When cache consistency is critical
- Write-heavy workloads where you need fast subsequent reads
- When you can tolerate higher write latency
- Write-heavy, read-light workloads
- When you can batch database writes
- When you can tolerate potential data loss
- Gaming leaderboards, view counts, etc.
Key Takeaways
Cache Strategically
Not everything needs caching. Cache hot paths where latency matters most.
Plan for Invalidation
Design invalidation strategy upfront. Delete is usually safer than update.
Handle the Stampede
Popular keys + expiration = danger. Use locking, probabilities, or stale serving.
Accept Eventual Consistency
For most use cases, eventual is fine. Build UX around it.