Cache-aside vs read-through
Two ways to fill a cache on a miss, and what changes when the cache does the loading.
5 minute lesson
Who loads the data on a miss?
Both patterns put a cache in front of the database. The difference is who fetches from the database when the cache misses: your code (cache-aside) or the cache itself (read-through).
- cache-aside
- The application checks the cache, loads from the database on a miss and populates the cache itself.
- read-through
- The application reads only from the cache; on a miss the cache calls a registered loader to fetch the value.
- loader
- A function you register with the cache that knows how to fetch a value for a key.
Key terms
Cache-aside in seven lines
async function getProduct(id) {
const hit = await cache.get(`product:${id}`);
if (hit) return JSON.parse(hit);
const product = await db.product.find({ where: { id } });
await cache.set(`product:${id}`, JSON.stringify(product), { EX: 300 });
return product;
}- Lines 2 to 3: Check the cache first. A hit returns right away. The database never hears about it.
- Line 4: Miss: your code queries the database. The app owns the fallback, so it decides what to load and how.
- Line 5: Populate with a TTL. Store the result for the next reader. The TTL bounds staleness if a write path forgets to invalidate.
With cache-aside, the app asked the cache for product:42 and missed. It has just read the product from the database.
What does the app do next?
- Returns the product and leaves the cache empty
- Writes the product into the cache, then returns it
- Waits for the cache to fetch it from the database
- Asks the database to push it into the cache
Show the answer
- Writes the product into the cache, then returns it
In cache-aside the application does all the work: check the cache, read the database on a miss, then fill the cache (usually with a TTL) so the next read hits. The cache knows nothing about the database.
Cache-aside: the app checks, reads the source on a miss, and fills the cache itself.
Read-through: the cache does the fetching
With read-through, the app only ever talks to the cache. On a miss, the cache calls the loader you registered, stores the result and returns it. Same data flow, but the miss logic lives in one place.
- Appget(42)one call
- Cachemisscalls loader
- DatabaseSELECTrun by the loader
Side by side
Cache-aside
app loads
- On a miss
- Your code queries the DB
- Cache down
- Reads fall back to the DB
- Works with
- Any key-value cache
Read-through
cache loads
- On a miss
- The cache calls its loader
- Cache down
- Reads fail without a fallback
- Works with
- Caches that support loaders
Which statements are true of cache-aside?
- The application queries the database on a miss
- The cache calls a registered loader on a miss
- If the cache is down, reads can fall back to the database
- Every write updates the cache and the database together
Show the answer
- The application queries the database on a miss
- If the cache is down, reads can fall back to the database
In cache-aside the cache is a plain key-value store next to your code. Your code handles misses, so it can also route around a cache that's down. A cache that calls a loader is read-through, and updating both stores on every write is write-through.
Cache-aside: your code owns the miss path, so it can route around a dead cache.
With read-through, who fetches the value on a miss, and what does the application gain?
Show the answer
The cache does, through a loader it was configured with. The application only ever asks the cache, so the fetch-and-fill logic lives in one place instead of every caller.
Recap
- Cache-aside: your code checks the cache, loads from the database on a miss and populates the cache.
- Read-through: the cache calls a loader on a miss; the app only talks to the cache.
- Cache-aside degrades gracefully when the cache is down. Neither pattern handles writes for you.