Cache write strategies: write-through, write-back, write-around and cache-aside
When data changes, who updates the cache, who updates the database, and in what order. Each answer has a different failure.
By devskills editorial team · Last updated
What problem do cache write strategies solve?
A cache holds a copy of data that lives in a database. Reads are the easy half. The hard half is writes: when a value changes, the cache and the database must be updated in some order, by someone, and every choice leaves a different window where they disagree or where data can be lost.
Interviewers ask "write-through or write-back?" to see whether you know what each costs when something crashes halfway. The names matter less than being able to say what a user sees in that window.
What cache write strategies cost you
When to reach for cache write strategies, and when not to
Use it when
- Cache-aside with delete-on-write: the default for read-heavy application data, such as profiles, products and settings.
- Write-through: data that is read straight after it is written, where a stale read would confuse the user.
- Write-back: very high write rates where losing the last second or two is acceptable, such as counters, metrics and presence.
- Write-around: data written once and rarely read soon after, such as logs, uploads and audit records.
Do not, when
- Write-back for money, orders or anything you cannot recompute. A cache crash becomes data loss.
- Write-through for data that is mostly written and rarely read. You pay twice per write to cache things nobody asks for.
- Any strategy for a value a commit decision depends on, such as remaining stock. Read that from the database inside the transaction.
How cache write strategies actually work
Two of these names describe the read path and three describe the write path, which is why the comparison confuses people. Cache-aside (also called lazy loading) and read-through say what happens on a read miss. Write-through, write-back (also called write-behind) and write-around say what happens on a write. A real system picks one of each. The most common pairing is cache-aside reads with delete-on-write.
Cache-aside. The application checks the cache; on a miss it reads the database, puts the value in the cache with a TTL, and returns it. On a write it updates the database and then deletes the cache key, so the next read reloads it. The cache only ever holds data someone asked for, and if the cache is down the application still works, just slower.
Read-through. Same behaviour, but the cache layer loads from the database itself, so application code only ever talks to the cache. It is tidier, and it ties the cache to your database schema.
Write-through. Every write goes to the cache and the database together, and returns only when both have it. Reads after a write are fresh. Write latency is the sum of both, and you cache everything written whether or not anyone reads it, which is why it is often paired with a TTL.
Write-back. The write goes to the cache and returns at once; the cache writes to the database later, often batching many updates to one key into one write. That is the fastest option and it absorbs bursts. The cost is the window: if the cache node dies before flushing, those writes are gone, and if the flush fails, the database falls behind silently. Use it only where the data can be lost or rebuilt, or where the cache itself is replicated and persisted.
Write-around. The write goes straight to the database and skips the cache (usually deleting any cached copy). The cache fills only when something is read. It suits write-heavy data that is rarely read back soon, and keeps one-off writes from pushing useful entries out of the cache.
The race that bites cache-aside. Reader A misses and reads the old value from the database. Writer B updates the database and deletes the key. Reader A, slow, now writes its old value into the cache. The cache is stale until the TTL expires, and nothing errors. Defences, cheapest first: keep a TTL on every key as a safety net; delete after the transaction commits, never before; delete a second time a short moment later ("delayed double delete"); or use leases, where the cache hands a token to the first reader on a miss and rejects its fill if the key was deleted in the meantime. Facebook's memcache paper describes leases for exactly this race.
Order matters in write-through too. Write the cache first and the database second, and a database failure leaves the cache holding a value that was never stored. Write the database first, then update or delete the cache; if the cache step fails, the TTL limits the damage.
Choosing by read/write ratio. Mostly reads with occasional writes: cache-aside plus delete-on-write plus a TTL. Reads right after writes, where a stale read is visible: write-through. Huge write volume to a few hot keys, where some loss is fine: write-back. Many writes that are rarely read: write-around.
| Policy | Path | Who writes the database | Stale reads | Write latency | Can lose writes? |
|---|---|---|---|---|---|
| Cache-aside (lazy loading) | Read | The application | Until delete or TTL; a race can extend it | Database only | No |
| Read-through | Read | The application, or the cache library | Same as cache-aside | Depends on the write policy | No |
| Write-through | Write | The cache layer, synchronously | Rare | Cache + database | No |
| Write-back (write-behind) | Write | The cache, later | None from the cache; the database lags | Cache only | Yes, anything not yet flushed |
| Write-around | Write | The application, directly | First read after a write is a miss | Database only | No |
func (s *Store) GetUser(ctx context.Context, id int64) (User, error) { key := fmt.Sprintf("user:%d", id) if u, ok := s.cache.Get(ctx, key); ok { return u, nil // hit } u, err := s.db.LoadUser(ctx, id) // miss: go to the source of truth if err != nil { return User{}, err } s.cache.Set(ctx, key, u, 10*time.Minute) // TTL is the safety net return u, nil}func (s *Store) RenameUser(ctx context.Context, id int64, name string) error { if err := s.db.UpdateName(ctx, id, name); err != nil { // database first return err } // then invalidate, after the commit; if this fails, the TTL caps the damage s.cache.Delete(ctx, fmt.Sprintf("user:%d", id)) return nil}- Writing the cache before the database. A failed database write leaves a value in the cache that was never saved.
- Calling write-back "write-through with batching" and skipping the data-loss window.
- Cache-aside with no TTL. One missed delete and the stale value lives forever.
- Treating read-through and write-through as competitors. One is a read policy and one is a write policy.
The follow-up an interviewer will ask about cache write strategies
"You use cache-aside and delete the key after every update. A user still saw an old value for ten minutes. How?"
The classic read-write race. A reader missed the cache and read the row from the database just before a writer updated it. The writer committed and deleted the key. Then the reader, which was slow, wrote the old row into the cache, after the delete. Nothing failed, so nothing logged it, and the old value lived until its ten-minute TTL. I would check first that the delete happens after the commit, because deleting before commit opens the same window wider. Then, depending on how much a stale read hurts: shorten the TTL for this kind of data, add a second delete a second or so after the write, or use leases so a fill from a reader that started before the delete is rejected. Cost: a shorter TTL means more misses and more database load, and leases need support in the cache client. For a profile page I would take the shorter TTL; for something like permissions I would read from the database.
Cache write strategies: common questions
What is the difference between write-through and write-back?
Write-through confirms a write only after both the cache and the database have it: slower writes, nothing lost. Write-back confirms once the cache has it and writes the database later: faster writes, and anything not flushed is lost if the cache fails.
Is write-behind the same as write-back?
Yes. Both names describe writing to the cache now and to the database asynchronously later. "Write-back" comes from CPU caches; "write-behind" is more common in application caching products.
Cache-aside vs read-through: which should I use?
They behave the same on a miss. In cache-aside the application loads from the database and fills the cache; in read-through the cache layer does it. Cache-aside is the usual choice with Redis or Memcached, because neither loads from your database for you.
What is write-around caching?
Writes go straight to the database and bypass the cache, which is only filled by reads. It suits data that is written often and read rarely or much later, and keeps one-off writes from evicting useful entries.
Which strategy does Redis use?
None by itself. Redis is the store; the strategy lives in your application code or in a caching library around it. Most applications use Redis with cache-aside reads and delete-on-write.
Your turn
Pick a write strategy for each and say why: (a) a video's view count, (b) a user's display name, (c) an account balance.
Learn it from the source
- Amazon ElastiCache — Caching strategies ↗Lazy loading versus write-through, with the trade-offs and TTL advice, from a managed cache vendor.
- Scaling Memcache at Facebook (NSDI 2013) ↗The look-aside cache at very large scale, including leases for the stale-set race described above.
Related concepts
All concepts on one page: system design cheat sheet