Cache Update Strategies and Concurrency Control: CAS, Distributed Locks, Asynchronous Updates, and Delayed Double Delete
This article examines cache update strategies in high‑concurrency systems, analyzing the trade‑offs of delete‑then‑write versus write‑then‑write approaches, illustrating race conditions, and presenting solutions such as CAS, distributed locks, asynchronous updates, and delayed double‑delete to maintain data consistency.
When a write operation occurs, the data read from the cache must stay consistent with the persisted data in the database, which requires careful cache update handling.
Two basic cache‑update methods exist—deleting stale cache entries or directly overwriting them—and they can be combined with two possible ordering choices (database first or cache first), resulting in four strategies that each have specific concurrency pitfalls, often manifested as "race" conditions.
Strategy 1: Update database first, then delete cache. If the cache‑deletion step fails after the database update succeeds, subsequent reads will retrieve stale data from the cache, causing inconsistency.
Strategy 2: Update database first, then update cache. A failure in the cache‑update step after a successful database write also leads to stale data, and concurrent read/write threads can still produce inconsistent results.
Strategy 3: Delete cache first, then update database. While this avoids reading stale data, a failure during the database update leaves the system without the new data, and concurrent writes can still cause race‑condition errors.
Strategy 4: Update cache first, then update database. If the cache update succeeds but the database write fails, the cache holds unpersisted data, which is dangerous because cache data is volatile.
To solve these problems, the article proposes using CAS (Check‑And‑Set) as an optimistic lock. The following Go‑like pseudocode demonstrates the idea:
<span>func CAS(oldVal, newVal) {</span> <span> if cache.get() == oldVal {</span> <span> cache.set(newVal)</span> <span> }</span> <span>}</span>Applying CAS to the “database‑first, cache‑update” scenario prevents the second thread from overwriting the cache when the expected old value has already changed, thereby eliminating the race.
Another solution is a distributed lock (pessimistic locking). By acquiring an exclusive lock before any read or write to the database or cache, the operations become serialized, ensuring consistency at the cost of higher latency.
For asynchronous consistency, the article suggests using a binlog subscription component such as Alibaba’s Canal to capture database changes and update the cache in a separate consumer thread, optionally combined with CAS to handle any residual race conditions.
Finally, the delayed double‑delete technique is described: after a write thread finishes, it waits a short period before deleting the cache again, reducing the window where stale data might be read. Although the exact safe delay cannot be guaranteed, this method is low‑cost and often effective.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
IT Architects Alliance
Discussion and exchange on system, internet, large‑scale distributed, high‑availability, and high‑performance architectures, as well as big data, machine learning, AI, and architecture adjustments with internet technologies. Includes real‑world large‑scale architecture case studies. Open to architects who have ideas and enjoy sharing.
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.
