Fix: Add keep-alive options to Redis clients to prevent idle timeouts and socket closing. (#4377)

* redis keepalive mechanism for all redis

* removed offline queue commands

* Simplified changes for consistency. Added REDIS_KEEP_ALIVE env variable.

* update redis socket alive env variable

* lint fix

---------

Co-authored-by: Henry <hzj94@hotmail.com>
Co-authored-by: Henry Heng <henryheng@flowiseai.com>
This commit is contained in:
nikitas-novatix 2025-05-14 04:29:32 +02:00 committed by GitHub
parent 98e75ad7d6
commit eadf1b11b3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 118 additions and 15 deletions

View File

@ -101,4 +101,5 @@ BLOB_STORAGE_PATH=/root/.flowise/storage
# REDIS_CERT= # REDIS_CERT=
# REDIS_KEY= # REDIS_KEY=
# REDIS_CA= # REDIS_CA=
# ENABLE_BULLMQ_DASHBOARD= # REDIS_KEEP_ALIVE=
# ENABLE_BULLMQ_DASHBOARD=

View File

@ -49,6 +49,7 @@ services:
- REDIS_CERT=${REDIS_CERT} - REDIS_CERT=${REDIS_CERT}
- REDIS_KEY=${REDIS_KEY} - REDIS_KEY=${REDIS_KEY}
- REDIS_CA=${REDIS_CA} - REDIS_CA=${REDIS_CA}
- REDIS_KEEP_ALIVE=${REDIS_KEEP_ALIVE}
- ENABLE_BULLMQ_DASHBOARD=${ENABLE_BULLMQ_DASHBOARD} - ENABLE_BULLMQ_DASHBOARD=${ENABLE_BULLMQ_DASHBOARD}
ports: ports:
- '${PORT}:${PORT}' - '${PORT}:${PORT}'

View File

@ -49,6 +49,7 @@ services:
- REDIS_CERT=${REDIS_CERT} - REDIS_CERT=${REDIS_CERT}
- REDIS_KEY=${REDIS_KEY} - REDIS_KEY=${REDIS_KEY}
- REDIS_CA=${REDIS_CA} - REDIS_CA=${REDIS_CA}
- REDIS_KEEP_ALIVE=${REDIS_KEEP_ALIVE}
- ENABLE_BULLMQ_DASHBOARD=${ENABLE_BULLMQ_DASHBOARD} - ENABLE_BULLMQ_DASHBOARD=${ENABLE_BULLMQ_DASHBOARD}
ports: ports:
- '${PORT}:${PORT}' - '${PORT}:${PORT}'

View File

@ -126,10 +126,19 @@ const getRedisClient = async (nodeData: INodeData, options: ICommonObject) => {
host, host,
username, username,
password, password,
keepAlive:
process.env.REDIS_KEEP_ALIVE && !isNaN(parseInt(process.env.REDIS_KEEP_ALIVE, 10))
? parseInt(process.env.REDIS_KEEP_ALIVE, 10)
: undefined,
...tlsOptions ...tlsOptions
}) })
} else { } else {
client = new Redis(redisUrl) client = new Redis(redisUrl, {
keepAlive:
process.env.REDIS_KEEP_ALIVE && !isNaN(parseInt(process.env.REDIS_KEEP_ALIVE, 10))
? parseInt(process.env.REDIS_KEEP_ALIVE, 10)
: undefined
})
} }
return client return client

View File

@ -83,10 +83,19 @@ class RedisEmbeddingsCache implements INode {
host, host,
username, username,
password, password,
keepAlive:
process.env.REDIS_KEEP_ALIVE && !isNaN(parseInt(process.env.REDIS_KEEP_ALIVE, 10))
? parseInt(process.env.REDIS_KEEP_ALIVE, 10)
: undefined,
...tlsOptions ...tlsOptions
}) })
} else { } else {
client = new Redis(redisUrl) client = new Redis(redisUrl, {
keepAlive:
process.env.REDIS_KEEP_ALIVE && !isNaN(parseInt(process.env.REDIS_KEEP_ALIVE, 10))
? parseInt(process.env.REDIS_KEEP_ALIVE, 10)
: undefined
})
} }
ttl ??= '3600' ttl ??= '3600'

View File

@ -132,7 +132,21 @@ class BufferMemoryExtended extends FlowiseMemory implements MemoryMethods {
} }
private async withRedisClient<T>(fn: (client: Redis) => Promise<T>): Promise<T> { private async withRedisClient<T>(fn: (client: Redis) => Promise<T>): Promise<T> {
const client = typeof this.redisOptions === 'string' ? new Redis(this.redisOptions) : new Redis(this.redisOptions) const client =
typeof this.redisOptions === 'string'
? new Redis(this.redisOptions, {
keepAlive:
process.env.REDIS_KEEP_ALIVE && !isNaN(parseInt(process.env.REDIS_KEEP_ALIVE, 10))
? parseInt(process.env.REDIS_KEEP_ALIVE, 10)
: undefined
})
: new Redis({
...this.redisOptions,
keepAlive:
process.env.REDIS_KEEP_ALIVE && !isNaN(parseInt(process.env.REDIS_KEEP_ALIVE, 10))
? parseInt(process.env.REDIS_KEEP_ALIVE, 10)
: undefined
})
try { try {
return await fn(client) return await fn(client)
} finally { } finally {

View File

@ -147,7 +147,15 @@ class Redis_VectorStores implements INode {
} }
try { try {
const redisClient = createClient({ url: redisUrl }) const redisClient = createClient({
url: redisUrl,
socket: {
keepAlive:
process.env.REDIS_KEEP_ALIVE && !isNaN(parseInt(process.env.REDIS_KEEP_ALIVE, 10))
? parseInt(process.env.REDIS_KEEP_ALIVE, 10)
: undefined // milliseconds
}
})
await redisClient.connect() await redisClient.connect()
const storeConfig: RedisVectorStoreConfig = { const storeConfig: RedisVectorStoreConfig = {
@ -212,7 +220,15 @@ class Redis_VectorStores implements INode {
redisUrl = 'redis://' + username + ':' + password + '@' + host + ':' + portStr redisUrl = 'redis://' + username + ':' + password + '@' + host + ':' + portStr
} }
const redisClient = createClient({ url: redisUrl }) const redisClient = createClient({
url: redisUrl,
socket: {
keepAlive:
process.env.REDIS_KEEP_ALIVE && !isNaN(parseInt(process.env.REDIS_KEEP_ALIVE, 10))
? parseInt(process.env.REDIS_KEEP_ALIVE, 10)
: undefined // milliseconds
}
})
const storeConfig: RedisVectorStoreConfig = { const storeConfig: RedisVectorStoreConfig = {
redisClient: redisClient, redisClient: redisClient,

View File

@ -99,4 +99,5 @@ PORT=3000
# REDIS_CERT= # REDIS_CERT=
# REDIS_KEY= # REDIS_KEY=
# REDIS_CA= # REDIS_CA=
# ENABLE_BULLMQ_DASHBOARD= # REDIS_KEEP_ALIVE=
# ENABLE_BULLMQ_DASHBOARD=

View File

@ -12,7 +12,12 @@ export class CachePool {
constructor() { constructor() {
if (process.env.MODE === MODE.QUEUE) { if (process.env.MODE === MODE.QUEUE) {
if (process.env.REDIS_URL) { if (process.env.REDIS_URL) {
this.redisClient = new Redis(process.env.REDIS_URL) this.redisClient = new Redis(process.env.REDIS_URL, {
keepAlive:
process.env.REDIS_KEEP_ALIVE && !isNaN(parseInt(process.env.REDIS_KEEP_ALIVE, 10))
? parseInt(process.env.REDIS_KEEP_ALIVE, 10)
: undefined
})
} else { } else {
this.redisClient = new Redis({ this.redisClient = new Redis({
host: process.env.REDIS_HOST || 'localhost', host: process.env.REDIS_HOST || 'localhost',
@ -26,6 +31,10 @@ export class CachePool {
key: process.env.REDIS_KEY ? Buffer.from(process.env.REDIS_KEY, 'base64') : undefined, key: process.env.REDIS_KEY ? Buffer.from(process.env.REDIS_KEY, 'base64') : undefined,
ca: process.env.REDIS_CA ? Buffer.from(process.env.REDIS_CA, 'base64') : undefined ca: process.env.REDIS_CA ? Buffer.from(process.env.REDIS_CA, 'base64') : undefined
} }
: undefined,
keepAlive:
process.env.REDIS_KEEP_ALIVE && !isNaN(parseInt(process.env.REDIS_KEEP_ALIVE, 10))
? parseInt(process.env.REDIS_KEEP_ALIVE, 10)
: undefined : undefined
}) })
} }

View File

@ -76,6 +76,7 @@ export abstract class BaseCommand extends Command {
REDIS_CERT: Flags.string(), REDIS_CERT: Flags.string(),
REDIS_KEY: Flags.string(), REDIS_KEY: Flags.string(),
REDIS_CA: Flags.string(), REDIS_CA: Flags.string(),
REDIS_KEEP_ALIVE: Flags.string(),
ENABLE_BULLMQ_DASHBOARD: Flags.string() ENABLE_BULLMQ_DASHBOARD: Flags.string()
} }
@ -211,6 +212,7 @@ export abstract class BaseCommand extends Command {
if (flags.QUEUE_REDIS_EVENT_STREAM_MAX_LEN) process.env.QUEUE_REDIS_EVENT_STREAM_MAX_LEN = flags.QUEUE_REDIS_EVENT_STREAM_MAX_LEN if (flags.QUEUE_REDIS_EVENT_STREAM_MAX_LEN) process.env.QUEUE_REDIS_EVENT_STREAM_MAX_LEN = flags.QUEUE_REDIS_EVENT_STREAM_MAX_LEN
if (flags.REMOVE_ON_AGE) process.env.REMOVE_ON_AGE = flags.REMOVE_ON_AGE if (flags.REMOVE_ON_AGE) process.env.REMOVE_ON_AGE = flags.REMOVE_ON_AGE
if (flags.REMOVE_ON_COUNT) process.env.REMOVE_ON_COUNT = flags.REMOVE_ON_COUNT if (flags.REMOVE_ON_COUNT) process.env.REMOVE_ON_COUNT = flags.REMOVE_ON_COUNT
if (flags.REDIS_KEEP_ALIVE) process.env.REDIS_KEEP_ALIVE = flags.REDIS_KEEP_ALIVE
if (flags.ENABLE_BULLMQ_DASHBOARD) process.env.ENABLE_BULLMQ_DASHBOARD = flags.ENABLE_BULLMQ_DASHBOARD if (flags.ENABLE_BULLMQ_DASHBOARD) process.env.ENABLE_BULLMQ_DASHBOARD = flags.ENABLE_BULLMQ_DASHBOARD
} }
} }

View File

@ -41,7 +41,12 @@ export class QueueManager {
port: parseInt(process.env.REDIS_PORT || '6379'), port: parseInt(process.env.REDIS_PORT || '6379'),
username: process.env.REDIS_USERNAME || undefined, username: process.env.REDIS_USERNAME || undefined,
password: process.env.REDIS_PASSWORD || undefined, password: process.env.REDIS_PASSWORD || undefined,
tls: tlsOpts tls: tlsOpts,
enableReadyCheck: true,
keepAlive:
process.env.REDIS_KEEP_ALIVE && !isNaN(parseInt(process.env.REDIS_KEEP_ALIVE, 10))
? parseInt(process.env.REDIS_KEEP_ALIVE, 10)
: undefined
} }
} }

View File

@ -7,7 +7,13 @@ export class RedisEventPublisher implements IServerSideEventStreamer {
constructor() { constructor() {
if (process.env.REDIS_URL) { if (process.env.REDIS_URL) {
this.redisPublisher = createClient({ this.redisPublisher = createClient({
url: process.env.REDIS_URL url: process.env.REDIS_URL,
socket: {
keepAlive:
process.env.REDIS_KEEP_ALIVE && !isNaN(parseInt(process.env.REDIS_KEEP_ALIVE, 10))
? parseInt(process.env.REDIS_KEEP_ALIVE, 10)
: undefined
}
}) })
} else { } else {
this.redisPublisher = createClient({ this.redisPublisher = createClient({
@ -19,7 +25,11 @@ export class RedisEventPublisher implements IServerSideEventStreamer {
tls: process.env.REDIS_TLS === 'true', tls: process.env.REDIS_TLS === 'true',
cert: process.env.REDIS_CERT ? Buffer.from(process.env.REDIS_CERT, 'base64') : undefined, cert: process.env.REDIS_CERT ? Buffer.from(process.env.REDIS_CERT, 'base64') : undefined,
key: process.env.REDIS_KEY ? Buffer.from(process.env.REDIS_KEY, 'base64') : undefined, key: process.env.REDIS_KEY ? Buffer.from(process.env.REDIS_KEY, 'base64') : undefined,
ca: process.env.REDIS_CA ? Buffer.from(process.env.REDIS_CA, 'base64') : undefined ca: process.env.REDIS_CA ? Buffer.from(process.env.REDIS_CA, 'base64') : undefined,
keepAlive:
process.env.REDIS_KEEP_ALIVE && !isNaN(parseInt(process.env.REDIS_KEEP_ALIVE, 10))
? parseInt(process.env.REDIS_KEEP_ALIVE, 10)
: undefined
} }
}) })
} }

View File

@ -9,7 +9,13 @@ export class RedisEventSubscriber {
constructor(sseStreamer: SSEStreamer) { constructor(sseStreamer: SSEStreamer) {
if (process.env.REDIS_URL) { if (process.env.REDIS_URL) {
this.redisSubscriber = createClient({ this.redisSubscriber = createClient({
url: process.env.REDIS_URL url: process.env.REDIS_URL,
socket: {
keepAlive:
process.env.REDIS_KEEP_ALIVE && !isNaN(parseInt(process.env.REDIS_KEEP_ALIVE, 10))
? parseInt(process.env.REDIS_KEEP_ALIVE, 10)
: undefined
}
}) })
} else { } else {
this.redisSubscriber = createClient({ this.redisSubscriber = createClient({
@ -21,7 +27,11 @@ export class RedisEventSubscriber {
tls: process.env.REDIS_TLS === 'true', tls: process.env.REDIS_TLS === 'true',
cert: process.env.REDIS_CERT ? Buffer.from(process.env.REDIS_CERT, 'base64') : undefined, cert: process.env.REDIS_CERT ? Buffer.from(process.env.REDIS_CERT, 'base64') : undefined,
key: process.env.REDIS_KEY ? Buffer.from(process.env.REDIS_KEY, 'base64') : undefined, key: process.env.REDIS_KEY ? Buffer.from(process.env.REDIS_KEY, 'base64') : undefined,
ca: process.env.REDIS_CA ? Buffer.from(process.env.REDIS_CA, 'base64') : undefined ca: process.env.REDIS_CA ? Buffer.from(process.env.REDIS_CA, 'base64') : undefined,
keepAlive:
process.env.REDIS_KEEP_ALIVE && !isNaN(parseInt(process.env.REDIS_KEEP_ALIVE, 10))
? parseInt(process.env.REDIS_KEEP_ALIVE, 10)
: undefined
} }
}) })
} }

View File

@ -24,7 +24,12 @@ export class RateLimiterManager {
constructor() { constructor() {
if (process.env.MODE === MODE.QUEUE) { if (process.env.MODE === MODE.QUEUE) {
if (process.env.REDIS_URL) { if (process.env.REDIS_URL) {
this.redisClient = new Redis(process.env.REDIS_URL) this.redisClient = new Redis(process.env.REDIS_URL, {
keepAlive:
process.env.REDIS_KEEP_ALIVE && !isNaN(parseInt(process.env.REDIS_KEEP_ALIVE, 10))
? parseInt(process.env.REDIS_KEEP_ALIVE, 10)
: undefined
})
} else { } else {
this.redisClient = new Redis({ this.redisClient = new Redis({
host: process.env.REDIS_HOST || 'localhost', host: process.env.REDIS_HOST || 'localhost',
@ -38,6 +43,10 @@ export class RateLimiterManager {
key: process.env.REDIS_KEY ? Buffer.from(process.env.REDIS_KEY, 'base64') : undefined, key: process.env.REDIS_KEY ? Buffer.from(process.env.REDIS_KEY, 'base64') : undefined,
ca: process.env.REDIS_CA ? Buffer.from(process.env.REDIS_CA, 'base64') : undefined ca: process.env.REDIS_CA ? Buffer.from(process.env.REDIS_CA, 'base64') : undefined
} }
: undefined,
keepAlive:
process.env.REDIS_KEEP_ALIVE && !isNaN(parseInt(process.env.REDIS_KEEP_ALIVE, 10))
? parseInt(process.env.REDIS_KEEP_ALIVE, 10)
: undefined : undefined
}) })
} }
@ -65,7 +74,13 @@ export class RateLimiterManager {
port: parseInt(process.env.REDIS_PORT || '6379'), port: parseInt(process.env.REDIS_PORT || '6379'),
username: process.env.REDIS_USERNAME || undefined, username: process.env.REDIS_USERNAME || undefined,
password: process.env.REDIS_PASSWORD || undefined, password: process.env.REDIS_PASSWORD || undefined,
tls: tlsOpts tls: tlsOpts,
maxRetriesPerRequest: null,
enableReadyCheck: true,
keepAlive:
process.env.REDIS_KEEP_ALIVE && !isNaN(parseInt(process.env.REDIS_KEEP_ALIVE, 10))
? parseInt(process.env.REDIS_KEEP_ALIVE, 10)
: undefined
} }
} }