3.7 KiB
Common Pitfalls
Pitfall 1: Infinite TTL
Problem: Setting cache values without TTL leads to stale data and memory growth.
Solution: Always use setex() or set() with EX option. Never use set() alone.
await redis.setex(key, 300, value)
Pitfall 2: Cache Key Inconsistency
Problem: Query parameter order affects cache key, causing cache misses.
Solution: Sort object keys before hashing or use deterministic key generation.
function generateKey(obj: Record<string, unknown>) {
const sorted = Object.keys(obj).sort().reduce((acc, key) => {
acc[key] = obj[key]
return acc
}, {} as Record<string, unknown>)
return JSON.stringify(sorted)
}
Pitfall 3: Missing Invalidation Paths
Problem: Cache invalidated on direct updates but not on related mutations.
Solution: Map all mutation paths and ensure comprehensive invalidation.
async function deleteUser(userId: string) {
await prisma.user.delete({ where: { id: userId } })
await Promise.all([
redis.del(`user:${userId}`),
redis.del(`posts:author:${userId}`),
redis.del(`comments:author:${userId}`),
])
}
Pitfall 4: Caching Pagination Without Page Number
Problem: Different pages cached with same key, returning wrong results.
Solution: Include skip/take or cursor in cache key.
const cacheKey = `posts:skip:${skip}:take:${take}`
Pitfall 5: No Redis Fallback
Problem: Application crashes when Redis unavailable.
Solution: Wrap Redis operations in try/catch, fallback to database.
async function getCachedData(key: string, fetchFn: () => Promise<unknown>) {
try {
const cached = await redis.get(key)
if (cached) return JSON.parse(cached)
} catch (err) {
console.error('Redis error, falling back to database:', err)
}
return fetchFn()
}
Pitfall 6: Caching Sensitive Data
Problem: Storing passwords, tokens, or sensitive credentials in cache.
Solution: Never cache authentication tokens, passwords, or PII without encryption.
async function getCachedUser(userId: string) {
const cacheKey = `user:${userId}`
const cached = await redis.get(cacheKey)
if (cached) return JSON.parse(cached)
const user = await prisma.user.findUnique({
where: { id: userId },
select: {
id: true,
email: true,
name: true,
role: true,
},
})
if (user) {
await redis.setex(cacheKey, 300, JSON.stringify(user))
}
return user
}
Pitfall 7: Pattern Matching in Hot Paths
Problem: Using redis.keys('pattern:*') in high-traffic endpoints causes performance degradation.
Solution: Use Redis SCAN for pattern matching or maintain explicit key sets.
async function invalidatePostCacheSafe(postId: string) {
const cursor = '0'
const pattern = 'posts:*'
const keysToDelete: string[] = []
let currentCursor = cursor
do {
const [nextCursor, keys] = await redis.scan(
currentCursor,
'MATCH',
pattern,
'COUNT',
100
)
keysToDelete.push(...keys)
currentCursor = nextCursor
} while (currentCursor !== '0')
if (keysToDelete.length > 0) {
await redis.del(...keysToDelete)
}
await redis.del(`post:${postId}`)
}
Pitfall 8: Serialization Issues
Problem: Storing Prisma model instances directly without serialization.
Solution: Always use JSON.stringify for caching, JSON.parse for retrieval.
const user = await prisma.user.findUnique({ where: { id: userId } })
await redis.setex(
`user:${userId}`,
300,
JSON.stringify(user)
)
const cached = await redis.get(`user:${userId}`)
if (cached) {
const user = JSON.parse(cached)
return user
}