This article explores the practical implementation of the cache-aside pattern to address API scalability issues caused by database bottlenecks. It highlights the benefits of using in-memory caches like Redis with Time-To-Live (TTL) for read-heavy workloads, offering a clear comparison with other caching strategies and providing concrete Node.js code examples.
Read original on Dev.to #systemdesignThe article begins by illustrating a common problem in microservice architectures: a single database becomes a bottleneck under increasing load, leading to high latency and error rates. This scenario often arises when every API request results in a direct database round-trip, underscoring the necessity of a caching layer to offload the database and improve response times.
The core insight presented is the power of the cache-aside (lazy loading) pattern combined with short-lived Time-To-Live (TTL) entries. This strategy allows the application layer to manage caching directly: it first checks the cache; if data is present (cache hit), it returns it immediately. If not (cache miss), it fetches data from the primary database, stores it in the cache with a TTL, and then returns it to the client.
| Pattern | Description | Trade-offs |
|---|
The article provides a practical Node.js example demonstrating how to implement cache-aside using Redis. It shows a `GET /users/:id` endpoint being refactored from a direct database query to one that first checks Redis. On a cache miss, data is fetched from PostgreSQL, stored in Redis with a 60-second TTL, and then returned. This approach drastically reduces database load for frequently accessed user data.
const Redis = require('ioredis');
const redis = new Redis({ host: 'redis-cache', port: 6379 });
const TTL_SECONDS = 60; // 1-minute freshness window
app.get('/users/:id', async (req, res) => {
const userId = req.params.id;
const cacheKey = `user:${userId}`;
// 1. Try cache first
const cached = await redis.get(cacheKey);
if (cached) {
return res.json(JSON.parse(cached));
}
// 2. Cache miss – fetch from DB
try {
const user = await db.query('SELECT * FROM users WHERE id = $1', [userId]);
if (user.rows.length === 0) {
return res.status(404).send({ error: 'Not found' });
}
const userData = user.rows[0];
// 3. Store in Redis with TTL for future requests
await redis.setex(cacheKey, TTL_SECONDS, JSON.stringify(userData));
res.json(userData);
} catch (err) {
console.error(err);
res.status(500).send({ error: 'DB error' });
}
});