This library implements Redis 6+ Client-Side Caching using the
CLIENT TRACKING
command in BCAST (Broadcast) mode for the popular ioredis
Node.js client.
It helps reduce latency and load on your Redis server by maintaining a local in-memory cache for specified key prefixes. The local cache is automatically invalidated when keys matching the tracked prefixes are modified in Redis by other clients, thanks to Redis Pub/Sub notifications.
- Automatic In-Memory Caching: Caches Redis key/value pairs locally within your Node.js application instance.
- Prefix-Based Tracking (BCAST Mode): Monitors changes to any key matching specified prefixes, regardless of whether the current client instance read the key.
-
Automatic Invalidation: Uses Redis 6+
CLIENT TRACKING
(BCAST mode) and Pub/Sub (__redis__:invalidate
channel) to remove stale data from the local cache when changes occur in Redis. - Configurable Local TTL: Set a time-to-live (TTL) specifically for the local in-memory cache, independent of the Redis key's TTL.
-
Standard Cache Interface: Provides familiar methods like
getCached
,setCached
,mGetCached
,mSetCached
,delCached
. -
Uses
ioredis
: Built on top of theioredis
client. You provide your configuredioredis
instance. - NOLOOP: Prevents the client from receiving invalidation messages for changes it makes itself.
- Node.js: Version 14 or higher recommended.
-
Redis Server: Version 6.0 or higher (required for
CLIENT TRACKING
). -
ioredis
: This library requiresioredis
as a peer dependency or direct dependency in your project.
Assuming you save the provided code as redisClientSideCache.js
within your project (e.g., in a lib
folder):
# Install ioredis if you haven't already
npm install ioredis
# or
yarn add ioredis
const Redis = require('ioredis');
const { RedisClientSideCache } = require('redis-csc');
// Create your ioredis client
const redis = new Redis({
host: 'localhost',
port: 6379
});
// Initialize the client-side cache
const prefixes = ['user:', 'product:']; // Keys to track
const defaultExpiry = 3600; // 1 hour default expiry for Redis keys
const invalidationCallback = (invalidatedKeys) => {
console.log('Keys invalidated:', invalidatedKeys);
};
const readyTimeoutMs = 5000; // 5 seconds timeout for initial connection
const localCacheTTL = 300; // 5 minutes local cache TTL
const cache = await RedisClientSideCache.create(
redis,
prefixes,
defaultExpiry,
invalidationCallback,
readyTimeoutMs,
localCacheTTL
);
-
client
(Required): An initializedioredis
client instance -
prefixes
(Required): Array of key prefixes to track (e.g.,['user:', 'product:']
) -
defaultExpiry
(Optional): Default TTL in seconds for Redis keys (default: 1 week) -
listener
(Optional): Callback function for invalidation events -
readyTimeoutMs
(Optional): Connection timeout in milliseconds (default: 5000) -
localCacheTTL
(Optional): Local cache TTL in seconds (default: 300)
const { data, cacheHits, cacheMisses } = await cache.getCached('user:123');
console.log(data); // The value or null if not found
console.log(cacheHits); // 1 if served from local cache, 0 if from Redis
console.log(cacheMisses); // 1 if fetched from Redis, 0 if from local cache
// Set with default expiry
await cache.setCached('user:123', JSON.stringify({ name: 'John' }));
// Set with custom expiry (in seconds)
await cache.setCached('user:123', JSON.stringify({ name: 'John' }), 3600);
const { data, cacheHits, cacheMisses } = await cache.mGetCached([
'user:123',
'user:456',
'product:789'
]);
console.log(data); // Object with key-value pairs
console.log(cacheHits); // Number of keys served from local cache
console.log(cacheMisses); // Number of keys fetched from Redis
const data = {
'user:123': JSON.stringify({ name: 'John' }),
'user:456': JSON.stringify({ name: 'Jane' })
};
// Set with default expiry
await cache.mSetCached(data);
// Set with custom expiry (in seconds)
await cache.mSetCached(data, 3600);
// Delete a single key
const deletedCount = await cache.delCached('user:123');
// Delete multiple keys
const deletedCount = await cache.delCached(['user:123', 'user:456']);
cache.clearLocalCache();
const stats = cache.getLocalCacheStats();
console.log(stats.size); // Number of keys in local cache
console.log(stats.keys); // Array of keys in local cache
// Disconnect the cache (doesn't affect the main Redis client)
await cache.disconnect();
// Don't forget to quit your main Redis client when done
await redis.quit();
-
Local Caching: When you fetch a key using
getCached
ormGetCached
, the library:- First checks the local in-memory cache
- If not found locally, fetches from Redis
- Stores the Redis response in local cache if it matches tracked prefixes
-
Automatic Invalidation: When any Redis client modifies a tracked key:
- Redis sends an invalidation message via Pub/Sub
- The library automatically removes the key from local cache
- Subsequent requests fetch fresh data from Redis
-
TTL Management:
-
defaultExpiry
: Controls how long keys persist in Redis -
localCacheTTL
: Controls how long keys persist in local memory - Local cache entries expire independently of Redis TTLs
-
-
Choose Prefixes Carefully:
- Be specific to avoid tracking unnecessary keys
- Consider data access patterns and update frequency
- Example:
user:profile:
instead of justuser:
-
Memory Management:
- Set appropriate
localCacheTTL
to prevent memory bloat - Monitor cache size using
getLocalCacheStats()
- Clear cache manually if needed using
clearLocalCache()
- Set appropriate
-
Error Handling:
- Wrap cache operations in try-catch blocks
- Handle disconnections gracefully
- Monitor invalidation events using the callback
- Only works with Redis 6.0 or higher due to
CLIENT TRACKING
requirement - Requires two Redis connections (main + subscriber)
- Local cache is not shared between different Node.js processes
- Memory usage grows with number of cached keys
MIT