diff --git a/.env.example b/.env.example index e375c60a8..23777fe26 100644 --- a/.env.example +++ b/.env.example @@ -627,6 +627,11 @@ HELP_AND_FAQ_URL=https://librechat.ai # Redis connection limits # REDIS_MAX_LISTENERS=40 +# Redis ping interval in seconds (0 = disabled, >0 = enabled) +# When set to a positive integer, Redis clients will ping the server at this interval to keep connections alive +# When unset or 0, no pinging is performed (recommended for most use cases) +# REDIS_PING_INTERVAL=300 + # Force specific cache namespaces to use in-memory storage even when Redis is enabled # Comma-separated list of CacheKeys (e.g., STATIC_CONFIG,ROLES,MESSAGES) # FORCED_IN_MEMORY_CACHE_NAMESPACES=STATIC_CONFIG,ROLES diff --git a/api/cache/cacheConfig.js b/api/cache/cacheConfig.js index 551107f7a..87c403bae 100644 --- a/api/cache/cacheConfig.js +++ b/api/cache/cacheConfig.js @@ -43,6 +43,7 @@ const cacheConfig = { REDIS_CA: process.env.REDIS_CA ? fs.readFileSync(process.env.REDIS_CA, 'utf8') : null, REDIS_KEY_PREFIX: process.env[REDIS_KEY_PREFIX_VAR] || REDIS_KEY_PREFIX || '', REDIS_MAX_LISTENERS: math(process.env.REDIS_MAX_LISTENERS, 40), + REDIS_PING_INTERVAL: math(process.env.REDIS_PING_INTERVAL, 0), CI: isEnabled(process.env.CI), DEBUG_MEMORY_CACHE: isEnabled(process.env.DEBUG_MEMORY_CACHE), diff --git a/api/cache/cacheConfig.spec.js b/api/cache/cacheConfig.spec.js index 0809d6f62..7d4078a84 100644 --- a/api/cache/cacheConfig.spec.js +++ b/api/cache/cacheConfig.spec.js @@ -14,6 +14,7 @@ describe('cacheConfig', () => { delete process.env.REDIS_KEY_PREFIX_VAR; delete process.env.REDIS_KEY_PREFIX; delete process.env.USE_REDIS; + delete process.env.REDIS_PING_INTERVAL; delete process.env.FORCED_IN_MEMORY_CACHE_NAMESPACES; // Clear require cache @@ -107,6 +108,20 @@ describe('cacheConfig', () => { }); }); + describe('REDIS_PING_INTERVAL configuration', () => { + test('should default to 0 when REDIS_PING_INTERVAL is not set', () => { + const { cacheConfig } = require('./cacheConfig'); + expect(cacheConfig.REDIS_PING_INTERVAL).toBe(0); + }); + + test('should use provided REDIS_PING_INTERVAL value', () => { + process.env.REDIS_PING_INTERVAL = '300'; + + const { cacheConfig } = require('./cacheConfig'); + expect(cacheConfig.REDIS_PING_INTERVAL).toBe(300); + }); + }); + describe('FORCED_IN_MEMORY_CACHE_NAMESPACES validation', () => { test('should parse comma-separated cache keys correctly', () => { process.env.FORCED_IN_MEMORY_CACHE_NAMESPACES = ' ROLES, STATIC_CONFIG ,MESSAGES '; diff --git a/api/cache/redisClients.js b/api/cache/redisClients.js index 1a653ba13..5a633b196 100644 --- a/api/cache/redisClients.js +++ b/api/cache/redisClients.js @@ -25,10 +25,13 @@ if (cacheConfig.USE_REDIS) { ? new IoRedis(cacheConfig.REDIS_URI, redisOptions) : new IoRedis.Cluster(cacheConfig.REDIS_URI, { redisOptions }); - // Pinging the Redis server every 5 minutes to keep the connection alive - const pingInterval = setInterval(() => ioredisClient.ping(), 5 * 60 * 1000); - ioredisClient.on('close', () => clearInterval(pingInterval)); - ioredisClient.on('end', () => clearInterval(pingInterval)); + // Pinging the Redis server to keep the connection alive (if enabled) + let pingInterval = null; + if (cacheConfig.REDIS_PING_INTERVAL > 0) { + pingInterval = setInterval(() => ioredisClient.ping(), cacheConfig.REDIS_PING_INTERVAL * 1000); + ioredisClient.on('close', () => clearInterval(pingInterval)); + ioredisClient.on('end', () => clearInterval(pingInterval)); + } } /** @type {import('@keyv/redis').RedisClient | import('@keyv/redis').RedisCluster | null} */ @@ -48,10 +51,16 @@ if (cacheConfig.USE_REDIS) { keyvRedisClient.setMaxListeners(cacheConfig.REDIS_MAX_LISTENERS); - // Pinging the Redis server every 5 minutes to keep the connection alive - const keyvPingInterval = setInterval(() => keyvRedisClient.ping(), 5 * 60 * 1000); - keyvRedisClient.on('disconnect', () => clearInterval(keyvPingInterval)); - keyvRedisClient.on('end', () => clearInterval(keyvPingInterval)); + // Pinging the Redis server to keep the connection alive (if enabled) + let pingInterval = null; + if (cacheConfig.REDIS_PING_INTERVAL > 0) { + pingInterval = setInterval( + () => keyvRedisClient.ping(), + cacheConfig.REDIS_PING_INTERVAL * 1000, + ); + keyvRedisClient.on('disconnect', () => clearInterval(pingInterval)); + keyvRedisClient.on('end', () => clearInterval(pingInterval)); + } } module.exports = { ioredisClient, keyvRedisClient, GLOBAL_PREFIX_SEPARATOR };