Cache Invalidation Strategies: Cache-Aside, Write-Through, Write-Back & CDC Pipelines
Cache invalidation is inherently a dual-write problem across uncoordinated storage systems. This article breaks down why Write-Invalidate beats Write-Update, exposes the insidious Replica-Lag Cache Poisoning trap, analyzes Write-Back crash-loss tradeoffs, and derives transactional CDC log-tailing via Debezium and Kafka.
Caching & Distributed Concurrency
Cache Invalidation Strategies: Cache-Aside, Write-Through, Write-Back & CDC Pipelines
A cache is not an optimization layer you sprinkle over slow queries — it is a volatile, distributed state machine with independent failure modes and eventual consistency tradeoffs. Invalidation is the exact boundary where those tradeoffs turn into production bugs. Phil Karlton famously noted that cache invalidation is one of the two hard things in Computer Science. The reason is mechanical: updating state across a database and an external caching layer constitutes an uncoordinated dual-write across two independent network services. When engineers attempt to keep these systems synchronized with naive application-layer updates, they introduce silent race conditions, stale data overwrites, and the catastrophic "Replica-Lag Cache Poisoning" trap. This article breaks down every major cache write topology, analyzes their failure modes, and derives a resilient Change Data Capture (CDC) invalidation architecture.
This is Part 2 of the Caching & Distributed Concurrency series. It builds directly on the multi-tier hierarchy and Redis clustering models established in Part 1 — Caching Topologies.
1. The Dual-Write Hazard: Why Write-Invalidate Beats Write-Update
When a backend service updates an entity in the primary database, it must decide what to do with the corresponding cache entry. Developers intuitively choose between two patterns:
- Write-Update: Update the database row, then immediately write the new value into the cache (
redis.set(key, newValue)). - Write-Invalidate: Update the database row, then delete the key from the cache (
redis.del(key)), forcing the next reader to repopulate it lazily.
Under any non-trivial concurrency, Write-Update is fundamentally broken.
1.1 The Interleaved Concurrent Write Race
When two concurrent requests attempt to update the same record using Write-Update, their execution order at the database level does not guarantee their arrival order at the Redis level. A slow network packet from an earlier transaction will overwrite a newer write in Redis, permanently de-synchronizing the cache from the database until the key's TTL expires.
By contrast, Write-Invalidate (DEL) is idempotent: deleting an already-deleted key is a harmless no-op. Even if deletions arrive out of order, the cache simply remains empty until a subsequent read fetches the latest committed database state.
1.2 The Post-Commit Deletion Contract
A common footgun with Write-Invalidate is deleting the cache key before committing the database transaction:
Transaction Invariant: In Cache-Aside, never delete a cache key prior to transaction commit. Always register cache deletions as post-commit hooks (afterCommit) to ensure that readers only repopulate the cache from committed transactions.
2. The "Replica-Lag Cache Poisoning" Trap
Even with post-commit cache invalidation, modern high-scale architectures introduce a subtle distributed systems vulnerability: Replica-Lag Cache Poisoning.
Most production backends route write queries to a primary database node and read queries to read-replicas to scale throughput. Replicas synchronize asynchronously via WAL streaming, creating a replication lag window ($\tau_{\text{lag}}$), typically 10–100ms.
2.1 Mitigations for Replica Lag Poisoning
- Primary-Pinned Re-Reads: Configure your database routing middleware so that if a read occurs immediately following a cache miss for a recently mutated entity, the query routes directly to the primary DB node.
- Delayed Double Deletion: Execute an immediate
DELpost-commit, and enqueue an asynchronous task (via SQS/BullMQ) to execute a secondDELafter $2 \times \tau_{\text{max_lag}}$ (e.g., 500ms). Any stale write populated during the replication lag window is purged by the second deletion. - CDC-Driven Invalidation: Defer cache invalidation entirely until replication log events are committed on all replicas (covered in Section 4).
3. Synchronous vs. Asynchronous Write Topologies
When selecting a write architecture, systems trade write latency, read latency, and durability:
| Pattern | Write Latency | Read Latency | Consistency Guarantee | Crash Durability Risk |
|---|---|---|---|---|
| Cache-Aside (Lazy) | Low (DB only) | High on miss, Low on hit | Eventual (Write-Invalidate) | None (DB is source of truth) |
| Read/Write-Through | High (DB + Cache sync) | Zero miss penalty (warmed) | Strong within single thread | None (DB updated synchronously) |
| Write-Back (Write-Behind) | Ultra-Low (<1ms in-memory) | Ultra-Low (Always in RAM) | Eventual (Async batched DB flush) | HIGH: Data lost if cache crashes before flush |

3.1 Write-Back (Write-Behind) Internals & Durability Engineering
Write-Back caching achieves extreme write throughput by acknowledging writes immediately in memory and asynchronously flushing batched updates to the database. It is widely used for analytics counters, gaming leaderboards, and telemetry feeds.
However, Write-Back introduces a severe durability hazard: if the Redis node or worker process terminates abruptly before dirty blocks are flushed to PostgreSQL, committed user writes are permanently destroyed.
4. Change Data Capture (CDC): Transactional Invalidation via WAL
Application-level cache invalidation (tx.afterCommit(() => redis.del(key))) works well for simple monolithic services. However, in distributed architectures with multiple microservices, background jobs, and direct database migrations, application code cannot be relied upon to invalidate every cache key reliably.
The gold standard for decoupled, resilient cache invalidation is Change Data Capture (CDC) via database transaction log tailing.

4.1 How CDC Solves the Dual-Write Problem
- Zero Dual Writes: The application writes only to PostgreSQL. It contains zero Redis invalidation logic.
- Deterministic Commit Ordering: Postgres writes every change to its Write-Ahead Log (WAL) in exact sequential order.
- Guaranteed Delivery: Debezium captures row-level changes via Postgres logical decoding (
pgoutput) and publishes them to a Kafka topic. - Decoupled Eviction: An independent invalidation consumer reads the Kafka stream and issues
redis.del(key)orredis.unlink(key). If Redis is temporarily down, Kafka retains the offset and replays invalidations upon reconnection.
4.2 Invalidation Blast Radius: SCAN vs KEYS *
When invalidating collections or wildcards (e.g., purging all user sessions under session:user_100:*), many developers instinctively run redis.keys('session:user_100:*').
Production Critical: KEYS * runs in $O(N)$ time where $N$ is the total number of keys in the Redis dataset. On a database with 10 million keys, KEYS * blocks the single-threaded Redis event loop for 200–800ms, causing cascading API timeouts across your entire platform.
Summary
| Strategy | Mechanical Rule |
|---|---|
| Write-Invalidate | Never update cached entities on write (Cache.set()); always invalidate (Cache.del()) to prevent concurrent write interleaving from poisoning the cache. |
| Post-Commit Invalidation | Invalidate cache keys only after database transaction commit (afterCommit); invalidating before commit allows concurrent reads to cache pre-commit state. |
| Replica-Lag Poisoning | When reading from asynchronous replicas, apply Delayed Double Deletion or pin cache-miss re-reads to the primary database to prevent caching stale replica data. |
| Write-Back Durability | Write-Back delivers extreme write throughput by buffering in RAM, but must be paired with an append-only Redis Stream WAL to prevent permanent data loss during crashes. |
| CDC Pipeline | Change Data Capture (Debezium + Kafka) completely eliminates application dual writes by deriving cache invalidations directly from the database WAL. |
Zero KEYS * Policy |
Never execute KEYS in production; use non-blocking cursor-based SCAN with UNLINK, or maintain explicit reverse-index Sets for $O(1)$ multi-key invalidations. |
What's Next
Now that we have mastered cache write paths and invalidation architectures, Part 3: High-Concurrency Cache Hazards investigates what happens when systems experience extreme traffic spikes: thundering herds, the mathematical derivation of the XFetch algorithm, Bloom filters, and memory eviction internals.
This article was developed with AI-assisted deep search, specification cross-referencing, and technical research synthesis.