Reasonable Strategies for Database and Cache Read/Write Consistency
The article discusses how to achieve reasonable read/write consistency between databases and caches by emphasizing cache expiration, eventual consistency, analyzing the Cache‑Aside pattern, evaluating four update‑order options, recommending the "update‑DB‑then‑delete‑cache" approach, and suggesting async deletion via message queues and distributed locks to handle concurrency and cache breakdown.
Introduction
When using a database together with a cache (e.g., MySQL and Redis), developers must agree on two basic principles: the cache must have an expiration time, and eventual consistency between the database and cache is sufficient; strong consistency is not required.
Database and Cache Read/Write Order
The classic solution is the Cache‑Aside pattern, which consists of three steps:
Miss: read from cache; if absent, read from database and populate the cache.
Hit: read directly from cache.
Update: update the database first, then delete the cache entry.
The first two steps are straightforward, but the update step raises the question of why the database should be updated before the cache and why the cache should be deleted rather than updated.
Four Possible Update Options
Update cache first, then update database.
Update database first, then update cache.
Delete cache first, then update database.
Update database first, then delete cache.
Option 1: Update Cache First, Then Database
If the database update fails after the cache has been changed, the system ends up with stale data in the database and the cache, potentially losing the update forever.
Option 2: Update Database First, Then Cache
This approach suffers from concurrency problems: two threads may update the database in different orders, causing the later cache update to overwrite newer data. It also wastes cache space when updates are frequent but reads are rare.
Option 3: Delete Cache First, Then Update Database
In a write‑read race, a read request may fetch stale data from the database after the cache has been deleted but before the new value is written, leading to the stale value being written back into the cache.
Option 4: Update Database First, Then Delete Cache (Recommended)
This is the most common practice. Although a brief window exists where reads may see stale data, the probability is low because reads far outnumber writes. In read‑write‑split architectures, the window can be larger due to replication lag, but the approach still offers the best trade‑off between performance and consistency.
Improving the Recommended Approach
Two practical problems remain: cache deletion may fail, and the delete‑then‑read race can cause cache breakdown under high concurrency. To mitigate these, the article suggests:
Retrying cache deletions with exponential back‑off.
Offloading deletion to an asynchronous worker or thread pool.
Using a message queue to reliably record delete requests and let a consumer handle retries.
In read‑write‑split scenarios, subscribing to binlog (e.g., via Canal) to detect when a cache entry should be removed.
Cache Breakdown (Cache Stampede)
When many requests simultaneously miss a just‑expired cache entry, they all hit the database, causing a stampede. The simple mitigation is to use a lock: the first request acquires a lock, reads from the database, populates the cache, and releases the lock; others wait and retry.
For multi‑machine environments, a distributed lock (e.g., Curator's InterProcessReadWriteLock or Redisson's RReadWriteLock ) may be needed, though it introduces performance overhead and complexity.
Achieving Strong Consistency
Strong consistency would require blocking all reads until the database update and cache deletion complete, which can be realized with a distributed read‑write lock: writers acquire an exclusive lock before updating the database; readers acquire a shared lock before reading.
Implementing such locks is non‑trivial and may degrade performance, so the article advises weighing the cost against the benefits of caching.
Summary
Choosing a consistency strategy is a trade‑off. Using a cache inevitably sacrifices strong consistency for performance. Properly setting expiration times, handling cache breakdown, and employing async deletion via message queues can mitigate most consistency issues while preserving the performance gains of caching.
Author Bio
Lv Yadong, a technical expert in a leading internet risk‑control company, focuses on high‑performance, high‑concurrency systems and middleware optimization.
Top Architect
Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.