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, };