A comprehensive Redis caching service with support for multiple data structures, circuit breaker pattern, and connection pooling. This service provides a singleton pattern implementation with built-in error handling and monitoring capabilities.
- Singleton Pattern: Ensures single Redis connection instance
- Circuit Breaker: Protects against Redis failures with automatic recovery
- Multiple Data Structures: Supports strings, hashes, JSON, lists, and sets
- Connection Pooling: Configurable concurrency limits and retry strategies
- Environment Isolation: Automatic key prefixing based on environment
- Comprehensive Error Handling: Timeout protection and graceful degradation
- Health Monitoring: Built-in health checks and connection monitoring
To use the CacheService in your microservice, follow these steps:
npm install @pesalink/cache-package
## Environment Variables
```env
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
REDIS_USER=username # Optional
REDIS_PASSWORD=password # Optional
REDIS_TLS=true # Optional, enables TLS
REDIS_CLUSTER=true # Optional, enables cluster mode
REDIS_MAX_CONCURRENT_OPS=100 # Optional, default: 100
REDIS_CACHE_TIMEOUT=10000 # Optional, default: 10000ms
NODE_ENV=production # Optional, used for key prefixing
ENVIRONMENT=production # Alternative to NODE_ENV
const CacheService = require('./CacheService');
// Get singleton instance
const cache = CacheService.getInstance();
// Health check
const isHealthy = await cache.healthCheck();
console.log('Redis is healthy:', isHealthy);
The service automatically builds environment-prefixed keys from objects:
const keyObj = { userId: 123, type: 'profile' };
// Generates key: "production|type:profile|userId:123"
Retrieves cached data stored as JSON string.
const keyObj = { userId: 123, type: 'profile' };
const userData = await cache.getCache(keyObj);
console.log(userData); // null or parsed JSON object
Stores data as JSON string with optional expiry.
const keyObj = { userId: 123, type: 'profile' };
const userData = { name: 'John', email: 'john@example.com' };
await cache.setCache(keyObj, userData, 7200); // 2 hours expiry
Stores data as Redis hash fields.
const keyObj = { userId: 123, type: 'session' };
const sessionData = {
token: 'abc123',
lastActive: '2023-01-01T00:00:00Z',
ipAddress: '192.168.1.1'
};
await cache.setHashCache(keyObj, sessionData, 3600);
Retrieves all fields from a Redis hash.
const keyObj = { userId: 123, type: 'session' };
const sessionData = await cache.getHashCache(keyObj);
console.log(sessionData); // { token: 'abc123', lastActive: '...', ipAddress: '...' }
Retrieves specific fields from a Redis hash.
const keyObj = { userId: 123, type: 'session' };
const fields = ['token', 'lastActive'];
const partialData = await cache.getHashFields(keyObj, fields);
console.log(partialData); // { token: 'abc123', lastActive: '...' }
Stores data using Redis JSON module.
const keyObj = { userId: 123, type: 'preferences' };
const preferences = {
theme: 'dark',
notifications: { email: true, sms: false },
settings: { language: 'en', timezone: 'UTC' }
};
await cache.setJsonCache(keyObj, preferences, 86400); // 24 hours
Retrieves JSON data from Redis.
const keyObj = { userId: 123, type: 'preferences' };
const preferences = await cache.getJsonCache(keyObj);
console.log(preferences); // Full JSON object
Retrieves specific JSON paths.
const keyObj = { userId: 123, type: 'preferences' };
const paths = ['$.theme', '$.notifications.email'];
const partialPrefs = await cache.getJsonPaths(keyObj, paths);
Adds items to a Redis list.
const keyObj = { userId: 123, type: 'activity_log' };
const activities = [
{ action: 'login', timestamp: new Date() },
{ action: 'view_profile', timestamp: new Date() }
];
// Push to right (RPUSH)
await cache.pushToList(keyObj, activities, 604800); // 7 days
// Push to left (LPUSH)
await cache.pushToList(keyObj, activities, 604800, true);
Retrieves items from a Redis list.
const keyObj = { userId: 123, type: 'activity_log' };
// Get all items
const allActivities = await cache.getListRange(keyObj);
// Get first 10 items
const recentActivities = await cache.getListRange(keyObj, 0, 9);
// Get last 5 items
const lastActivities = await cache.getListRange(keyObj, -5, -1);
Adds items to a Redis set.
const keyObj = { userId: 123, type: 'viewed_products' };
const productIds = [101, 102, 103, 104];
await cache.addToSet(keyObj, productIds, 2592000); // 30 days
Retrieves all members from a Redis set.
const keyObj = { userId: 123, type: 'viewed_products' };
const viewedProducts = await cache.getSetMembers(keyObj);
console.log(viewedProducts); // [101, 102, 103, 104]
Implements cache-aside pattern with automatic fallback.
const keyObj = { userId: 123, type: 'profile' };
const userData = await cache.getCachedData(
keyObj,
async () => {
// This function runs only if cache miss
return await database.getUserProfile(123);
},
7200 // 2 hours cache
);
Deletes cached data.
const keyObj = { userId: 123, type: 'profile' };
await cache.deleteCache(keyObj);
Alias for deleteCache()
.
const keyObj = { userId: 123, type: 'profile' };
await cache.invalidateCache(keyObj);
Performs Redis health check.
const isHealthy = await cache.healthCheck();
if (!isHealthy) {
console.error('Redis is not responding');
}
Gracefully closes Redis connection.
await cache.disconnect();
The service includes comprehensive error handling:
- Circuit Breaker: Automatically opens when Redis is unreachable
- Operation Timeouts: All operations have configurable timeouts
- Retry Logic: Built-in retry strategy for connection failures
- Graceful Degradation: Operations fail fast when circuit is open
try {
const data = await cache.getCache(keyObj);
} catch (error) {
if (error.message === 'Redis circuit breaker is open') {
// Handle circuit breaker state
console.log('Redis is temporarily unavailable');
} else if (error.message === 'Redis operation limit reached') {
// Handle concurrency limit
console.log('Too many concurrent operations');
} else {
// Handle other Redis errors
console.error('Redis operation failed:', error);
}
}
// Good: Hierarchical and descriptive
const keyObj = {
service: 'user-service',
userId: 123,
type: 'profile',
version: 'v1'
};
// Avoid: Generic or unclear keys
const keyObj = { id: 123, data: 'stuff' };
// Different TTLs for different data types
await cache.setCache(userProfile, userData, 3600); // 1 hour
await cache.setCache(sessionData, session, 1800); // 30 minutes
await cache.setCache(staticConfig, config, 86400); // 24 hours
// Always handle cache failures gracefully
async function getUserData(userId) {
try {
const cached = await cache.getCache({ userId, type: 'profile' });
if (cached) return cached;
} catch (error) {
logger.warn('Cache read failed, falling back to database', error);
}
// Always have a fallback
return await database.getUser(userId);
}
// Regular health checks
setInterval(async () => {
const healthy = await cache.healthCheck();
if (!healthy) {
// Alert or take corrective action
logger.error('Redis health check failed');
}
}, 30000); // Every 30 seconds
Use Case | Data Structure | Method |
---|---|---|
Simple key-value | String |
getCache() , setCache()
|
Object with multiple fields | Hash |
getHashCache() , setHashCache()
|
Complex nested objects | JSON |
getJsonCache() , setJsonCache()
|
Ordered collections | List |
pushToList() , getListRange()
|
Unique collections | Set |
addToSet() , getSetMembers()
|
- Connection Pooling: Singleton pattern ensures efficient connection usage
- Concurrent Operations: Configurable limits prevent Redis overload
- Timeouts: All operations have timeout protection
-
JSON vs Hash vs String: Choose based on access patterns:
- Use Hash for partial field access
- Use JSON for complex queries and updates
- Use String for simple objects
This service is part of the PesaLink package ecosystem.