Files
sena_db_api_layer/config/redis.js
2026-01-19 09:33:35 +07:00

170 lines
3.8 KiB
JavaScript

const Redis = require('ioredis');
const config = require('./config.json');
/**
* Redis Connection - Direct connection to master
* For Sentinel HA from external clients, we connect directly to the master port
* and rely on manual failover by trying both ports
*/
const redisConfig = config.redis;
// Direct connection to master (port 11010)
const redisClient = new Redis({
host: redisConfig.cluster[0].host,
port: redisConfig.cluster[0].port,
password: redisConfig.password,
db: redisConfig.db,
keyPrefix: redisConfig.keyPrefix,
connectTimeout: 10000,
retryStrategy: (times) => {
if (times > 10) {
console.log(`⚠️ Redis retry exhausted after ${times} attempts`);
return null;
}
const delay = Math.min(times * 100, 3000);
console.log(`🔄 Redis retry attempt ${times}, delay ${delay}ms`);
return delay;
},
// Reconnect on READONLY error (slave promoted)
reconnectOnError: (err) => {
if (err.message.includes('READONLY')) {
console.log('⚠️ READONLY error detected - slave may have been promoted');
return true;
}
return false;
},
enableOfflineQueue: true,
maxRetriesPerRequest: null,
enableReadyCheck: true,
});
/**
* Redis Event Handlers
*/
redisClient.on('connect', () => {
console.log('✅ Redis client connected');
});
redisClient.on('ready', () => {
console.log('✅ Redis client ready');
});
redisClient.on('error', (err) => {
console.error('❌ Redis error:', err.message);
});
redisClient.on('close', () => {
console.log('⚠️ Redis client closed');
});
redisClient.on('reconnecting', () => {
console.log('🔄 Redis client reconnecting...');
});
/**
* Cache utility functions
*/
const cacheUtils = {
/**
* Get value from cache
*/
get: async (key) => {
try {
const value = await redisClient.get(key);
return value ? JSON.parse(value) : null;
} catch (error) {
console.error(`Error getting cache for key ${key}:`, error.message);
return null;
}
},
/**
* Set value to cache with TTL
*/
set: async (key, value, ttl = 3600) => {
try {
const serialized = JSON.stringify(value);
await redisClient.setex(key, ttl, serialized);
return true;
} catch (error) {
console.error(`Error setting cache for key ${key}:`, error.message);
return false;
}
},
/**
* Delete cache by key
*/
delete: async (key) => {
try {
await redisClient.del(key);
return true;
} catch (error) {
console.error(`Error deleting cache for key ${key}:`, error.message);
return false;
}
},
/**
* Delete cache by pattern
*/
deletePattern: async (pattern) => {
try {
const keys = await redisClient.keys(pattern);
if (keys.length > 0) {
await redisClient.del(...keys);
}
return keys.length;
} catch (error) {
console.error(`Error deleting cache pattern ${pattern}:`, error.message);
return 0;
}
},
/**
* Check if key exists
*/
exists: async (key) => {
try {
const result = await redisClient.exists(key);
return result === 1;
} catch (error) {
console.error(`Error checking cache existence for key ${key}:`, error.message);
return false;
}
},
/**
* Get TTL for key
*/
ttl: async (key) => {
try {
return await redisClient.ttl(key);
} catch (error) {
console.error(`Error getting TTL for key ${key}:`, error.message);
return -1;
}
},
};
/**
* Close Redis connection
*/
const closeRedisConnection = async () => {
try {
await redisClient.quit();
console.log('✅ Redis connection closed gracefully');
} catch (error) {
console.error('❌ Error closing Redis connection:', error.message);
}
};
module.exports = {
redisClient,
cacheUtils,
closeRedisConnection,
};