From 45c0e9a1e6e6b5c3088d6998d7d210289b3b3188 Mon Sep 17 00:00:00 2001 From: phatle-qualgo Date: Tue, 14 Apr 2026 01:23:05 +0700 Subject: [PATCH 1/3] feat: add redis reconnect --- src/lib/server/redisConnector.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/lib/server/redisConnector.ts b/src/lib/server/redisConnector.ts index faad9ecd..56f7636c 100644 --- a/src/lib/server/redisConnector.ts +++ b/src/lib/server/redisConnector.ts @@ -6,12 +6,24 @@ dotenv.config(); let redisIOClient: IORedis | null = null; let redisClient: IORedis | null = null; +const redisClientOptions: Redis.RedisOptions = { + maxRetriesPerRequest: null, + // Reconnect if Redis starts rejecting writes after a failover. + reconnectOnError: (error) => { + const message = error?.message ?? ""; + if (message.includes("READONLY")) { + return 1; + } + return false; + }, +}; + export function redisIOConnection(): IORedis { if (!redisIOClient) { if (!process.env.REDIS_URL) { throw new Error("REDIS_URL is not defined in environment variables"); } - redisIOClient = new IORedis(process.env.REDIS_URL, { maxRetriesPerRequest: null }); + redisIOClient = new IORedis(process.env.REDIS_URL, redisClientOptions); } return redisIOClient; } @@ -21,7 +33,7 @@ export function redisConnection(): Redis { if (!process.env.REDIS_URL) { throw new Error("REDIS_URL is not defined in environment variables"); } - redisClient = new Redis(process.env.REDIS_URL, { maxRetriesPerRequest: null }); + redisClient = new Redis(process.env.REDIS_URL, redisClientOptions); } return redisClient; } From e53a5771745a36375ab7f050c8b3cc55b41b138e Mon Sep 17 00:00:00 2001 From: phatlet Date: Wed, 6 May 2026 18:16:58 +0700 Subject: [PATCH 2/3] Adding TCP keepalive and expanding reconnectOnError --- src/lib/server/redisConnector.ts | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/lib/server/redisConnector.ts b/src/lib/server/redisConnector.ts index 56f7636c..d29a06cf 100644 --- a/src/lib/server/redisConnector.ts +++ b/src/lib/server/redisConnector.ts @@ -6,13 +6,28 @@ dotenv.config(); let redisIOClient: IORedis | null = null; let redisClient: IORedis | null = null; +function shouldReconnectAfterRedisError(message: string): boolean { + const m = message.toUpperCase(); + // Failover: writes hit a replica until the client points at the new primary. + if (m.includes("READONLY")) return true; + // RDB/AOF reload after pod restart — commands fail until loading finishes. + if (m.includes("LOADING")) return true; + // Replication: primary unavailable during StatefulSet rollout. + if (m.includes("MASTERDOWN")) return true; + return false; +} + const redisClientOptions: Redis.RedisOptions = { maxRetriesPerRequest: null, - // Reconnect if Redis starts rejecting writes after a failover. - reconnectOnError: (error) => { + // Detect dead peers during long K8s / network stalls (default ioredis keepAlive is off). + keepAlive: 30000, + // Allow long RDB reloads after a StatefulSet restart before giving up on "ready". + maxLoadingRetryTime: 120_000, + reconnectOnError: (error: Error) => { const message = error?.message ?? ""; - if (message.includes("READONLY")) { - return 1; + if (shouldReconnectAfterRedisError(message)) { + // Reconnect and retry the failed command once the connection is healthy again. + return 2; } return false; }, From 3f4df5faa7d877123901b900909f04b0882081ed Mon Sep 17 00:00:00 2001 From: Raj Nandan Sharma Date: Mon, 15 Jun 2026 09:43:12 +0530 Subject: [PATCH 3/3] fix(redis): use named RedisOptions type so npm run check passes ioredis v5 dropped the namespace merge on the default export, so `Redis.RedisOptions` resolves to TS2702 (type used as a namespace). Import the `RedisOptions` type by name instead. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/lib/server/redisConnector.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/server/redisConnector.ts b/src/lib/server/redisConnector.ts index d29a06cf..7263012e 100644 --- a/src/lib/server/redisConnector.ts +++ b/src/lib/server/redisConnector.ts @@ -1,5 +1,6 @@ import IORedis from "ioredis"; import Redis from "ioredis"; +import type { RedisOptions } from "ioredis"; import dotenv from "dotenv"; dotenv.config(); @@ -17,7 +18,7 @@ function shouldReconnectAfterRedisError(message: string): boolean { return false; } -const redisClientOptions: Redis.RedisOptions = { +const redisClientOptions: RedisOptions = { maxRetriesPerRequest: null, // Detect dead peers during long K8s / network stalls (default ioredis keepAlive is off). keepAlive: 30000,