SysDesignPrep.com
Interactive concepts

Cache-aside vs write-through vs write-back: an interactive simulator

Caching reads is the easy part; the design question is what happens on a write. Cache-aside writes the database and deletes the cached copy. Write-through writes both together. Write-backwrites only the cache and flushes to the database later. Write-around writes only the database and lets the cached copy expire. They trade write latency, read hit ratio, database load, freshness and the risk of losing data.

The playground runs the same ten seconds of reads and writes through all four. Change the share of writes and how often write-back flushes, and compare the cards.

WritesWrite-back flushes every

2,000 operations a second on a few thousand keys, 10% of them writes; a cache read takes 1 ms and a database call 10 ms. Cache-aside deletes a key on every write, so the next read of it misses: hit ratio 78%. Write-through keeps the new value in the cache, so reads hit 87% of the time, but every write waits for both, 11 ms. Write-back acknowledges writes from the cache in 1.0 ms and flushes every 1 s, folding repeated writes to a key into one: 106 database writes a second instead of 204. When the cache node crashes, the 120 keys written since the last flush are lost. Write-around never touches the cache on writes, so cached copies go stale until they expire after 5 s: 57% of reads returned an old value.

Cache-asidewrite DB, delete from cache
Read, average
3.2 ms
Write, average
11 ms
Cache hit ratio
78%
Database writes
204/s
Database reads
387/s
Stale reads
0%
Lost if the cache crashes
nothing
Write-throughwrite DB and cache together
Read, average
2.3 ms
Write, average
11 ms
Cache hit ratio
87%
Database writes
204/s
Database reads
225/s
Stale reads
0%
Lost if the cache crashes
nothing
Write-backwrite cache, flush to DB later
Read, average
2.3 ms
Write, average
1.0 ms
Cache hit ratio
87%
Database writes
106/s
Database reads
225/s
Stale reads
0%
Lost if the cache crashes
120 keys
Write-aroundwrite DB only, let the cache expire
Read, average
2.4 ms
Write, average
10 ms
Cache hit ratio
86%
Database writes
204/s
Database reads
258/s
Stale reads
57%
Lost if the cache crashes
nothing

20,000 operations over 10 s on keys chosen by popularity, so the busiest keys are both read and written often. Cached entries expire after 5 s.

The four policies

On a writeGood atWatch out for
Cache-asideWrite the database, delete the keySimple, the default; the cache holds only what is readA miss after every write; races between a reader filling and a writer deleting
Write-throughWrite the database and the cacheReads after writes hit and are freshEvery write pays for both; caches data that may never be read
Write-backWrite the cache; flush to the database laterFast writes; repeated writes to a key become oneLosing the cache loses unflushed writes
Write-aroundWrite the database onlyWrite-heavy data that is rarely read backCached copies stay stale until they expire

What the playground shows

With 10% writes, cache-aside hits 78% of reads and write-through 87%, because write-through leaves the fresh value in the cache where cache-aside leaves a hole. Push writes to 50% and cache-aside’s hit ratio falls to 45% while write-through stays near 89%. Write-back answers writes in 1 ms and, flushing once a second, sends the database about half as many writes, since a hot key written ten times between flushes is flushed once; but when the cache node crashes, the 120 keys written since the last flush are gone, and with a 10 second flush, 643. Write-around keeps writes simple but serves old values: 57% of reads returned a value the database had since overwritten, each until the 5 second TTL expired it.

Choosing

  • Default to cache-aside with delete-on-write and a TTL. It is simple, it never caches what nobody reads, and the TTL bounds the damage of any bug.
  • Use write-through when data is read right after it is written, like a profile edit, and an extra cache write per update is cheap.
  • Use write-back only where losing recent writes is acceptable or the cache itself is durable and replicated: counters, metrics, rate-limit state, game scores flushed periodically.
  • Use write-around for data written in bulk and rarely read soon after, such as logs or ingestion pipelines.

Whatever the policy, delete rather than update cached values on writes from many writers: two concurrent writers can update the database in one order and the cache in the other, leaving the cache holding the older value with no TTL to rescue it. Deleting, and letting the next read refill, avoids most of that race. For a hot key, pair this with protection against a cache stampede on the refill.

Cache-aside and write-through in code

# cache-aside: the application owns the cache
def get_user(user_id):
    user = cache.get(f"user:{user_id}")
    if user is None:
        user = db.query_one("SELECT * FROM users WHERE id = %s", user_id)
        cache.set(f"user:{user_id}", user, ttl=300)       # fill on miss
    return user

def update_user(user_id, fields):
    db.execute("UPDATE users SET ... WHERE id = %s", user_id)
    cache.delete(f"user:{user_id}")                      # delete, don't set: avoids racing writers

# write-through: write both before acknowledging
def update_user_through(user_id, fields):
    user = db.execute_returning("UPDATE users SET ... WHERE id = %s RETURNING *", user_id)
    cache.set(f"user:{user_id}", user, ttl=300)

In a system design interview

Say the policy per kind of data, and what happens when the cache dies: “Product pages use cache-aside with a 5 minute TTL and delete-on-update. Like counts are write-back: increments go to Redis and a job flushes them to the database every few seconds, and losing a few seconds of likes in a cache failure is acceptable. Sessions are written through, since they are read on the very next request.” Expect the follow-ups: consistency between cache and database, what a cold cache does to the database, and how invalidation reaches every cache node.

Read the guide to caching or cache invalidation, see LRU caches for what gets evicted, or watch a strong candidate design a distributed cache.

Questions people ask

What is the difference between cache-aside and write-through?

With cache-aside the application writes the database and deletes the cached key, and the next read fills the cache from the database. With write-through every write goes to both the database and the cache before it is acknowledged, so reads right after a write hit the cache and see the new value, at the cost of slower writes.

What is write-back caching?

A policy where writes go only to the cache and are acknowledged immediately, and dirty entries are written to the database later, in batches or on eviction. Writes are fast and repeated writes to the same key collapse into one database write, but anything not yet flushed is lost if the cache fails.

What is write-around caching?

A policy where writes go only to the database and the cache is not touched, so it is filled only by reads. It avoids filling the cache with data that is written but rarely read, but a cached copy of a key that is written stays stale until it expires or is evicted.

Should you update or delete the cache on a write?

Usually delete. If two writers update the database and then set the cache, their cache writes can land in the opposite order, leaving the older value cached. Deleting the key lets the next read load the current value from the database, which avoids that race, though a short window for stale fills remains.

Which cache write policy should I use?

Cache-aside with delete-on-write and a TTL is the common default. Use write-through when data is typically read right after being written. Use write-back only when losing recent writes is acceptable or the cache is durable, such as for counters. Use write-around for data written in bulk and rarely read soon after.

Why does write-back reduce database load?

Because a key written many times between flushes is written to the database only once, with its latest value, and flushes can be batched. For hot counters or frequently updated state this can cut database writes by orders of magnitude, which is why it is used for things like view counts and leaderboards.

How do you keep a cache consistent with the database?

Choose a write policy that updates or invalidates the cache on writes, give entries a TTL so any inconsistency is bounded, delete rather than set on writes from concurrent writers, and for stronger needs invalidate from the database’s change stream so every write, from any service, reaches the cache.