Is the Classic Update‑DB → Delete‑Cache → TTL Pattern Really the Best Way to Keep Cache Consistent?
The article examines why the common update‑database, delete‑cache, add‑TTL workflow can still produce permanent stale data under high concurrency, explains the underlying race conditions, and compares several alternative strategies—including delete‑first, binlog‑driven invalidation, and lease‑based approaches—to help engineers choose the most reliable and low‑complexity solution for cache consistency.
Understanding Cache Consistency
Many developers assume that updating the database, deleting the related cache entry, and finally adding a TTL is the safest cache‑update pattern, but this approach still has vulnerabilities in concurrent environments.
Cache consistency is defined as: if a key exists in the cache, its value must eventually match the value stored in the underlying data store.
From this definition two observations follow:
If a key is missing from the cache, consistency is irrelevant, but the cache also provides no performance benefit.
The term "eventually" permits brief inconsistencies but forbids permanent stale data; short‑lived stale values are usually acceptable, whereas permanent stale values constitute a failure.
Strong consistency is avoided because caches and databases are separate systems without cross‑system transactions; achieving it would require distributed locks or consensus protocols, negating the performance gains of caching.
In practice, engineers talk about eventual consistency ; the comparison metric is the length of the inconsistency window and the probability of permanent dirty data.
Permanent Inconsistency in Cache‑Aside
Cache‑Aside is the dominant pattern: a read checks the cache first, falls back to the database on a miss, and writes the result back to the cache; writes must manually keep the database and cache in sync.
Without a TTL fallback, a subtle race can cause long‑lasting dirty data:
Request A reads the cache and misses.
Request A queries the database and obtains an old value.
Request B updates the database and deletes the cache entry (the entry may already be absent).
Request A writes the stale value back into the cache.
From step 4 onward the cache holds stale data indefinitely until the TTL expires.
The root cause is that the read‑from‑DB and the write‑back to the cache are not atomic; any concurrent write can render the cached value outdated during the network round‑trip.
High‑concurrency hot‑spot workloads make this gap inevitable, so the problem must be addressed.
Choosing Among Four Write Strategies
When updating data, two decisions create four possible patterns: whether the cache operation is an update or a delete , and whether the database or the cache is touched first.
Deleting the cache is generally preferred for three reasons:
Updates can become out‑of‑order under concurrent writes, leaving the cache with an older value; deletions are order‑agnostic.
For write‑heavy, read‑light data, constantly refreshing the cache wastes resources; deletion postpones cache population until a real read occurs.
Cache entries often store derived results (joins, aggregations); recomputing on every write is costly, whereas deletion avoids that cost.
Deletion here means invalidating only the affected key, not flushing the entire cache, so other keys continue to hit.
Regarding order, deleting the cache before committing the database widens the inconsistency window to the entire transaction duration, dramatically increasing the chance of stale reads. Therefore, the default and most balanced approach is: update the database first, then delete the cache, and finally set a TTL.
Remaining Issues After DB‑Then‑Delete
Even with the preferred pattern, two problems persist:
The read‑back race described earlier can still occur, though its probability is much lower because the write‑back is usually faster than the DB‑delete sequence.
Cache deletion may fail due to network glitches or cache‑service outages, leaving stale data until the TTL expires.
The TTL acts as a universal safety net; its duration should be chosen based on business tolerance for stale data, not as a technical parameter.
Retrying Failed Deletions
A common remedy is to retry deletions asynchronously via a message queue; if retries eventually fail, an alert is raised. This avoids blocking the business thread but introduces extra code at every update entry point, increasing the risk of missed invalidations.
Centralising Invalidation with Binlog Subscription
A cleaner solution is to let the application only write to the database and delegate cache invalidation to a separate component that subscribes to MySQL binlog events (e.g., Alibaba’s open‑source Canal). The component parses committed changes, maps them to cache keys, and deletes the corresponding entries.
Benefits include:
All invalidation logic is concentrated in one place, eliminating scattered code.
Only committed changes trigger deletions, avoiding roll‑backs.
Ordered processing and replayability: binlog preserves transaction order, and failed consumptions can be replayed from a saved offset.
The trade‑off is added latency (tens of milliseconds to a few seconds) and operational overhead for the binlog consumer.
Facebook’s Lease Mechanism to Block Write‑Back Races
To eliminate the read‑back race, Facebook uses a lease token in Memcache (described in the NSDI 2013 paper *Scaling Memcache at Facebook*). When a read misses, Memcache issues a lease; the client must present the lease when writing back. If the key is deleted before the lease is used, the lease becomes invalid and the write‑back is rejected.
This also mitigates cache stampede: only one request obtains a lease for a hot key, performs the DB read, and repopulates the cache, while other concurrent requests wait and later hit the fresh entry. The paper reports that lease adoption reduced peak DB queries from 17 k QPS to 1.3 k QPS.
Redis lacks a native lease primitive, but a similar effect can be achieved with a version field and a CAS‑style check using a Lua script that atomically reads, compares, and writes.
Version‑based leases add extra reads and may themselves become a source of inconsistency if the version is stored in Redis; therefore they are suitable only for extremely hot keys where the benefit outweighs the complexity.
Solution Comparison
Only TTL – Inconsistency window equals TTL; no permanent dirty data; lowest complexity; suitable for rarely‑changed data with high tolerance.
Delete‑then‑DB – Large window (DB‑write time); high permanent dirty risk; low complexity; not recommended as primary choice.
DB‑then‑Delete + TTL – Millisecond‑level window; low permanent dirty risk (covered by TTL); low complexity; default for most workloads.
DB‑then‑Delete + Message‑Queue Retry – Same window; lower permanent dirty risk due to retry; medium complexity; for data sensitive to stale values where binlog is not desired.
Binlog‑driven Invalidation (Canal) – Window of tens of ms to seconds, monitorable; no permanent dirty data on the invalidation side; medium‑high complexity; fits systems with many update entry points or multiple cache replicas.
Lease or Version Check on Write‑Back – Eliminates write‑back race; essentially no permanent dirty data; high complexity; ideal for ultra‑high‑frequency hot keys where stale values are unacceptable.
Conclusion
All cache‑consistency techniques aim to shrink the inconsistency window and bound the lifetime of dirty data. The real decision is not which method achieves absolute consistency, but which provides the shortest, most controllable window with acceptable complexity for the business’s tolerance to stale values.
By first answering how long a business can tolerate outdated data, engineers can select the simplest approach that keeps the inconsistency window within that bound, rather than starting from the most feature‑rich solution.
Thank you for reading; hope this analysis helps you design a more reliable caching layer.
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.
samdeepthink
Knowledge Planet: Old Dock's Tech Chronicles Zhihu: SamDeepThinking A technical manager who still codes heavily on the front line. From junior developer to tech lead, then tech manager, now leading the whole front‑ and back‑end development team—leveling up along the way. I have some insights on programming, career development, and tech management.
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.
