Skip to content

Cache Consistency: Ensuring Data Coherence Between Cache and Database

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about Cache Consistency: Ensuring Data Coherence Between Cache and Database. We cover key concepts, practical examples, and best practices to help you master this topic.

Cache consistency refers to the guarantee that cached data matches the source of truth (the database). Different consistency models offer different trade-offs between freshness, performance, and complexity. Choosing the right model depends on your application's tolerance for stale data.

flowchart TB
    subgraph Consistency Models
        Strong[Strong Consistency]
        EW[Eventual Consistency]
        RYW[Read-Your-Writes]
        Causal[Causal Consistency]
    end
    Strong -->|Cache + DB always match| LowLatency[Higher Write Latency]
    EW -->|Cache may be stale| LowLatency
    RYW -->|User sees own writes| LowLatency
    Causal -->|Related events in order| LowLatency
    EW --> MaxPerf[Maximum Performance]
    Strong --> MaxCons[Maximum Consistency]

What You'll Learn

  • Strong vs. eventual consistency in caching
  • Read-your-writes and causal consistency
  • Techniques: write-through, versioning, and Conflict Resolution
  • CAP Theorem implications for cache design

Why It Matters

Inconsistent cache data causes bugs: users see stale profiles, incorrect balances, or missing updates. Understanding consistency models helps you choose the right caching Strategy for each data type — strong consistency for payments, eventual for news feeds.

Real-World Use

A collaborative document editor uses causal consistency for edits. If user A sees user B's comment and replies, user C sees both in order — even if they arrived via different cache nodes. Edits not causally related (two independent paragraphs) can appear in any order.

Consistency Techniques

Version-Based Consistency with Optimistic Locking

async function updateWithVersion(userId, updateFn) {
  const key = `user:${userId}`;
  const maxRetries = 3;

  for (let attempt = 0; attempt < maxRetries; attempt++) {
    const cached = await cache.get(key);
    const user = cached ? JSON.parse(cached) : await db.getUser(userId);
    const currentVersion = user._version || 0;

    const updated = await updateFn(user);

    // Increment version to invalidate stale cache entries
    updated._version = currentVersion + 1;

    // CAS: compare-and-swap for cache update
    const casResult = await cache.set(key, JSON.stringify(updated), {
      EX: 3600,
      XX: true, // Only set if key exists
      GET: true  // Return old value
    });

    if (casResult) {
      const oldValue = JSON.parse(casResult);
      if (oldValue._version !== currentVersion) {
        // Conflict, retry
        continue;
      }
    }

    await db.updateUser(userId, updated);
    return updated;
  }

  throw new Error('Update failed after max retries due to version conflict');
}

Expected output:

Cache write uses compare-and-swap. If another process updated the cache concurrently, the version check fails and the operation retries.

Read-Your-Writes Consistency

class ReadYourWritesCache {
  constructor() {
    this.writeTimestamps = new Map();
  }

  async write(key, value) {
    await db.write(key, value);
    await cache.set(key, JSON.stringify(value), 3600);
    this.writeTimestamps.set(key, Date.now());
  }

  async read(key) {
    const lastWriteAt = this.writeTimestamps.get(key) || 0;

    // Always read from DB for keys recently written by this process
    if (Date.now() - lastWriteAt < 5000) {
      const fresh = await db.read(key);
      await cache.set(key, JSON.stringify(fresh), 3600);
      return fresh;
    }

    const cached = await cache.get(key);
    if (cached) return JSON.parse(cached);

    const fresh = await db.read(key);
    await cache.set(key, JSON.stringify(fresh), 3600);
    return fresh;
  }
}

Expected output:

Within 5 seconds of writing, reads always fetch from DB. After 5 seconds, falls back to cache. Users always see their own writes immediately.

Cache and DB Dual-Write with Outbox Pattern

async function dualWrite(table, id, data) {
  const connection = await db.getConnection();

  try {
    await connection.beginTransaction();

    await connection.query(`UPDATE ${table} SET ? WHERE id = ?`, [data, id]);

    // Write to outbox for async cache invalidation
    await connection.query(
      'INSERT INTO cache_outbox (entity_type, entity_id, action, created_at) VALUES (?, ?, ?, NOW())',
      [table, id, 'invalidate']
    );

    await connection.commit();

    // After transaction commits, invalidate cache
    await cache.del(`${table}:${id}`);

    // Process outbox asynchronously for other cache nodes
    await publish('cache.invalidate', { table, id });
  } catch (err) {
    await connection.rollback();
    throw err;
  }
}

Expected output:

Transaction guarantees the DB write and invalidation event are atomic. If the transaction succeeds, cache is invalidated. If the cache del fails, the outbox processor retries.

Common Mistakes

  • Assuming eventual consistency means "eventually" is always fast enough — under heavy load, convergence can take minutes.
  • Not implementing read-your-writes for user-facing features where the user expects to see their own changes immediately.
  • Using strong consistency for everything — this adds latency and reduces availability unnecessarily.
  • Ignoring clock skew in version-based consistency — relying on timestamps for ordering requires synchronized clocks (NTP).
  • Not testing consistency behavior under network partitions — caching bugs often surface only during failures.

Practice Questions

  1. What is the difference between strong and eventual consistency?
  2. When would you use read-your-writes consistency?
  3. How does the outbox pattern ensure consistency between cache and database?
  4. What is the CAP theorem and how does it apply to caching?
  5. How does version-based consistency handle concurrent updates?

Challenge

Design a consistency strategy for a ticket booking system. Two users should not be able to book the same seat. Use version-based consistency with optimistic locking in the cache. Measure the retry rate under 1000 concurrent booking requests for the same event.

FAQ

What is cache consistency?

Cache consistency is the guarantee that data in the cache matches the data in the source of truth (database). Stronger consistency means less staleness but higher cost.

What is strong consistency in caching?

Strong consistency guarantees that every read returns the most recent write. In caching, this typically requires write-through or synchronous cache invalidation.

What is eventual consistency?

Eventual consistency guarantees that if no new writes occur, all reads will eventually return the latest value. The cache may return stale data for an unbounded time window.

How does the CAP theorem affect cache design?

CAP states you can have at most two of Consistency, Availability, and Partition Tolerance. In distributed caching, you typically prioritize Availability and Partition Tolerance (AP) over Strong Consistency (CP).

What is the outbox pattern?

The outbox pattern writes both the data change and the cache invalidation event in the same database transaction. This ensures at-least-once delivery of invalidation events.

Mini Project

Build a consistency testing framework for the blog API. Implement three modes: eventually consistent (TTL only), read-your-writes, and strong consistent (write-through). Write a test that inserts a record, immediately reads it, and measures the percentage of stale reads across 10,000 iterations for each mode.

What's Next

Continue with Caching Strategies Overview to compare all caching strategies side by side.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro