SCALABILITY & PERFORMANCE / SYSTEM CONCEPT BRIEF

Cache invalidation

Cache invalidation is keeping cached data consistent with its source of truth.

IntermediatePhase 03 / Topic 5 of 13RequirementsTrade-offsFailure modes
01

Overview

Cache invalidation is keeping cached data consistent with its source of truth. It is famously one of the two hard problems in computer science because caches are copies: every write creates a window in which some readers see old data. The design question is how long that window may be and how you shrink it.

The main strategies are expiry (TTL), explicit invalidation on write (delete the key), write-through (update cache and database together), and event-driven invalidation (change data capture streams publish updates). Production systems combine a TTL safety net with explicit or event-driven invalidation.

Printed menus in a restaurant

When prices change, the kitchen knows immediately, but printed menus on tables are copies. You can reprint menus every morning (TTL), replace menus as soon as a price changes (explicit invalidation), or put a screen on each table that always shows the kitchen's current price (no cache).

02

When to use it

  • Any cache in front of mutable data.
  • Designing how writes propagate to caches, CDNs, and search indexes.
  • Debugging users seeing stale data.
  • Choosing TTLs per data type.
03

Where it shows up in interviews

Read-heavy data with updates

Recognize it when: cache product, profile, or config data that changes occasionally.

  • Design a product catalog
  • Design a user profile service
Multi-layer caches

Recognize it when: browser, CDN, app cache, and search index must all update.

  • Design a news site with breaking updates
  • Design Twitter's cached timelines
04

Where it is used in real software

Facebook's memcache paper

Facebook invalidates memcache keys using the MySQL replication stream (McSqueal) and leases to prevent stale sets and thundering herds.

Debezium CDC

Streams database changes to Kafka, where consumers invalidate caches and update search indexes.

CDN surrogate keys

Fastly and Cloudflare tag cached responses so one purge call invalidates every page containing a changed product.

05

Key terms

TTL
Entries expire after a time; bounds staleness.
Delete on write
After updating the database, delete the cache key so the next read reloads it.
Write-through
Write to cache and database in the same operation.
CDC
Change data capture: reading the database log to publish every change.
Stale-while-revalidate
Serve the old value while refreshing it in the background.
06

Safe cache-aside invalidation

  1. 1
    Write to the database first

    The database is the source of truth.

  2. 2
    Delete the cache key

    Deleting is safer than setting a new value, which can race with other writers.

  3. 3
    Readers repopulate on miss

    Next read loads fresh data and caches it with a TTL.

  4. 4
    Keep a TTL anyway

    If a delete is lost, the entry still expires.

  5. 5
    For strict freshness, use CDC

    Invalidate from the database log so no write path can forget to invalidate.

07

The classic race condition

Reader A and Writer B, cache-aside with 'set on read'

Step 1 / 4
StepReader AWriter BDB valueCache value
1cache miss, reads DB: v1-v1-
2(paused)writes v2 to DBv2-
3(paused)deletes cache keyv2-
4sets cache = v1-v2v1 (stale)

NOWStep: 1 | Reader A: cache miss, reads DB: v1 | Writer B: - | DB value: v1 | Cache value: -

The stale v1 stays until TTL expiry. Fixes: short TTLs, delayed double delete (delete again after a short delay), leases or version checks on set, or CDC-based invalidation.

08

Implementation

async function updateProduct(id: string, changes: Partial<Product>) {  await db.products.update(id, changes);          // 1. source of truth  await redis.del(`product:${id}`);              // 2. invalidate  setTimeout(() => redis.del(`product:${id}`), 500); // 3. delayed double delete closes the race window  await cdn.purgeByTag(`product-${id}`);          // 4. edge caches} // Versioned set: only write the cache if our copy is not older than what is cachedasync function setIfNewer(key: string, value: Product) {  const script = `    local current = redis.call('HGET', KEYS[1], 'version')    if (not current) or tonumber(ARGV[1]) > tonumber(current) then      redis.call('HSET', KEYS[1], 'version', ARGV[1], 'data', ARGV[2])      redis.call('EXPIRE', KEYS[1], 300)      return 1    end    return 0`;  return redis.eval(script, { keys: [key], arguments: [String(value.version), JSON.stringify(value)] });}
09

Complexity and performance

Staleness with TTL only<= TTL

Simple upper bound.

Staleness with delete on write~milliseconds

Plus rare race windows.

CDC propagation~100 ms-seconds

Log reading delay.

10

Trade-offs

Freshness vs complexity

TTL-only is trivial but stale for up to TTL; CDC invalidation is fresh and reliable but adds Kafka and consumers to operate.

Delete vs update

Deleting causes one extra miss but avoids races between concurrent writers setting different values.

11

Variants and related techniques

Write-behind

Write to the cache and flush to the database later; fast but risks data loss.

Versioned keys

Include a version in the key (product:42:v7) so updates simply use a new key.

Tag-based purges

Invalidate groups of entries by tag at the CDN.

12

Common mistakes

  • Updating the cache before the database.

    Fix: If the DB write fails, the cache holds data that never existed. Write the DB first.

  • No TTL on cache entries.

    Fix: A missed invalidation would otherwise be permanent.

  • Forgetting derived caches.

    Fix: A product change may need invalidation of listing pages, search results, and recommendations.

13

Interview questions

Why delete the key instead of updating it on writes?

Two concurrent writers can update the cache in the opposite order from the database, leaving an old value cached. Deleting forces the next reader to load the current database value.

How would you keep caches consistent across many services?

Publish change events from the source of truth (CDC via Debezium or an outbox table) and let each cache owner invalidate on those events, with TTLs as a backstop.

14

Practice problems

ProblemDifficultyWhat it trains
Choose TTLs for 5 data types in an e-commerce siteEasyStaleness tolerance.
Fix the stale-cache race in cache-asideMediumLeases and versioning.
Design CDC-driven invalidation for caches and searchHardEvent pipeline.