Course contentsShow
← Databases & SQL
Lesson 1389 of 1,95312. Key-Value and Caching EnginesFree lesson

Handling Stale Reads

Implement application logic to manage the visibility of eventual consistency.

In this lesson, you will learn how to implement application-level logic to mask eventual consistency when reading from distributed caches or read-replicas.

The Problem: Eventual Consistency

In distributed systems, especially when using caching engines like Redis or read-only database replicas, there is a delay between a write operation and its visibility across all nodes. If a user updates their profile and immediately refreshes the page, they might see the "old" data because the request hit a node that hasn't synchronized yet. This is a stale read.

Using Version Vectors for Visibility

Since we cannot always force immediate consistency without destroying performance, we shift the responsibility to the application layer. One common technique is the use of Version Vectors or Logical Timestamps.

Instead of trusting the cache blindly, we attach a version number or a timestamp to the data. When the application writes data, it increments the version. When the application reads data, it compares the cached version against the expected version stored in the user's session or a "source of truth" primary database.

def get_user_profile(user_id, expected_version):
    cached_data = cache.get(f"user:{user_id}")
    
    # If the cache is behind, we ignore it and fetch from primary
    if cached_data['version'] < expected_version:
        data = primary_db.get(user_id)
        cache.set(f"user:{user_id}", data)
        return data
        
    return cached_data

Strategic Strategies

  1. Read-Your-Writes: Always perform high-priority reads (like profile updates) against the primary database immediately after a write.
  2. Version Fencing: In the code above, the expected_version acts as a fence. If the cache doesn't meet the requirement, the application bypasses the stale data.
  3. Graceful Degradation: If the primary database is overloaded, you can decide whether to serve the slightly stale data (with a "loading" UI state) or show a cached version while flagging it as "updating."

By tracking the "state of freshness" at the application level, you gain control over the user experience, ensuring that eventual consistency feels instantaneous even when the underlying data replication is lagging.

Key Takeaway: By tracking data versions at the application level, you can programmatically detect and bypass stale cache entries, ensuring users always see the state they expect.