Networking & Web Fundamentals
Caching Strategies
Cache-aside, write-through, and write-back patterns for reads and writes.
In short
Cache-aside, write-through, and write-back patterns for reads and writes.
A caching strategy defines the data flow patterns between the application layer, the caching layer, and the underlying database. Choosing the right pattern is critical to balance data consistency, write latency, read speed, and the overall load on your primary data store.
1. Learning Objectives
- Identify and explain the five major caching strategies: Cache-Aside, Read-Through, Write-Through, Write-Around, and Write-Back.
- Analyze the latency, throughput, and consistency trade-offs of each caching strategy.
- Evaluate consistency models (strong consistency vs. eventual consistency) across different strategies.
- Identify and mitigate common caching failures, including cache stampedes, cache avalanches, and cache penetration.
- Implement thread-safe caching patterns with proper TTLs, locking, and fallback routines in code.
2. Prerequisites
- A foundational understanding of caching concepts covered in Caching Fundamentals.
- Basic familiarity with relational databases (e.g., PostgreSQL, MySQL) and SQL queries.
- Understanding of in-memory key-value stores (e.g., Redis, Memcached) and their basic commands (GET, SET, DEL).
- Basic comprehension of network latency, round-trip time (RTT), and thread concurrency.
3. Why This Topic Matters
At scale, the primary performance bottleneck in any application architecture is almost always the database. Disk operations, table locks, complex joins, and index traversals limit the throughput of relational databases to a few thousand queries per second per instance. In contrast, in-memory caches like Redis can easily serve over 100,000 requests per second per node with sub-millisecond latencies.
However, introducing a caching layer creates a classic distributed systems problem: state replication. Having data in two places (the database and the cache) inevitably leads to synchronization challenges. If your caching strategy is poorly chosen, your application will serve stale information, suffer from write bottlenecks, or experience catastrophic failure when the cache goes down. Understanding these strategies is the difference between building a platform that scales smoothly to millions of active users and one that suffers from frequent data corruption or database collapse.
4. Real-world Analogy
Imagine running a busy doctor's office with a patient records filing system:
- Cache-Aside (Lazy Loading): The receptionist keeps a small folder holder on their desk (cache). When a patient arrives, the receptionist checks the desk folder holder first. If the file is not there, they walk to the back filing room (database), retrieve the physical folder, make a copy for the desk holder, and use it. Future visits for this patient will be fast, but the initial visit incurs a lookup delay.
- Read-Through: The receptionist hires an assistant who handles all folder retrievals. The receptionist only asks the assistant for folders. If the assistant doesn't have it on the desk, they run to the back filing room, retrieve it, place it on the desk, and hand it to the receptionist. The receptionist does not care how or where the folder was fetched.
- Write-Through: When a doctor updates a patient's prescription, they write it on the desk folder and immediately walk to the back room to update the master file cabinet. Both copies are always completely synchronized, ensuring high consistency but slowing down the checkout process.
- Write-Around: When the office gets marketing mail or billing updates that doctors rarely read, the receptionist files them directly in the back filing room, completely skipping the desk holder. This avoids wasting desk space on rarely accessed documents.
- Write-Back (Write-Behind): The doctor writes updates on a temporary notepad on the desk. They finish seeing patients rapidly, and at the end of the shift, they bundle all notepad updates and file them in the back filing room in a single batch. This is extremely fast, but if the building catches fire before the shift ends, the temporary notepad updates are lost.
5. Core Concepts
Before analyzing individual strategies, it is essential to understand the primary terminologies and mechanism metrics:
- Cache Hit Ratio: The percentage of read requests successfully served by the cache. Calculated as
Hits / (Hits + Misses). Higher hit ratios (typically >85%) denote an efficient caching setup. - Eviction Policies: The algorithms (e.g., LRU - Least Recently Used, LFU - Least Frequently Used) that determine which keys to delete when the cache runs out of memory. These are discussed in detail in Cache Eviction Policies.
- Cache Invalidation: The process of removing or updating cached data when the source database changes to prevent serving stale data.
- Dual Writes: The pattern where an application must write to two different systems (the cache and the database), which poses transaction boundaries and synchronization risks.
- Time-to-Live (TTL): A threshold timer attached to cached data. Once the TTL expires, the key is evicted, forcing the system to retrieve fresh data from the database.
6. Visualization
Below is an interactive diagram area reserved for caching strategies visual flows. It maps the visual interactions between client, server, cache, and database layers.
Cache-Aside Read Flow
In a cache-aside pattern, the application is responsible for orchestrating reads and writes to both the cache and the database. The cache does not talk to the database directly.
Write-Back (Write-Behind) Write Flow
In a write-back caching strategy, writes go directly to the cache. An asynchronous worker background thread periodically flushes accumulated updates to the database in bulk.
7. How It Works
Cache-Aside (Lazy Loading)
In this pattern, the application is responsible for managing both the database and the cache. The cache is passive and does not interact with the database directly.
- The application receives a read request.
- The application queries the cache. If it hits, the data is returned to the client.
- If a cache miss occurs, the application queries the database, saves the result back in the cache (with a TTL), and returns it to the client.
- For write requests, the application updates the database directly and invalidates (deletes) the cache entry.
Read-Through & Write-Through
In these strategies, the application delegates caching logic to a dedicated cache client or middleware. The cache acts as the main database interface for the application.
- Read-Through: When the app queries the cache client, a miss triggers the cache client to fetch the data from the database internally, store it, and return it to the app.
- Write-Through: The application writes data directly to the cache client. The cache client updates the cache and immediately writes to the database in the same synchronous transaction block before confirming success to the application.
Write-Around
Write-around is combined with cache-aside or read-through to handle write-heavy but read-infrequent data.
- The application writes new data or updates directly to the database.
- The cache is bypassed completely (no key creation or update).
- The data is only cached if a read request for it occurs later, causing a cache miss and subsequent population.
Write-Back (Write-Behind)
Write-back optimizes write throughput by prioritizing the cache layer over the persistent database layer.
- The application writes data directly to the cache.
- The cache layer registers the write and immediately returns a success status to the application.
- The update is queued in a memory buffer or message queue.
- A background worker pulls updates from the queue and flushes them to the database in batches, asynchronously.
8. Internal Architecture
A resilient caching system consists of multiple components cooperating to maintain low-latency lookups and data durability. Below is a breakdown of the architectural components:
| Component | Responsibility | Key Failure Point | Mitigation |
|---|---|---|---|
| Application Host | Runs business logic, determines caching strategies, and marshals objects. | Connection pool exhaustion under heavy traffic. | Proper sizing of connection pools, circuit breakers, and rate limiting. |
| Cache Client Library | Handles serialization, connection keep-alives, hashing, and command execution. | High serialization CPU overhead or connection leaks. | Using binary serialization formats (e.g., Protobuf) and reused client instances. |
| Cache Cluster (Redis/Memcached) | Stores key-value data in RAM, handles TTL expiration, and evicts cold data. | Out-of-memory (OOM) crashes or cluster leader failures. | Configuring eviction policies (e.g., volatile-lru), master-replica clustering, and sentinel nodes. |
| Database (SQL/NoSQL) | Acts as the persistent system of record (single source of truth). | Disk I/O saturation during cache misses or stampedes. | Read replicas, query indexes, and connection pooling. |
| Write-Behind Queue / Worker | Buffers and flushes writes to the database asynchronously (for Write-Back). | Queue memory overflow or worker crashing before flushing. | Using persistent queues (e.g., Kafka) and implementing idempotent database writes. |
9. Request Lifecycle
Request Lifecycle: Cache-Aside Read Miss
- The Client issues a request:
GET /items/99. - The load balancer routes the request to an Application Server instance.
- The application checks the cache for key
item:99. - The Cache returns a miss (key does not exist or expired).
- The application executes a database query:
SELECT * FROM items WHERE id = 99. - The Database executes the query on disk/buffer pool and returns the row.
- The application serializes the database row to JSON or a binary format.
- The application calls the Cache client:
SETEX item:99 3600 <serialized_data>. - The Cache saves the data in memory and sets an expiration timestamp of 3600 seconds.
- The application sends a
200 OKresponse to the client.
Request Lifecycle: Write-Back Write
- The Client sends a write request:
PUT /items/99 {"price": 200}. - The application receives the request, parses the payload, and validates it.
- The application updates the cache entry immediately:
SET item:99 {"price": 200}. - The application pushes a job to a local thread queue or message broker:
"update_price", id: 99, value: 200. - The application returns a
200 OKresponse to the client immediately (latency ~5ms). - In the background, a worker process aggregates updates and executes a batch write to the Database:
UPDATE items SET price = 200 WHERE id = 99.
10. Deep Dive
The Dual-Write Problem and Invalidation Race Conditions
In Cache-Aside architectures, we must write to two separate systems: the database and the cache. Because distributed transactions across these systems are extremely slow and complex, we cannot easily coordinate them in a single ACID transaction. This leads to consistency race conditions:
- Race Condition 1: Invalidate Before DB Update: If you delete the cache key before writing to the database, a concurrent read request might hit the cache, experience a miss, read the old value from the database, write that old value back into the cache, and then the database write completes. The cache now contains stale data indefinitely (until eviction or TTL).
- Race Condition 2: Invalidate After DB Update: If you delete the cache key after writing to the database, a read query could theoretically read an old value from the cache just before you delete it. This is a short-lived inconsistency window. However, a worse race occurs if a read-miss queries the database (getting old data), a write updates the database and deletes the cache key, and then the read process writes the old data into the cache. This is rare because database updates are typically slower than cache writes, but it can happen under high concurrency.
Solution: The industry standard for Cache-Aside is to write to the database first, and then delete (invalidate) the cache key. To protect against the rare read-miss write race, developers use short TTLs, or employ a cache leasing mechanism like Memcached leases.
Cache Stampede (Thundering Herd) Mitigation
A cache stampede occurs when a highly requested hot key expires. Suddenly, thousands of concurrent requests read-miss and hit the database at the same time, causing CPU spikes, network congestion, and database lockouts.
There are two primary mitigation techniques:
- Mutex Locking (Single Flight): When a cache miss occurs, the application attempts to acquire a distributed lock (e.g., Redis
SETNX lock:key). Only the thread that acquires the lock is permitted to query the database and update the cache. All other threads sleep for a brief duration and retry reading from the cache. - Probabilistic Early Expiration (XFetch): Instead of waiting for a key to expire, the system uses a probabilistic algorithm to renew the key early. When reading a key, the client calculates a probability based on the key's TTL, the time it took to calculate the key originally (computation time), and a constant beta factor. If the probability triggers, a background task is spawned to re-fetch and renew the key before it officially expires, preventing any cache miss from hitting the main request threads.
11. Production Example
Facebook's Scaling with Memcached (Cache-Aside)
Facebook operates one of the largest caching layers in the world, deploying thousands of Memcached nodes to handle billions of requests per second. They use a Cache-Aside strategy, but have modified it extensively to handle production edge cases:
- Leases: To prevent cache stampedes and stale writes, Facebook's Memcached returns a token (lease) when a client experiences a cache miss. The client can only write back to the cache if it provides the lease token. If another client has already been granted a lease for that key within a short window, the cache tells the client to wait, avoiding redundant database lookups.
- McSqueal: A daemon that processes database commit logs (WAL) to invalidate Memcached keys asynchronously. Instead of application code invalidating the cache, the database replication pipeline handles cache invalidation, ensuring the cache is never updated with uncommitted database changes.
Netflix and EVCache (Write-Through/Around)
Netflix uses EVCache, a specialized caching client built on top of Memcached, to store personalization metadata and recommendation graphs. Because reads are highly frequent, Netflix writes updates to all global replication regions synchronously (Write-Through style) to guarantee cache hits, but relies on Write-Around for low-frequency administrative data to optimize memory usage.
12. Advantages
- Cache-Aside: Resilience to cache failure. If the cache layer goes down, traffic falls back to the database directly (though the database will face higher load). It also ensures that only requested data is cached, maximizing memory efficiency.
- Write-Through: Extremely high read-performance because the cache is always warm with the latest database state. Simplifies application code since caching is handled transparently by the cache provider.
- Write-Back: Unmatched write throughput. Writes take only a few milliseconds because they write to RAM, and database disk I/O is reduced via batching and serialization consolidation.
13. Limitations
- Cache-Aside: First-time read latencies are higher because they incur a cache miss. Complex application logic is required to manage cache invalidations and state synchronization.
- Write-Through: Higher write latency. Every write requires two network round trips and a database disk write before returning success.
- Write-Back: Risk of data loss. If the cache cluster crashes or power is cut before the background worker flushes queued updates to the database, data is lost permanently. This strategy is also complex to implement safely.
14. Trade-offs
Write Latency vs. Data Consistency
Choosing a write strategy represents a fundamental trade-off. Write-Back minimizes write latency by sacrificing immediate consistency and durability. Conversely, Write-Through guarantees strong consistency and durability at the expense of higher write latency. Cache-Aside + Invalidation offers a middle ground, but leaves a small window where users may read stale data.
Memory Costs vs. Cache Hit Ratio
Keeping all active data cached maximizes the Hit Ratio, but RAM is expensive. Evicting data aggressively saves infrastructure costs but increases the number of database misses, driving up database CPU usage and request latency. Architectural designs must balance cache capacity (eviction margins) against database scalability limits.
15. Performance Considerations
- Serialization Overhead: Converting objects to string-based JSON or XML for caching consumes significant CPU cycles. Consider binary serialization formats like Protocol Buffers, MessagePack, or Avro for high-throughput nodes.
- Network Roundtrips: Making sequential cache checks can accumulate latency. Use Redis pipelining or bulk commands (MGET/MSET) to batch requests into a single network packet.
- Connection Pools: Establishing a TCP connection to Redis or Memcached on every request adds 1-2ms. Maintain a persistent connection pool at the application level to reuse connections.
- Memory Fragmentation: Redis storing values of widely varying sizes can cause memory fragmentation, leading to premature OOM. Storing consistently sized serialized buffers mitigates this.
16. Failure Scenarios
Cache Avalanche
A Cache Avalanche occurs when the cache layer crashes, or when a large set of keys expire simultaneously. The database is suddenly flooded with all read traffic, causing the database to run out of connections or disk throughput, leading to cascading application failure.
Mitigation: Add random jitter (e.g., 5-10 minutes) to TTLs so they don't expire simultaneously. Implement rate limits, circuit breakers, and database replication clusters.
Cache Penetration
Cache Penetration happens when clients request keys that do not exist in the database (e.g., querying for user ID -999 or random UUIDs). Because the keys don't exist, they are never cached, and every request bypasses the cache and hits the database.
Mitigation: Cache empty/null results with a very short TTL (e.g., 5 minutes), or use a Bloom filter in front of the cache to quickly verify if the key exists in the database dataset.
17. Best Practices
- Always Set a TTL: Never store keys indefinitely. TTLs are a safety net that bounds data inconsistency even if cache invalidation logic fails.
- Jitter Expiration Times: Prevent cache avalanches by calculating TTLs with a small random variance (e.g.,
TTL = BaseTTL + random(0, 300)). - Cache Empty Values: Prevent cache penetration by writing a dummy value like
"__NULL__"to the cache on database misses. - Evict Cache on Write: For Cache-Aside, invalidate the cache key by deleting it rather than updating it. Deletes are idempotent and prevent race conditions.
- Decouple Write-Back Queues: Do not use in-memory arrays for Write-Back queues in production. Use a durable message broker (like Apache Kafka or RabbitMQ) to guarantee message retention during server crashes.
18. Common Mistakes
- Invalidating Cache Before DB Commits: If you delete the cache key before the database transaction is committed, concurrent readers will query the database, get the old uncommitted state, and repopulate the cache with it.
- Using Write-Back for Financial Records: Using asynchronous write queues for transaction ledgers or checkout carts is a recipe for data loss if the cache node crashes. Use Write-Through or direct DB writes for sensitive transactions.
- Caching Huge Objects: Caching full HTML page structures or large binary blobs consumes RAM quickly and triggers high memory eviction rates. Cache only structured primitive models.
- Ignoring Cache Miss Monitoring: Failing to alert on sudden drops in Cache Hit Ratio can hide severe bugs like cache penetration or misconfigured TTLs.
19. Implementation
The following TypeScript implementation demonstrates the runtime mechanics and latency differences between Cache-Aside, Write-Through, and Write-Back strategies. Copy and run this file locally using a TypeScript executor (e.g., ts-node).
20. Interview Questions
Easy: What is the main difference between write-through and write-back caching?
Answer: The difference lies in the synchronization timing of database updates. In write-through caching, writes are synchronous; data is written to the cache and the database simultaneously, and the operation only completes when both write transactions succeed. In write-back (write-behind), the update is written immediately to the cache (RAM) and confirmed to the caller, while a background thread asynchronously flushes updates to the database later in batches. Write-back has lower latency but introduces a risk of data loss if the cache node crashes before updates are flushed.
Medium: Why is it generally better to delete a cache key rather than update it during a database update in Cache-Aside?
Answer: Updating the cache key directly introduces a concurrency race condition. If two clients update the same record simultaneously, client A's update could write to the DB first, then client B writes to the DB. If network routing delays client A's cache update, client B might update the cache first, followed by client A's stale update. This leaves the cache with client A's old data while the database contains client B's new data. Deleting the cache key is idempotent and safe: it forces the next reader to pull the latest truth directly from the database and populate the cache safely.
Hard: In Cache-Aside, even if we invalidate the cache key *after* updating the database, a race condition can still result in stale cache data. Explain this scenario and how to prevent it.
Answer: The race condition occurs when a read and write request interleave under high concurrency:
- Client A attempts to read a record and experiences a cache miss. It reads the current value (e.g., V1) from the database.
- Before Client A can write V1 back to the cache, Client B performs a write operation, updates the database to V2, and successfully deletes the cache key (which was already empty).
- Client A finally completes its network request and writes V1 back to the cache. The cache now contains stale value V1, while the database has value V2.
This is rare because Client A's step 3 (writing to the cache) must take longer than Client B's entire database write and cache deletion. However, it can happen during network congestion. To prevent this, you can set a short TTL on cache keys to bound the stale window, use mutual exclusion locking (mutex) on cache misses so Client A must hold a lock, or use Memcached leases to invalidate old write attempts.
21. Practice Exercises
- Easy: Draw a sequence diagram mapping the request lifecycle for a Write-Around cache strategy when a write occurs, followed by a read-miss.
- Medium: Modify the provided TypeScript implementation to implement a basic mutex lock (single flight mechanism) during a cache miss to prevent multiple concurrent database requests.
- Hard: Design the failure recovery architecture for a Write-Back caching engine. Specify how the system detects cache crashes and recovers pending writes stored in memory without losing data.
22. Challenge Problem
You are the lead architect of a global ticketing system. During a flash concert ticket sale, you expect a single hot ticket key (e.g., concert:taylor-swift-2026) to receive 50,000 read requests per second and 1,000 purchase updates per second. If the key expires or is invalidated during the sale, the database will fail. Design a caching architecture that prevents database overload, ensures no purchase writes are lost, and keeps ticket inventory accurate within 1 second. Write a 3-paragraph structural proposal describing your strategy combos (e.g., L1/L2 cache, locking, queuing).
23. Summary
Caching strategies dictate the data flow between application code, in-memory caches, and database engines. Cache-Aside loads data on demand and is resilient to cache crashes. Write-Through guarantees consistency by updating cache and database together. Write-Back prioritizes write speed by saving updates to memory and flushing asynchronously to disk. Balancing write latency, data consistency, and architectural complexity determines which strategy fits your system design.
24. Cheat Sheet
| Strategy | Read Pattern | Write Pattern | Consistency Level | Write Latency | Primary Drawback |
|---|---|---|---|---|---|
| Cache-Aside | Checks cache, queries DB on miss, writes to cache. | Writes to DB, deletes cache key. | Eventual consistency (potential stale data window). | Medium (DB write overhead). | Application must manage caching orchestration logic. |
| Write-Through | Checks cache, queries DB on miss (Read-through). | Writes to cache and DB simultaneously. | Strong consistency. | High (must wait for DB disk write). | Slower writes; can cache unused write-heavy data. |
| Write-Around | Checks cache, queries DB on miss. | Writes directly to DB, skipping cache. | Eventual consistency. | Medium (DB write overhead). | Cache miss on first read following a database update. |
| Write-Back | Reads from cache directly. | Writes to cache; queues DB write asynchronously. | Eventually consistent (DB lag). | Low (RAM speeds ~sub-millisecond). | Risk of permanent data loss if cache fails before flushing. |
25. Quiz
1. Which caching strategy is best suited for write-heavy workloads where immediate durability of data is not required, but low latency is critical?
A) Cache-Aside
B) Write-Through
C) Write-Back
D) Write-Around
2. In Cache-Aside caching, why is it recommended to delete a cache key rather than update it during a database update?
A) Deleting is faster than updating the key in RAM.
B) Updating the key is susceptible to race conditions where stale data can overwrite newer data.
C) Redis does not support key updates; only deletes and inserts are permitted.
D) Deleting the key saves memory instantly.
3. What is a "Cache Avalanche"?
A) An eviction policy that randomly deletes 50% of the cache keys.
B) A scenario where many keys expire simultaneously or the cache crashes, overloading the database with read traffic.
C) A network failure where Redis replica nodes disconnect from the master instance.
D) A condition where the cache runs out of RAM and rejects all read operations.
4. How can you mitigate "Cache Penetration"?
A) Implement multi-threading locks.
B) Configure master-replica replication clusters.
C) Cache empty/null results with a short TTL, or use a Bloom filter in front of the cache.
D) Increase the database connection pool limit.
5. What is the formula for the Cache Hit Ratio?
A) Hits / Misses
B) Hits / (Hits + Misses)
C) Misses / (Hits + Misses)
D) (Hits - Misses) / Hits
6. Which strategy relies on the cache client or middleware internally fetching data from the database on a cache miss, making the process transparent to the application code?
A) Cache-Aside
B) Read-Through
C) Write-Back
D) Write-Around
7. What is the purpose of adding "jitter" to TTL values?
A) To make eviction policies more random.
B) To reduce network roundtrips to the cache cluster.
C) To spread out key expiration times and prevent a Cache Avalanche.
D) To prevent hackers from guessing key names.
8. What is the thundering herd problem (cache stampede)?
A) When replica nodes query the master cluster simultaneously.
B) When a hot key expires and concurrent requests hit the database simultaneously.
C) When the cache memory leaks, causing OOM errors.
D) When the network connection pool reaches capacity.
9. Under which strategy is the database bypassed entirely during writes, with updates filed only in RAM and synced to the database asynchronously?
A) Write-Through
B) Cache-Aside
C) Write-Back
D) Write-Around
10. What does the XFetch algorithm help prevent?
A) Memory fragmentation.
B) Cache Stampede (using probabilistic early expiration).
C) Cache Penetration (using Bloom filters).
D) High write latency in Write-Through.
Answer Key
- C — Write-Back is extremely fast because it writes directly to RAM (cache) and returns success, deferring disk writes to asynchronous workers.
- B — Updating cache values concurrently can result in out-of-order writes and data inconsistency. Deletes are idempotent and force sequential reader lookups.
- B — A Cache Avalanche occurs when concurrent cache expirations or a node crash floods the database with queries.
- C — Caching nulls with a short TTL or using a Bloom filter prevents malicious or invalid keys from bypassing the cache to the database.
- B — The Hit Ratio represents hits divided by the total number of read attempts.
- B — In Read-Through caching, the caching middleware interacts with the database on behalf of the application, hiding database lookups.
- C — Jitter adds random duration variation to TTLs, preventing keys from expiring at the exact same moment.
- B — Cache Stampede is the database overload that happens when a single expired hot key triggers concurrent lookups.
- C — Write-Back writes to the cache first and queues database writes asynchronously.
- B — The XFetch algorithm calculates the probability of early renewal based on query computation time and remaining TTL to prevent cache stampedes.
26. Further Reading
- Designing Data-Intensive Applications by Martin Kleppmann (Chapter 3: Storage and Retrieval, Chapter 5: Replication).
- AWS Caching Best Practices — Detailed overview of Memcached/Redis deployment strategies.
- Redis Client-Side Caching Manual — Implementing multi-tier (L1/L2) cache architectures.
27. Next Lesson Preview
Now that we understand how reads and writes flow between the cache and the database, we need to explore how caches manage their limited memory capacity. In the next lesson, Cache Eviction Policies, we will dive deep into LFU, LRU, FIFO, and Adaptive Replacement Cache (ARC) eviction algorithms.
Key takeaways
- Cache-aside loads on miss; read-through hides that from the app.
- Write-through is consistent; write-back is fast but can lose data.