12 — Caching Strategies: Cache-Aside, Write-Through, Write-Behind, Refresh-Ahead
"Just put Redis in front of the database" was my caching strategy, and it collapsed four different designs into one. Writing them down turned the choice into a precise trade-off: each strategy is a different answer to who owns the write to the cache and when — the application on demand, or the cache itself on every write — and the choice trades read speed, write speed, staleness, and durability in specific, predictable ways. [1][2] "Caching" is not one strategy; it is four, and the differences are where the engineering lives.
The framing that clicked is to read each strategy as a contract between the application, the cache, and the database. The contract says who checks the cache first, who is responsible for writing to it, whether the DB write is synchronous or asynchronous, and whether entries refresh before they expire. Once I could name those four contracts, picking one became a question about which property I cared about most.
The four strategies
- Cache-aside (a.k.a. lazy loading). The application is responsible for reading and writing storage; the cache does not interact with storage directly. On a read, the app looks in the cache; on a miss, it loads from the database, adds the entry to the cache, and returns it [3]. Only the requested data is cached, so the cache is not filled with data nobody asks for. The cost: a cache miss is slow (two trips), and writes that bypass the cache can leave it stale until the entry expires or is evicted.
- Write-through. The application uses the cache as the main data store — it reads and writes to the cache — and the cache is responsible for _synchronously_ writing through to the database [1][2]. Data in the cache is never stale, because every write hits the DB before acknowledging. The cost: writes are slow (they wait for the DB), and newly created cache nodes (after a failure or scale event) start empty until entries are written.
- Write-behind (a.k.a. write-back). Same as write-through, except the cache writes to the database _asynchronously_, improving write performance [1][2]. The cost is real: if the cache goes down before its contents hit the data store, that data is lost. It is more complex to implement than cache-aside or write-through.
- Refresh-ahead. The cache is configured to automatically refresh recently-accessed entries _before_ they expire [4]. If the cache accurately predicts which items will be needed next, refresh-ahead yields lower latency than read-through, because the user never waits for a refresh. The cost: if the prediction is wrong, it does more work than not caching at all and can reduce performance.
The trade-off map
The way of thinking I keep is a 2-axis map: who owns the write (application vs cache), and when the DB sees it (synchronously vs asynchronously / on-demand).
- Application-owned, on-demand (cache-aside): simplest, only caches what is asked for, but misses are slow and staleness is possible.
- Cache-owned, synchronous (write-through): no staleness ever, but every write pays the DB round-trip.
- Cache-owned, asynchronous (write-behind): writes are fast, but durability is at risk and the implementation is complex.
- Cache-owned, predictive (refresh-ahead): reads stay fast for hot keys, but burns resources if predictions miss.
How I use this
Cache-aside is my default — it is the simplest strategy, it does not fill the cache with unrequested data, and it composes cleanly with anything. I move to write-through when staleness is genuinely harmful and the slower writes are acceptable (user profile updates, configuration). I reserve write-behind for the rare write-heavy case where I am willing to accept the durability risk in exchange for throughput, and only when I have a story for cache-crash recovery. Refresh-ahead I use only for a small set of predictably-hot keys where a miss would be user-visible. The discipline is to name the property I am optimizing for — simplicity, freshness, write throughput, or read latency — and pick the strategy whose contract delivers exactly that.
References
[1] J. Bonér, "Scalability, availability, stability patterns," SlideShare, 2014. [Online]. Available: https://www.slideshare.net/jboner/scalability-availability-stability-patterns/
[2] T. Matyashovsky, "From cache to in-memory data grid — introduction to Hazelcast," SlideShare. [Online]. Available: https://www.slideshare.net/tmatyashovsky/from-cache-to-in-memory-data-grid-introduction-to-hazelcast
[3] D. Martin, "Application caching — cache aside," system-design-primer (open source), 2024. [Online]. Available: https://github.com/donnemartin/system-design-primer#application-caching
[4] EnjoyAlgorithms, "Caching strategy: refresh-ahead pattern," 2023. [Online]. Available: https://www.enjoyalgorithms.com/blog/refresh-ahead-caching-pattern
[5] M. Moshikoo, "Caching strategies," Medium, 2022. [Online]. Available: https://medium.com/@mmoshikoo/cache-strategies-996e91c80303
Knowledge check · Question 1 of 4
In cache-aside, who is responsible for writing to the cache?
Comments
Leave a Comment
You must be signed in to comment
0 Comments
No comments yet. Be the first to comment!