Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 60

Caching Patterns and Invalidation Strategies

Recommended resources

Introduction

Welcome to the first lesson in our module on Performance, Efficiency, and Cost. In our previous discussion, we built a comprehensive strategy for managing service communication at scale, focusing on load balancing with systems like Maglev and reliability patterns in gRPC. We established how to direct traffic effectively. Now, we will explore one of the most powerful techniques for reducing the volume of that traffic in the first place: caching.

This lesson addresses the learning outcome: Analyze caching patterns (e.g., read-through, write-behind) and invalidation strategies. For a Principal Engineer, this isn't just about speeding up a single query. It's about architecting systems that are more scalable, resilient to load spikes, and cost-effective by minimizing expensive computations and database accesses. We will dissect the fundamental patterns that govern how applications interact with caches and explore the critical, and often complex, problem of ensuring that cached data doesn't become dangerously stale.


The Role and Placement of Caches

At its heart, a cache is a high-speed data storage layer that stores a subset of data, typically transient in nature, so that future requests for that data are served faster than is possible by accessing the data's primary storage location. The performance gain comes from using faster media (like RAM instead of SSDs) or avoiding network latency.

Caching in System Design Interviews w/ Meta Staff Engineer

This video provides a solid introduction to why caching is essential and explores the different architectural layers where a cache can be placed.

Please watch the following segments: The Basic Idea: This quickly recaps the fundamental performance trade-off that caching exploits. Cache Placement: Pay close attention to the descriptions of external caching (the most common pattern for our purposes), in-process caching, CDNs, and client-side caching. Understand the trade-offs of each placement strategy.

To summarize the key placements for distributed backend systems:

  • External/Distributed Cache: A separate service like Redis or Memcached, shared by multiple application servers. This is the most common pattern in microservice architectures as it provides a shared, global cache that all services can leverage. It adds a network hop but decouples the cache from the application lifecycle.
  • In-Process Cache: Data is stored in the application's own memory space. This offers the lowest possible latency (no network hop) but the cache is not shared, leading to potential data duplication and inconsistency between service instances. It's best suited for small, very hot, and relatively static data like feature flags or configuration settings.

For the rest of this lesson, we will primarily focus on the interaction patterns with an external, distributed cache, as this is the most architecturally significant scenario in the systems we are studying.


Core Caching Patterns

The way an application reads from and writes to the cache and database defines its caching pattern. Each pattern represents a different trade-off between read/write performance, data consistency, and implementation complexity.

The article below provides an excellent, concise summary of the four primary patterns. We'll use it as our guide.

Caching - System Design in a Hurry

This resource clearly breaks down the most important caching patterns. As you read, focus on the flow of data for both reads and writes in each pattern.

Please read the entire section on Cache Architectures. Pay attention to the pros, cons, and typical use cases for Cache-Aside, Write-Through, Write-Behind, and Read-Through.

Let's review these patterns, using the following diagrams as a visual reference for the data flows.

This diagram illustrates the data flow for six different caching approaches. We will focus on Cache-aside, Write-through, Write-behind, and Read-through.
  1. Cache-Aside (or Lazy Loading)

    • Flow: This is the most common pattern. The application is responsible for managing the cache.
      • Read: The application first checks the cache. On a hit, it returns data. On a miss, it reads from the database, writes the result into the cache, and then returns.
      • Write: The application writes directly to the database. It may or may not invalidate the cache (we'll cover this shortly).
    • Trade-offs:
      • Pro: The cache only stores data that is actually requested, preventing it from being filled with unused data. It's also resilient to cache failures; if the cache is down, the application can fall back to the database (though with performance degradation).
      • Con: Every cache miss results in three distinct operations (cache read, DB read, cache write), increasing latency for that request. Data can become stale if it's updated in the database without being invalidated in the cache.
  2. Read-Through

    • Flow: The application treats the cache as its main data source. The cache itself handles database interaction.
      • Read: The application requests data from the cache. On a hit, it's returned. On a miss, the cache is responsible for fetching the data from the database, storing it, and returning it to the application.
    • Trade-offs:
      • Pro: Application logic is simplified; it doesn't need to know about the database.
      • Con: This pattern requires a specific cache provider or library that abstracts the database interaction, adding a layer of complexity or vendor lock-in. It's less common for general application caching than Cache-Aside but is the principle behind CDNs.
  3. Write-Through

    • Flow: On a write, data is written to the cache and the database in a single, synchronous operation.
      • Write: Application writes to the cache. The cache synchronously writes the data to the database. The operation is complete only after both writes succeed.
    • Trade-offs:
      • Pro: Guarantees that data in the cache is never stale relative to the database after a write. This is great for read-heavy workloads where consistency is key.
      • Con: Write latency is increased because it includes both the cache write and the database write. This pattern can also lead to the "dual-write problem": what if the database write fails but the cache write succeeded? The system is now in an inconsistent state.
  4. Write-Behind (or Write-Back)

    • Flow: The application performs a fast write to the cache, and the cache asynchronously writes the data to the database in the background.
      • Write: Application writes to the cache and gets an immediate acknowledgment. The cache then handles writing to the database at a later time, often in batches.
    • Trade-offs:
      • Pro: Extremely fast writes and high write throughput, as the application isn't blocked by database performance.
      • Con: There is a significant risk of data loss if the cache fails before the data is persisted to the database. This pattern is suitable only when some data loss is acceptable, such as for collecting metrics, logging, or handling analytics events.
This infographic provides alternative diagrams for Write-Through, Cache-Aside, and Write-Back, and also introduces the concept of cache consistency issues, which we'll explore next.

Invalidation and Consistency Strategies

A cache, by its nature, creates a second copy of data. Keeping that copy fresh is the hardest part of caching. A Principal Engineer must be able to articulate the trade-offs between different consistency strategies.

There are two primary ways to handle stale data:

  • Time To Live (TTL): This is the simplest strategy. You set an expiration time on each cache entry. After the TTL passes, the entry is automatically evicted. The next request will result in a cache miss, forcing a refresh from the database. This provides eventual consistency but allows data to be stale for the duration of the TTL.
  • Explicit Invalidation: When your application writes an update to the database, it also issues a command to the cache to delete the old entry. The next read for that data will miss, fetch the new data from the database, and populate the cache. This provides stronger consistency but introduces its own challenges. The most common implementation is a DELETE operation after a successful database UPDATE (the Cache-Aside pattern).

What happens if the cache invalidation DELETE command fails after the database UPDATE succeeds? The cache now holds stale data indefinitely (or until TTL). This is a classic distributed systems problem. There's no perfect solution, only trade-offs. Strategies to mitigate this include retries with backoff for the delete operation or having a background job that periodically sweeps for inconsistencies.

A Deep Dive into Strong Consistency: Chubby's Caching Model

Most application caches favor performance and availability, accepting eventual consistency. However, for systems where correctness is paramount, like a distributed lock service, a different model is required. Google's Chubby provides a fascinating case study in building a strictly consistent client-side cache.

[PDF] The Chubby lock service for loosely-coupled distributed systems

This section of the Chubby paper details its unique caching protocol, which prioritizes consistency above all else. This is an excellent example of applying distributed systems principles to a caching problem, moving beyond the standard patterns.

Please read the following sections: Section 2.7, "Caching": Focus on the core mechanism. The master blocks modifications while it sends invalidations to clients. Understand why this protocol ensures clients see a consistent view. Section 2.8, "Sessions and KeepAlives": This is the key to making the caching protocol work. Pay close attention to how invalidations are piggybacked on KeepAlive replies. A client cannot maintain its session lease without acknowledging invalidations.

The Chubby model is a powerful illustration of a design point that most application developers don't encounter. By tying cache consistency directly to the session lease mechanism, it enforces correctness at the protocol level. An application using Chubby is guaranteed not to see stale data because if it misses an invalidation (due to a network partition, for example), its session will eventually expire, forcing it to discard its entire cache. This is a much stronger guarantee than the "best-effort" nature of TTL or simple write-invalidation.


Eviction Policies and Common Pitfalls

A cache has a finite size. Eviction policies are the algorithms used to decide which items to discard when the cache is full.

Caching in System Design Interviews w/ Meta Staff Engineer

Let's return to the introductory video for a quick overview of the most common eviction policies.

Watch the segment on eviction policies. Note the difference between LRU, LFU, and TTL.

  • Least Recently Used (LRU): Evicts the data that hasn't been accessed in the longest time. This is the most common and often the best default choice.
  • Least Frequently Used (LFU): Evicts the data that has been accessed the fewest times. Useful if some items are consistently popular over others.
  • First-In, First-Out (FIFO): Evicts the oldest item, regardless of how often or recently it was accessed. Simple but generally inefficient.

Finally, while caching solves performance problems, it introduces new failure modes. A Principal Engineer must anticipate and mitigate these.

Caching Pitfalls Every Developer Should Know

This video from ByteByteGo clearly explains several critical caching pitfalls and their mitigation strategies.

Please watch the following segments: Cache Stampede: Understand what causes this "thundering herd" problem and the locking/offloading techniques to prevent it. Cache Penetration: Learn how requests for non-existent data can harm your database and how Bloom filters or placeholder values can help. Stampede vs. Avalanche: This clarifies the distinction between two similar-sounding but different large-scale failure modes.


Conclusion

In this lesson, we have systematically analyzed the core architectural decisions involved in caching. This knowledge is fundamental to designing high-performance, cost-effective, and resilient distributed systems.

Our key takeaways include:

  • Caching Patterns: The four primary patterns—Cache-Aside, Read-Through, Write-Through, and Write-Behind—offer a spectrum of trade-offs between read/write latency, data consistency, and implementation complexity. Cache-Aside is the most common and versatile pattern for general-purpose applications.
  • Invalidation Strategies: Keeping caches fresh is a critical challenge. Strategies range from simple Time-To-Live (TTL), which offers eventual consistency, to explicit write-invalidation.
  • Consistency Models in Caching: We saw how a system like Chubby implements a strongly consistent cache by linking invalidations directly to a session lease mechanism, providing a stark contrast to more common, relaxed consistency models.
  • Operational Concerns: A robust caching strategy must also include an appropriate eviction policy (typically LRU) and mitigations for common pitfalls like cache stampedes and cache penetration.

In our next lesson, we will move from analysis to synthesis. You will apply the patterns and strategies learned today to design a multi-layer caching architecture for a given system scenario, a common task for a Principal Engineer seeking to optimize a complex system for performance and cost.