🎉 Giảm 20% cho đơn hàng đầu tiên!✦🚚 Miễn phí vận chuyển cho đơn trên 500.000₫✦⚡ Flash Sale — Số lượng có hạn!✦🎁 Quà tặng kèm cho đơn từ 1.000.000₫✦🎉 Giảm 20% cho đơn hàng đầu tiên!✦🚚 Miễn phí vận chuyển cho đơn trên 500.000₫✦⚡ Flash Sale — Số lượng có hạn!✦🎁 Quà tặng kèm cho đơn từ 1.000.000₫✦
Redis Cache and Stale Data: Database Updated but Clients Still Read Old Data
Quay lại Blog

Redis Cache and Stale Data: Database Updated but Clients Still Read Old Data

25 tháng 10, 20267 phút đọcVũ Khôi
English
#English
Vũ Khôi

Vũ Khôi

Tác giả tại Wotech Electronics Blog

Redis stale cache — old data in the cache

You just updated a product's price in the database, verified the record changed correctly — but customers still see the old price after refreshing several times. It's not a "ghost bug": odds are your Redis cache is holding stale data — one of the oldest hard problems in software engineering. This article helps you understand the causes and the industry-standard solutions.

What is stale data in a cache?

A cache is a fast storage layer placed between the application and the database to reduce load and shorten response times; Redis is today's most popular in-memory caching software. The problem arises when the database has changed but the copy in the cache hasn't — that "expired" copy is stale data. The scary part: the database is correct, the service runs fine, and not a single log line reports an error — yet users keep reading outdated information.

Phil Karlton (of Netscape) famously quipped:

"There are only two hard things in computer science: cache invalidation and naming things."

The joke still holds: adding a cache is easy — keeping the cache consistent with the database is the hard part.

The classic scenario: DB updated but the cache never cleared

The flow looks simple:

DB UPDATE → DELETE REDIS KEY (update the database → delete the corresponding key in Redis)

The next read finds the key missing, the application loads fresh data from the database and repopulates the cache — on paper it's two steps with no room for error.

But the most valuable question in cache invalidation is: what happens if the database commit succeeds but the key deletion never executes, or executes at the wrong moment? At that instant, the database and cache become inconsistent — and stale data gets served to users, sometimes for hours.

Five common reasons a cache keeps old data "in stock"

  1. The application fails between the two steps. The database committed, but the application process crashed or hit an exception before sending the DELETE to Redis. A transaction spanning two separate systems is not atomic by itself — the technical root of the problem.
  2. Race conditions. One request reads old data from the database, stalls under heavy load, then writes that old data into the cache — overwriting the key another request just deleted. The old data "comes back to life" even though every step followed the rules.
  3. Multiple application instances, invalidation doesn't reach them all. A horizontally scaled system has many service replicas; if only the replica handling the write deletes the key, the others keep their local caches and remain blind to the change.
  4. Data changed outside the application. Manually run SQL, nightly ETL reloads... all write to the database without knowing the cache exists. The next morning, the application still displays yesterday's world.
  5. No TTL set. The TTL (Time-To-Live — a key's lifespan) is the "safety net" that makes keys expire on their own. A key with no TTL that falls into any of the four situations above will hold stale data indefinitely.

The business consequences

Stale data goes straight to customer experience and operations:

  • E-commerce: customers see old prices and wrong stock levels — orders get placed only to find items out of stock or prices mismatched.
  • Internal reporting: dashboards show outdated numbers, and managers make decisions on information that's no longer true.
  • Customer service: the agent sees the latest order status while the customer sees an old one — the two "argue" because they hold two sources of truth.
  • Lost trust: after a few repeats, internal users stop trusting the system — the most expensive thing to rebuild.

Industry-standard solutions

There is no single fix; in practice you combine multiple defensive layers based on the level of consistency you need.

1. TTLs on every key — the mandatory safety net

Set a TTL on every key, even when you already have active invalidation. A TTL doesn't create instant consistency, but stale data survives at most as long as the TTL. Time-insensitive data is usually fine with a TTL of a few minutes; sensitive data (prices, stock, order status) needs shorter ones.

2. Cache-aside + delete the key after writing

The most common pattern: the application manages cache reads/writes itself (cache-aside), and on a data update it deletes the key rather than overwriting the value. Deleting is safer than overwriting because it avoids two requests clobbering each other and avoids writing wrong data into the cache.

3. Delayed double delete

A defense against race conditions: after updating the database, delete the key once; wait a short interval (longer than any read request could stall); then delete again to clean up the case where an old request managed to write stale data back between the two steps.

4. Write-through / write-behind caching

The application writes to a single layer: the cache takes responsibility for syncing down to the database. Write-through writes synchronously — high consistency, higher write latency; write-behind writes asynchronously — faster, but with a risk of data loss if the cache fails before flushing.

5. Event-driven eviction

When the database changes, the system publishes an event via a message queue (such as Kafka or RabbitMQ) or Redis Pub/Sub (Redis's publish/subscribe mechanism) to every instance so each deletes the corresponding key. The most thorough approach for multi-service systems — at the cost of significantly more operational complexity.

Summary table: which solution to choose?

Strategy Consistency level Complexity Best when
Short TTL Eventually consistent Low Data tolerates a few minutes of lag
Delete key after write High (single writer) Low Simple applications, few instances
Delayed double delete Higher, blocks race conditions Medium High read traffic
Write-through High Medium Write latency is acceptable
Event-driven eviction Highest High Many services, many instances

The general rule: a TTL is mandatory on every key; active invalidation is layered on top depending on how sensitive the data is.

A perspective for Vietnamese businesses

For most small and medium-sized systems in Vietnam:

  • Start with cache-aside + delete key after write + TTLs on every key — simple, and covers most situations.
  • For data that touches money (prices, stock, balances), add the delayed double delete or skip the cache entirely for the most sensitive cases.
  • Invest in event-driven eviction only when the system is genuinely multi-service and multi-team — not because it's "modern," but because the problem has reached the threshold that demands it.

A quick check: do all your Redis keys have TTLs? Many projects have to answer "no" — and that is exactly the fuel for stale data.

Conclusion

Stale data is the price of the speed a cache brings — it can't be eliminated entirely, only narrowed to an acceptable inconsistency window: TTLs as the safety net, active key deletion as the daily workhorse, event-driven eviction as the long-term foundation for large systems. Master the five causes and five solutions above and you hold the core of the cache consistency problem.

Source: compiled from technical discussions on Facebook, edited by Wotech Electronics.

Is your system in the state where the database is updated but the application still shows old data? Contact Wotech for a cache architecture review and data-consistency optimization tailored to your business.