Where a Redis Cache Fits
Sprout grows, and its public "Most popular plants" page is opened thousands of times an hour. Every visit runs the same query, which counts plants across every user in the database. Redis, a very fast in-memory key-value store, can keep the answer ready so that most visits never reach PostgreSQL. The catch: a cache is a copy, and copies go stale.
The rule
Check the cache, fall back to the database, forget on change.
This pattern is called cache-aside, because the cache sits beside the database and your code decides when to use each:
app.get('/api/popular', async (req, res) => {
const cached = await redis.get('popular');
if (cached) return res.json(JSON.parse(cached)); // hit
const { rows } = await pool.query( // miss
'SELECT name, COUNT(DISTINCT owner_id) AS owners FROM plants GROUP BY name ORDER BY owners DESC LIMIT 10'
);
await redis.set('popular', JSON.stringify(rows), { EX: 600 });
res.json(rows);
});- Hit. The key exists, so the answer comes straight from memory in about a millisecond, and the database is not touched.
- Miss. Run the real query, store the result as text under a key with an expiry (
EX: 600means 600 seconds), then answer. - Expiry puts a ceiling on staleness: this answer is never more than ten minutes old.
- Forgetting. When a write makes a cached answer wrong and waiting for expiry is not acceptable, the write handler deletes the key with
await redis.del('popular'), so the next read rebuilds it.
In the round trip, Redis sits between the server and the database. The browser never talks to Redis, just as it never talks to PostgreSQL. Full-stack apps also use Redis for other short-lived, fast-changing data, such as rate-limit counters, session records and background job queues.
Not everything should be cached. A good candidate is read far more often than it changes, costly to compute, and fine if slightly old. A user's own plant list is a poor fit: right after watering a plant, they expect to see it at once. Measure first, and cache the endpoints that are actually slow.
The shortcut
For any endpoint you want to cache, answer two questions. "How stale may this be?" gives the expiry. "Which writes change it?" gives the places that must delete the key. If you cannot answer the second, keep the expiry short.
09:00, first visit
Miss. The query runs, the result is stored for 600 seconds, and the visitor gets the answer.
Drills
Answer fast — trust the rule.
In cache-aside, what happens on a cache miss?
What does { EX: 600 } do when the key is set?
Which Sprout data is the worst candidate for caching?
An admin fixes a typo in a cached care guide and it must show at once. What should the edit handler do besides updating PostgreSQL?
Remember
- Check the cache, fall back to the database, forget on change.
- Shortcut: "How stale may this be?" sets the expiry; "Which writes change it?" lists where to delete the key.
- Trap: caching everything. Cache what is read often, slow to compute and fine slightly old.
