Caching strategies: cache-aside, write-through, write-back
Cache-aside, read-through, write-through and write-back compared: how each works, how each fails, and which suits course pages, progress and leaderboards.
On this page 8 sections
The four common caching strategies differ in who fills the cache and when writes reach the database. In cache-aside (lazy loading), the application checks the cache and fills it on a miss; read-through moves that loading into the cache layer; write-through updates the cache and the database together on every write; and write-back, also called write-behind, writes to the cache first and flushes to the database later. Most platforms use cache-aside by default and reach for the others only for specific kinds of data.
Cache-aside (lazy loading)
Cache-aside is the pattern behind most Redis and Memcached code, and the one Microsoft's architecture guide describes as the Cache-Aside pattern. The application talks to both the cache and the database:
def get_course(course_id):
key = f"course:{course_id}"
course = cache.get(key)
if course is None: # miss: load and remember
course = db.fetch_course(course_id)
cache.set(key, course, timeout=600)
return course
def update_course(course_id, changes):
db.update_course(course_id, changes) # 1. write the database
cache.delete(f"course:{course_id}") # 2. then invalidate the copy
Notice that the write path deletes the cached copy rather than updating it. Facebook's paper on scaling memcache explains why it chose the same: deletes are idempotent, so repeating one or running two in the wrong order does no harm, whereas two updates that arrive out of order leave the older value in the cache.
Cache-aside's strengths are that only data someone actually reads gets cached, and that the application keeps working, just slower, if the cache goes down. Its weaknesses are a slow first read after every miss, a window of staleness, and one race worth knowing by heart:
| Step | Request A (reads) | Request B (writes) | Cache now holds |
|---|---|---|---|
| 1 | Misses, reads the fee ₹4,999 from the database | Nothing | |
| 2 | Updates the fee to ₹3,999 | Nothing | |
| 3 | Deletes the cache key | Nothing | |
| 4 | Stores ₹4,999, the value it read in step 1 | ₹4,999, which is stale |
The stale fee now stays until the entry expires. That is why every cache-aside entry needs a TTL as a safety net, however good your invalidation. Stronger fixes exist: Facebook's leases let the cache refuse a write from a reader whose lease was cancelled by a delete in between, and you can store a version or updated_at with each value and refuse to overwrite a newer one. Some teams also repeat the delete a second or so after the write.
Read-through
In read-through caching, the application only ever asks the cache. On a miss, the cache itself, or a library wrapped around it, calls a loader you registered, stores the result and returns it. The behaviour is the same as cache-aside; what changes is where the code lives. Loading logic sits in one place instead of being repeated around every call, which makes it easier to add locking or metrics later.
Redis doesn't load from your database by itself, so with Redis, read-through usually means a small wrapper in your code. Django's cache.get_or_set(key, loader, timeout) is a one-line version of the idea. The same race and the same need for TTLs apply, because the underlying sequence of reads and writes hasn't changed.
Write-through
Write-through updates the cache as part of every write. The application writes the database, and on success writes the new value into the cache straight away, so the next reader finds fresh data instead of a miss. Microsoft's guide makes the contrast explicit: with cache-aside a reader can briefly see a miss or stale data after a write, while write-through lets readers see the new value immediately.
The costs are real. Every write pays for two systems, the cache fills with data nobody may read (so keep a TTL), and a failure between the two writes leaves the cache behind the database. Two concurrent writers can also land their cache writes in the opposite order to their database writes, so compare versions before overwriting. Write-through suits data that is read right after it is written, such as a student's profile settings or a course's published flag, where a stale read would confuse the person who just made the change.
Write-back (write-behind)
Write-back reverses the order: the application writes to the cache, gets an acknowledgement, and a background job flushes changes to the database later, often in batches. It absorbs write bursts and merges repeated updates to the same key into one database write.
Video progress is the textbook case. Take an illustrative 30,000 students watching recorded lectures, with the player reporting each student's position every 15 seconds. Writing every report to the database means 2,000 writes a second. Keeping the latest position per student in Redis and flushing once a minute means 500 row updates a second, and batching them into multi-row statements cuts the number of database round trips much further. If Redis loses a few seconds of positions, a student resumes a few seconds early, which nobody minds.
The risk is exactly that loss. Anything not yet flushed disappears if the cache node dies, and even Redis's append-only file with its default once-a-second fsync can lose about a second of writes. Meanwhile other systems reading the database see old data. Never use write-back for payments, test submissions or marks. If you do use it, drive the flush from a durable queue or stream so a crash can resume where it stopped; message queues and background jobs covers the machinery.
Consistency and failure modes
| Strategy | Read right after your own write | If the cache goes down | Main risk |
|---|---|---|---|
| Cache-aside | A miss, then fresh data, provided the delete succeeded | Reads fall back to the database: slower but correct | Stale sets from races; stampedes when hot keys expire |
| Read-through | Same as cache-aside | Depends on the wrapper; add a database fallback | Same as cache-aside, with loading hidden from view |
| Write-through | Fresh | Writes must skip the cache or fail; reads fall back | Slower writes; cache and database diverging after a partial failure |
| Write-back | Fresh, from the cache | Unflushed writes are lost | Data loss; the database lags behind the truth |
A fifth pattern, refresh-ahead, rebuilds popular entries shortly before they expire so readers never wait for a miss. It is mainly a defence against the cache stampede, where a hot key expires and many requests rebuild it at once.
Which pattern for which data
| Data | Pattern | Why |
|---|---|---|
| Course catalogue and syllabus | Cache-aside, delete on publish, TTL of 10 to 60 minutes | Read constantly, changed rarely; a short stale window is harmless |
| Test paper for a scheduled mock test | Write it into the cache before the test opens, then read-through | Don't let 20,000 students discover the miss at 10 a.m. |
| Video watch position | Write-back | Frequent, merge-able updates where losing seconds is fine |
| Lesson or course completion | Database first, then write-through or delete | Must never be lost; the student expects to see it at once |
| Live leaderboard during a test | Redis sorted set updated on each submission, rebuilt from the database if lost | Sorted sets rank natively; the database stays the record |
| Leaderboard after the test | A materialized view or a cache-aside entry with a 30 to 120 second TTL | Everyone reads the same ranking |
| Fees, payments and order status | No cache, or cache-aside with a TTL of seconds | Correctness beats speed; read from the primary right after a write |
| Live-class viewer counts | Write-back counters | Approximate numbers are fine |
Whatever the pattern, invalidation decides whether it's correct in practice; see cache invalidation for TTL choices and versioned keys, and Redis caching for eviction and memory. For where this layer sits among browser, CDN and database caches, see types of caching.
Key takeaways
- Cache-aside is the default: read the cache, load on a miss, and delete (don't update) on write.
- Read-through is cache-aside with the loading moved into one wrapper; it has the same races.
- Write-through gives fresh reads after writes at the cost of slower writes and partial-failure handling.
- Write-back absorbs write bursts but can lose unflushed data, so keep it to counters and progress pings.
- Give every entry a TTL as a safety net, whichever pattern fills it.
Frequently asked questions
What is cache aside pattern?
Cache-aside is a caching pattern in which the application manages the cache itself. To read, it checks the cache first; on a miss it loads the data from the database, stores a copy in the cache with a TTL and returns it. To write, it updates the database and then deletes the cached copy, so the next read loads the new value. It is the most common way to use Redis or Memcached.
What is cache aside strategy?
As a strategy, cache-aside means caching lazily and invalidating on write: nothing enters the cache until someone reads it, and every change removes the stale copy. Choose it for read-heavy data that changes occasionally, such as course pages. Pair it with a TTL to cap how long a missed invalidation or race can leave stale data, and with a lock or early refresh for very hot keys.
What is look aside cache?
Look-aside cache is another name for cache-aside: the cache sits beside the database rather than in front of it, and the application looks in the cache before going to the database itself. Facebook's memcache paper calls its setup a demand-filled look-aside cache. The opposite arrangement is an inline cache, as in read-through and write-through, where the application talks only to the cache.