add changes
This commit is contained in:
318
config/redis.js
Normal file
318
config/redis.js
Normal file
@@ -0,0 +1,318 @@
|
||||
const Redis = require('ioredis');
|
||||
const logger = require('./logger');
|
||||
|
||||
/**
|
||||
* Redis Connection Configuration
|
||||
* Supports both single instance and cluster modes
|
||||
*/
|
||||
|
||||
const redisConfig = {
|
||||
host: process.env.REDIS_HOST || 'localhost',
|
||||
port: parseInt(process.env.REDIS_PORT) || 6379,
|
||||
password: process.env.REDIS_PASSWORD || undefined,
|
||||
db: parseInt(process.env.REDIS_DB) || 0,
|
||||
retryStrategy: (times) => {
|
||||
// Stop retrying after 3 attempts in development
|
||||
if (process.env.NODE_ENV === 'development' && times > 3) {
|
||||
logger.info('Redis unavailable - caching disabled (optional feature)');
|
||||
return null; // Stop retrying
|
||||
}
|
||||
const delay = Math.min(times * 50, 2000);
|
||||
return delay;
|
||||
},
|
||||
maxRetriesPerRequest: 3,
|
||||
enableReadyCheck: true,
|
||||
enableOfflineQueue: true,
|
||||
lazyConnect: true, // Don't connect immediately
|
||||
connectTimeout: 5000, // Reduced timeout
|
||||
keepAlive: 30000,
|
||||
family: 4, // IPv4
|
||||
// Connection pool settings
|
||||
minReconnectInterval: 100,
|
||||
maxReconnectInterval: 3000,
|
||||
// Reduce logging noise
|
||||
showFriendlyErrorStack: process.env.NODE_ENV !== 'development'
|
||||
};
|
||||
|
||||
// Create Redis client
|
||||
let redisClient = null;
|
||||
let isConnected = false;
|
||||
|
||||
try {
|
||||
redisClient = new Redis(redisConfig);
|
||||
|
||||
// Attempt initial connection
|
||||
redisClient.connect().catch(() => {
|
||||
// Silently fail if Redis is not available in development
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
logger.info('Redis not available - continuing without cache (optional)');
|
||||
}
|
||||
});
|
||||
|
||||
// Connection events
|
||||
redisClient.on('connect', () => {
|
||||
logger.info('Redis client connecting...');
|
||||
});
|
||||
|
||||
redisClient.on('ready', () => {
|
||||
isConnected = true;
|
||||
logger.info('Redis client connected and ready');
|
||||
});
|
||||
|
||||
redisClient.on('error', (err) => {
|
||||
isConnected = false;
|
||||
// Only log errors in production or first error
|
||||
if (process.env.NODE_ENV === 'production' || !errorLogged) {
|
||||
logger.error('Redis client error:', err.message || err);
|
||||
errorLogged = true;
|
||||
}
|
||||
});
|
||||
|
||||
redisClient.on('close', () => {
|
||||
if (isConnected) {
|
||||
isConnected = false;
|
||||
logger.warn('Redis client connection closed');
|
||||
}
|
||||
});
|
||||
|
||||
redisClient.on('reconnecting', () => {
|
||||
// Only log once
|
||||
if (isConnected === false) {
|
||||
logger.info('Redis client reconnecting...');
|
||||
}
|
||||
});
|
||||
|
||||
redisClient.on('end', () => {
|
||||
if (isConnected) {
|
||||
isConnected = false;
|
||||
logger.info('Redis connection ended');
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error('Failed to create Redis client:', error);
|
||||
}
|
||||
|
||||
// Track if error has been logged
|
||||
let errorLogged = false;
|
||||
|
||||
/**
|
||||
* Check if Redis is connected
|
||||
*/
|
||||
const isRedisConnected = () => {
|
||||
return isConnected && redisClient && redisClient.status === 'ready';
|
||||
};
|
||||
|
||||
/**
|
||||
* Get Redis client
|
||||
*/
|
||||
const getRedisClient = () => {
|
||||
if (!isRedisConnected()) {
|
||||
logger.warn('Redis client not connected');
|
||||
return null;
|
||||
}
|
||||
return redisClient;
|
||||
};
|
||||
|
||||
/**
|
||||
* Close Redis connection gracefully
|
||||
*/
|
||||
const closeRedis = async () => {
|
||||
if (redisClient) {
|
||||
await redisClient.quit();
|
||||
logger.info('Redis connection closed');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Cache helper functions
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get cached data
|
||||
* @param {string} key - Cache key
|
||||
* @returns {Promise<any>} - Parsed JSON data or null
|
||||
*/
|
||||
const getCache = async (key) => {
|
||||
try {
|
||||
if (!isRedisConnected()) {
|
||||
logger.warn('Redis not connected, cache miss');
|
||||
return null;
|
||||
}
|
||||
|
||||
const data = await redisClient.get(key);
|
||||
if (!data) return null;
|
||||
|
||||
logger.debug(`Cache hit: ${key}`);
|
||||
return JSON.parse(data);
|
||||
} catch (error) {
|
||||
logger.error(`Cache get error for key ${key}:`, error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Set cached data
|
||||
* @param {string} key - Cache key
|
||||
* @param {any} value - Data to cache
|
||||
* @param {number} ttl - Time to live in seconds (default: 300 = 5 minutes)
|
||||
* @returns {Promise<boolean>} - Success status
|
||||
*/
|
||||
const setCache = async (key, value, ttl = 300) => {
|
||||
try {
|
||||
if (!isRedisConnected()) {
|
||||
logger.warn('Redis not connected, skipping cache set');
|
||||
return false;
|
||||
}
|
||||
|
||||
const serialized = JSON.stringify(value);
|
||||
await redisClient.setex(key, ttl, serialized);
|
||||
|
||||
logger.debug(`Cache set: ${key} (TTL: ${ttl}s)`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
logger.error(`Cache set error for key ${key}:`, error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete cached data
|
||||
* @param {string} key - Cache key or pattern
|
||||
* @returns {Promise<boolean>} - Success status
|
||||
*/
|
||||
const deleteCache = async (key) => {
|
||||
try {
|
||||
if (!isRedisConnected()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Support pattern deletion (e.g., "user:*")
|
||||
if (key.includes('*')) {
|
||||
const keys = await redisClient.keys(key);
|
||||
if (keys.length > 0) {
|
||||
await redisClient.del(...keys);
|
||||
logger.debug(`Cache deleted: ${keys.length} keys matching ${key}`);
|
||||
}
|
||||
} else {
|
||||
await redisClient.del(key);
|
||||
logger.debug(`Cache deleted: ${key}`);
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
logger.error(`Cache delete error for key ${key}:`, error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear all cache
|
||||
* @returns {Promise<boolean>} - Success status
|
||||
*/
|
||||
const clearCache = async () => {
|
||||
try {
|
||||
if (!isRedisConnected()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
await redisClient.flushdb();
|
||||
logger.info('All cache cleared');
|
||||
return true;
|
||||
} catch (error) {
|
||||
logger.error('Cache clear error:', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get multiple keys at once
|
||||
* @param {string[]} keys - Array of cache keys
|
||||
* @returns {Promise<object>} - Object with key-value pairs
|
||||
*/
|
||||
const getCacheMultiple = async (keys) => {
|
||||
try {
|
||||
if (!isRedisConnected() || !keys || keys.length === 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const values = await redisClient.mget(...keys);
|
||||
const result = {};
|
||||
|
||||
keys.forEach((key, index) => {
|
||||
if (values[index]) {
|
||||
try {
|
||||
result[key] = JSON.parse(values[index]);
|
||||
} catch (err) {
|
||||
result[key] = null;
|
||||
}
|
||||
} else {
|
||||
result[key] = null;
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
logger.error('Cache mget error:', error);
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Increment a counter
|
||||
* @param {string} key - Cache key
|
||||
* @param {number} increment - Amount to increment (default: 1)
|
||||
* @param {number} ttl - Time to live in seconds (optional)
|
||||
* @returns {Promise<number>} - New value
|
||||
*/
|
||||
const incrementCache = async (key, increment = 1, ttl = null) => {
|
||||
try {
|
||||
if (!isRedisConnected()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const newValue = await redisClient.incrby(key, increment);
|
||||
|
||||
if (ttl) {
|
||||
await redisClient.expire(key, ttl);
|
||||
}
|
||||
|
||||
return newValue;
|
||||
} catch (error) {
|
||||
logger.error(`Cache increment error for key ${key}:`, error);
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if key exists
|
||||
* @param {string} key - Cache key
|
||||
* @returns {Promise<boolean>} - Exists status
|
||||
*/
|
||||
const cacheExists = async (key) => {
|
||||
try {
|
||||
if (!isRedisConnected()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const exists = await redisClient.exists(key);
|
||||
return exists === 1;
|
||||
} catch (error) {
|
||||
logger.error(`Cache exists error for key ${key}:`, error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
redisClient,
|
||||
isRedisConnected,
|
||||
getRedisClient,
|
||||
closeRedis,
|
||||
getCache,
|
||||
setCache,
|
||||
deleteCache,
|
||||
clearCache,
|
||||
getCacheMultiple,
|
||||
incrementCache,
|
||||
cacheExists
|
||||
};
|
||||
Reference in New Issue
Block a user