tessl install tessl/npm-cache-manager@7.2.0Cache Manager for Node.js with support for multi-store caching, background refresh, and Keyv-compatible storage adapters
Solutions to common cache-manager issues and problems.
Symptoms:
wrap()get() always returns undefinedPossible Causes & Solutions:
1. No TTL Set
// ❌ Problem: No TTL specified
const cache = createCache();
await cache.set('key', 'value'); // May not persist
// ✅ Solution: Set TTL
const cache = createCache({ ttl: 60000 });
await cache.set('key', 'value'); // Uses default TTL
// Or per-operation
await cache.set('key', 'value', 60000);2. TTL Too Short
// ❌ Problem: Expires immediately
await cache.set('key', 'value', 0);
// ✅ Solution: Use appropriate TTL
await cache.set('key', 'value', 60000); // 60 seconds3. Values Not Serializable
// ❌ Problem: Function can't be serialized
await cache.set('func', () => 'hello'); // Error
// ✅ Solution: Only cache serializable values
await cache.set('data', { value: 'hello' }); // WorksSymptoms:
get() returns undefined immediately after set()Diagnosis:
// Check if set succeeded
await cache.set('test', 'value', 60000);
const value = await cache.get('test');
console.log('Retrieved:', value); // Should be 'value'
// Check TTL
const ttl = await cache.ttl('test');
console.log('TTL:', ttl); // Should be future timestamp
// Check store connection
cache.on('set', ({ error }) => {
if (error) console.error('Set failed:', error);
});Symptoms:
Cause: Using nonBlocking: true or refreshAllStores: false
Solution:
// For strong consistency
const cache = createCache({
stores: [memoryStore, redisStore],
nonBlocking: false, // Wait for all stores
refreshAllStores: true, // Refresh all stores
});Symptoms:
Causes & Solutions:
1. LRU Size Too Small
// ❌ Problem
new CacheableMemory({ lruSize: 10 }) // Only 10 items
// ✅ Solution
new CacheableMemory({ lruSize: 1000 }) // 1000 items2. TTL Too Short
// ❌ Problem: Expires too quickly
new CacheableMemory({ ttl: 1000 }) // 1 second
// ✅ Solution: Longer TTL for L1
new CacheableMemory({ ttl: 60000 }) // 1 minute3. Check Hit Rate
let l1Hits = 0, l2Hits = 0, misses = 0;
cache.on('get', ({ value, store }) => {
if (value === undefined) misses++;
else if (store === 'primary') l1Hits++;
else l2Hits++;
});
console.log({ l1Hits, l2Hits, misses });Symptoms:
Diagnosis:
// Measure cache operation time
console.time('cache-get');
await cache.get('key');
console.timeEnd('cache-get');
// Should be:
// - In-memory: < 1ms
// - Redis: 1-10ms
// - Network Redis: 10-50msSolutions:
1. Use Batch Operations
// ❌ Slow: Individual operations
for (const id of ids) {
await cache.get(`user:${id}`); // 1ms each = 100ms for 100 items
}
// ✅ Fast: Batch operation
await cache.mget(ids.map(id => `user:${id}`)); // ~2ms for 100 items2. Add Memory Tier
// ❌ Slow: Redis only
const cache = createCache({
stores: [new Keyv({ store: new KeyvRedis(redisUrl) })],
});
// ✅ Fast: Memory + Redis
const cache = createCache({
stores: [
new Keyv({ store: new CacheableMemory({ lruSize: 1000 }) }),
new Keyv({ store: new KeyvRedis(redisUrl) }),
],
});3. Enable Non-Blocking Mode
// For read-heavy workloads
const cache = createCache({
stores: [memoryStore, redisStore],
nonBlocking: true, // Don't wait for all stores
});Symptoms:
Solutions:
1. Configure LRU Eviction
// ❌ Problem: Unlimited cache growth
const cache = createCache({
stores: [new Keyv()], // No size limit
});
// ✅ Solution: Set LRU limit
const cache = createCache({
stores: [
new Keyv({
store: new CacheableMemory({ lruSize: 1000 })
}),
],
});2. Calculate Appropriate Size
const avgItemSize = 1024; // 1KB per item
const memoryBudget = 50 * 1024 * 1024; // 50MB
const lruSize = Math.floor(memoryBudget / avgItemSize);
console.log(`LRU size: ${lruSize} items`);3. Monitor Memory Usage
setInterval(() => {
const usage = process.memoryUsage();
console.log({
heapUsed: `${Math.round(usage.heapUsed / 1024 / 1024)}MB`,
heapTotal: `${Math.round(usage.heapTotal / 1024 / 1024)}MB`,
});
}, 60000);Symptoms:
Solutions:
1. Check Redis is Running
# Test Redis connection
redis-cli ping
# Should return: PONG
# If not running:
redis-server2. Verify Connection String
// Check Redis URL
console.log('Redis URL:', process.env.REDIS_URL);
// Test connection
import Redis from 'ioredis';
const redis = new Redis(process.env.REDIS_URL);
redis.on('connect', () => console.log('Connected'));
redis.on('error', (err) => console.error('Error:', err));3. Add Retry Logic
const cache = createCache({
stores: [
new Keyv({
store: new KeyvRedis(redisUrl, {
maxRetriesPerRequest: 3,
retryStrategy: (times) => Math.min(times * 50, 2000),
}),
}),
],
});Symptoms:
Solutions:
1. Configure Timeouts
const cache = createCache({
stores: [
new Keyv({
store: new KeyvRedis(redisUrl, {
connectTimeout: 5000, // 5 second connect timeout
commandTimeout: 3000, // 3 second command timeout
}),
}),
],
});2. Monitor Connection State
cache.on('set', ({ error }) => {
if (error && error.message.includes('timeout')) {
console.error('Redis timeout, check network/load');
}
});Symptoms:
Causes & Solutions:
1. Threshold >= TTL
// ❌ Problem
await cache.wrap('key', fetchData, 10000, 15000);
// refreshThreshold (15s) >= ttl (10s)
// ✅ Solution
await cache.wrap('key', fetchData, 10000, 3000);
// refreshThreshold (3s) < ttl (10s)2. Not Enough Time Passed
// Refresh only triggers when:
// - Entry exists
// - wrap() is called
// - remaining TTL < refreshThreshold
await cache.wrap('key', fetchData, 10000, 3000);
// Wait at least 7+ seconds for refresh to trigger
await new Promise(resolve => setTimeout(resolve, 7500));
await cache.wrap('key', fetchData, 10000, 3000); // Triggers refresh3. Monitor Refresh Events
cache.on('refresh', ({ key, error }) => {
if (error) {
console.error(`Refresh failed for ${key}:`, error);
} else {
console.log(`Refreshed ${key}`);
}
});Symptoms:
Solution:
// ❌ Problem: Threshold too high (90% of TTL)
await cache.wrap('key', fetchData, 10000, 9000);
// ✅ Solution: Threshold 10-30% of TTL
await cache.wrap('key', fetchData, 10000, 2000); // 20%Symptoms:
Possible Causes:
1. Different Cache Instances
// ❌ Problem: Multiple caches don't coalesce
const cache1 = createCache();
const cache2 = createCache();
cache1.wrap('key', expensiveFn, 60000); // Executes
cache2.wrap('key', expensiveFn, 60000); // Executes again
// ✅ Solution: Use single cache instance
const cache = createCache();
cache.wrap('key', expensiveFn, 60000); // Executes once2. Errors Not Cached
// Errors are never cached
async function failing() {
throw new Error('Fail');
}
await cache.wrap('key', failing, 60000); // Throws
await cache.wrap('key', failing, 60000); // Throws againSolution:
// ❌ Problem: Type not preserved
const user = await cache.get('user:123');
console.log(user.name); // Error: user might be undefined
// ✅ Solution 1: Explicit type
const user = await cache.get<User>('user:123');
if (user) {
console.log(user.name); // OK
}
// ✅ Solution 2: Type assertion
const user = (await cache.get('user:123')) as User;
console.log(user.name); // OK (but unsafe)Symptoms:
TypeError: Converting circular structure to JSONSolutions:
1. Break Circular References
const circular: any = { a: 1 };
circular.self = circular;
// ❌ Problem
await cache.set('bad', circular); // Error
// ✅ Solution
const safe = { a: circular.a }; // Omit circular.self
await cache.set('good', safe); // Works2. Custom Serializer
import { Keyv } from 'keyv';
const cache = createCache({
stores: [
new Keyv({
serialize: (data) => JSON.stringify(data, (key, value) => {
// Handle circular refs
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) return '[Circular]';
seen.add(value);
}
return value;
}),
deserialize: JSON.parse,
}),
],
});Symptoms:
Solutions:
1. Verify Event Name
// ❌ Problem: Wrong event name
cache.on('gets', handler); // No such event
// ✅ Solution: Correct event name
cache.on('get', handler); // Correct2. Check Listener Signature
// ❌ Problem: Wrong signature
cache.on('get', (key: string) => {
console.log(key); // Wrong - receives object, not string
});
// ✅ Solution: Correct signature
cache.on('get', ({ key, value }) => {
console.log(key, value); // Correct
});Symptoms:
Solution:
// Always remove listeners when done
const listener = ({ key, value }) => {
console.log(key, value);
};
cache.on('get', listener);
// Later, cleanup
cache.off('get', listener);// Log all cache operations
cache.on('get', ({ key, value, store }) => {
console.log(`GET ${key}: ${value !== undefined ? 'HIT' : 'MISS'} ${store || ''}`);
});
cache.on('set', ({ key, value }) => {
console.log(`SET ${key}: ${JSON.stringify(value).slice(0, 50)}`);
});
cache.on('del', ({ key }) => {
console.log(`DEL ${key}`);
});async function checkCacheHealth() {
const testKey = 'health-check';
const testValue = { timestamp: Date.now() };
try {
// Test write
await cache.set(testKey, testValue, 60000);
// Test read
const retrieved = await cache.get(testKey);
if (!retrieved) {
throw new Error('Write succeeded but read failed');
}
// Test delete
await cache.del(testKey);
console.log('✓ Cache health check passed');
return true;
} catch (error) {
console.error('✗ Cache health check failed:', error);
return false;
}
}
// Run periodically
setInterval(checkCacheHealth, 60000);const perfMetrics = {
get: [] as number[],
set: [] as number[],
wrap: [] as number[],
};
// Measure operation times
async function profiledGet<T>(key: string): Promise<T | undefined> {
const start = performance.now();
const result = await cache.get<T>(key);
const duration = performance.now() - start;
perfMetrics.get.push(duration);
return result;
}
// Analyze metrics
function analyzePerformance() {
const avg = (arr: number[]) =>
arr.length > 0 ? arr.reduce((a, b) => a + b, 0) / arr.length : 0;
console.log({
avgGet: `${avg(perfMetrics.get).toFixed(2)}ms`,
avgSet: `${avg(perfMetrics.set).toFixed(2)}ms`,
avgWrap: `${avg(perfMetrics.wrap).toFixed(2)}ms`,
});
}// Cache configuration
console.log('Cache ID:', cache.cacheId());
console.log('Store count:', cache.stores.length);
// Environment
console.log('Node version:', process.version);
console.log('Platform:', process.platform);
console.log('Memory:', process.memoryUsage());
// Package versions
console.log('cache-manager version:', require('cache-manager/package.json').version);
console.log('keyv version:', require('keyv/package.json').version);// Log all events
const events: Array<keyof Events> = [
'get', 'mget', 'set', 'mset',
'del', 'mdel', 'clear', 'ttl', 'refresh'
];
events.forEach(event => {
cache.on(event, (data) => {
console.log(`[${event}]`, data);
});
});