Hello! Welcome to our next lesson.
In the previous session, we delved into the internals of Redis, understanding it as a high-performance, in-memory "data structure server." We explored its core data types and the clever internal implementations that make it so fast. You now have the tool for caching.
Today, we'll focus on the strategy. Having a fast cache is one thing, but knowing how and when to use it in relation to your primary database is where the architectural decisions lie. This lesson directly addresses the learning outcome: Compare caching patterns: cache-aside, write-through, and read-through.
We will analyze three fundamental patterns that dictate the flow of data between your application, your cache (Redis), and your database (e.g., PostgreSQL). For each pattern, we'll examine:
- The exact sequence of read and write operations.
- The pros and cons in terms of performance, data consistency, and implementation complexity.
- The ideal use cases for each strategy.
By the end of this lesson, you'll be able to make informed decisions about which caching pattern to apply in different backend scenarios, a key skill in system design.
The Core Distinction: Who is the Orchestrator?
Before we dive into the specific patterns, it's crucial to understand a fundamental difference in their design philosophy: who is responsible for managing the cache?
To get a clear idea of this distinction, let's start with a short reading.
Read Through Vs Write Through Cache
This article from Algomaster, 'Read Through Vs Write Through Cache,' does an excellent job of framing the core difference. It contrasts the application being the 'orchestrator' versus the cache being the 'orchestrator'.
Read the first three paragraphs, starting from 'In the cache-aside pattern...' and ending with '...as a single system.' Focus on the distinction between the application handling cache logic versus the cache handling it internally.
As the article explains, the patterns we'll discuss fall into two main camps:
- Application as Orchestrator (Cache-Aside): Your application code contains the explicit logic. It must first check the cache, and if the data isn't there, it must then query the database and manually place the data into the cache for next time.
- Cache as Orchestrator (Read-Through/Write-Through): Your application's code is simplified. It communicates only with the cache. The cache itself is configured to know how to fetch data from the database (on a read) or persist data to it (on a write).
This distinction is the key to understanding the trade-offs in complexity and control that each pattern offers.
The Caching Patterns in Detail
Let's now examine each pattern one by one. The following diagrams provide a great visual reference for the interaction flows we're about to discuss.

1. Cache-Aside (or Lazy Loading)
This is the most common caching pattern you'll encounter and implement, especially when using a straightforward cache like Redis with a framework like Django.
The Flow:
The application tries to read data from the cache.
- Cache Hit: If the data is in the cache, it's returned directly to the application. Fast and simple.
- Cache Miss: If the data is not in the cache, the application:
- Queries the primary database to get the data.
- Stores a copy of that data in the cache.
- Returns the data to the client.
The next time this same data is requested, it will be a cache hit. This is called "lazy loading" because you only load data into the cache when it's first requested.
Top Redis Caching Strategies Every Backend Developer Should Know
This video from 'SWE with Vivek Bharatha' gives a very clear explanation of the cache-aside pattern, its pros, cons, and a practical example.
Watch the segment on the Cache-Aside strategy from 00:43 to 02:51. Note the discussion of 'cold start' and 'stale data'.
Implementation Example:
The following Python snippet illustrates the cache-aside logic.
# This is a conceptual example. In a real Django app,
# you'd use the cache framework.
def get_product_details(product_id):
cache_key = f"product:{product_id}"
# 1. Try to get data from cache
cached_product = cache.get(cache_key)
if cached_product:
print("Cache Hit!")
return cached_product
# 2. Cache Miss: Get data from the database
print("Cache Miss!")
product = Product.objects.get(id=product_id)
# 3. Store the data in the cache for next time
# We also set a TTL (Time-To-Live) of 1 hour.
cache.set(cache_key, product.to_dict(), timeout=3600)
return product.to_dict()
Note: The resource LINK shows this exact pattern but confusingly labels it "Read-Through". The code itself is a perfect example of cache-aside.
Pros & Cons:
| Pros | Cons |
|---|---|
| Simple to implement | "Cold start" penalty on first request |
| Resilient to cache failure | Potential for stale data |
| Only caches requested data | Requires explicit invalidation logic |
2. Write-Through
In this pattern, the cache acts as the main point of contact for writes. When your application needs to write new data or update existing data, it writes it directly to the cache, which then synchronously writes it to the database.
The Flow:
- The application issues a write command to the cache.
- The cache immediately writes the data to the primary database.
- Once the database write is confirmed, the cache updates its own storage.
- The cache confirms the successful write back to the application.
A read operation in a pure write-through system would still typically follow the cache-aside or read-through pattern. The key feature is the synchronous write to both systems, ensuring consistency.
Caching in System Design Interviews w/ Meta Staff Engineer
Now let's watch the 'Hello Interview' explanation of write-through. The speaker does a great job of explaining the synchronous nature, the need for specialized libraries, and the 'dual write problem'.
Watch from 09:57 to 12:00. Focus on the trade-offs: slower writes for better consistency.
The Dual-Write Problem:
As the video mentions, write-through introduces a consistency challenge: what happens if the database write succeeds, but the subsequent cache write fails? Or vice-versa? This leaves your cache and database in an inconsistent state. Handling this requires complex two-phase commit logic or retry mechanisms, which is why it's often managed by specialized caching providers rather than being manually implemented.
Pros & Cons:
| Pros | Cons |
|---|---|
| High consistency | Higher write latency (must wait for both cache and DB) |
| Reads are always fast and fresh | Caches data that may never be read |
| Durable writes | Complex to implement correctly (dual-write problem) |
3. Read-Through
The read-through pattern is the logical sibling of cache-aside, but it shifts the responsibility from the application to the cache provider.
The Flow:
- The application requests data from the cache.
- The cache checks for the data internally.
- Cache Hit: The data is returned.
- Cache Miss: The cache itself, not the application, queries the primary database.
- The cache stores the retrieved data and then returns it to the application.
Read Through Vs Write Through Cache
The 'Read Through Vs Write Through Cache' article we looked at earlier has a great section on this, clearly explaining the role of a 'Cache Loader'.
Read the section titled 'Read-Through Cache'. Notice how the application code becomes simpler because it no longer contains database-fetching logic.
The key takeaway is that your application code is completely abstracted from the database. It treats the cache as the single source of truth. This requires a cache provider that can be configured with a loader—a function that tells it how to fetch data from your specific database schema. Standard Redis does not offer this natively, but systems like Redis Enterprise (with Redis Gears), Hazelcast, or Oracle Coherence do.
Pros & Cons:
| Pros | Cons |
|---|---|
| Simplified application code | Increased cache layer complexity |
| Consistent data loading logic | Tightly couples cache to database schema |
| Less prone to bugs in app logic | Less flexible for complex caching needs |
Comparison and When to Use Each
Now that we understand each pattern, let's put them side-by-side to make the trade-offs clear.
| Aspect | Cache-Aside (Application-Managed) | Read-Through (Cache-Managed) | Write-Through (Cache-Managed) |
|---|---|---|---|
| Who Manages Cache Read Miss? | Application | Cache | N/A (for reads, often paired) |
| Who Manages Database Write? | Application | N/A (for writes, often paired) | Cache |
| Application Complexity | Higher (contains cache logic) | Lower (abstracted away) | Lower (abstracted away) |
| Cache Layer Complexity | Lower (e.g., plain Redis) | Higher (needs a "loader") | Higher (needs a "writer") |
| Write Latency | Database latency only | N/A | Database + Cache latency |
| Data Consistency | Eventual (can be stale until TTL) | Depends on write pattern | Strong (after a successful write) |
(This table is adapted from the algomaster.io resource.)
Decision Guide:
-
Use Cache-Aside when:
- You are using a simple cache like standard Redis or Memcached.
- You need fine-grained control over what gets cached and for how long.
- Your application can tolerate slightly stale data.
- This is the default, most practical choice for many web applications.
-
Use Read-Through when:
- You want to centralize data-access logic and simplify multiple application services.
- You are using a caching system that supports configurable loaders (e.g., Hazelcast).
- Your data models and fetching logic are relatively consistent.
-
Use Write-Through when:
- Data consistency is critical, and you cannot afford to serve stale data after a write (e.g., for financial transactions or inventory counts).
- Your application is read-heavy, and you want to ensure the cache is always warm after a write.
- You can tolerate slower write performance.
Conclusion
Today, we've dissected the three most important caching strategies. Understanding these patterns is not just about memorizing diagrams; it's about grasping the fundamental trade-offs between performance, consistency, and complexity.
Key Takeaways:
- Cache-Aside is the workhorse pattern where the application manages the cache. It's simple and flexible but puts more responsibility on the developer.
- Write-Through prioritizes consistency by ensuring writes go to both the cache and the database synchronously, at the cost of higher write latency.
- Read-Through simplifies application code by having the cache itself handle database lookups, but this requires a more sophisticated caching provider and configuration.
- The choice of pattern depends heavily on your application's requirements (consistency needs, read/write ratio) and the capabilities of your caching technology.
Preview of the Next Lesson
We've filled our cache, but it has finite memory. What happens when it gets full? We can't just stop caching new items. We need a policy to decide which old items to throw out to make room for new ones.
In our next lesson, we will explore common cache eviction policies (e.g., LRU, LFU, TTL). We'll learn how Redis and other systems decide what data to discard when memory runs low, another critical piece of the caching puzzle.