Cache the misses too, on a shorter TTL
A lookup that finds nothing writes nothing to the cache, so every repeat of the same bad id goes to the database. Scrapers and broken clients find those ids and keep asking. Write a sentinel on the miss too, with a much shorter TTL than a hit.
const row = await db.user(id);
await redis.set(key, row ? JSON.stringify(row) : "__missing__", { EX: row ? 300 : 30 });Thirty seconds absorbs the flood and still lets a row created a moment later show up quickly. Pick a sentinel no real value can produce, otherwise a cached nothing and a cache miss look the same.
cachingperformance