From 07e7535dce38abcc080ccc48315649b47928dd52 Mon Sep 17 00:00:00 2001 From: MuhammedMagdyy Date: Tue, 2 Sep 2025 18:55:32 +0300 Subject: [PATCH 1/4] refactor: enhance connection logic with retry mechanism for Prisma and Redis clients --- src/database/PrismaClient.ts | 28 +++++++++++++++++++++------- src/database/RedisClient.ts | 28 +++++++++++++++++++++------- 2 files changed, 42 insertions(+), 14 deletions(-) diff --git a/src/database/PrismaClient.ts b/src/database/PrismaClient.ts index a64c307..fa9ec66 100644 --- a/src/database/PrismaClient.ts +++ b/src/database/PrismaClient.ts @@ -22,13 +22,27 @@ export class PrismaDatabaseClient implements IDatabaseClient { return this.client; } - async connect(): Promise { - try { - await this.getClient().$connect(); - logger.info(`Prisma connected successfully! ✅`); - } catch (error) { - logger.error(`Prisma connection failed - ${error} ❌`); - process.exit(1); + async connect(maxRetries = 10, baseDelay = 500): Promise { + let attempt = 0; + + while (attempt < maxRetries) { + try { + await this.getClient().$connect(); + logger.info(`Prisma connected successfully! ✅`); + return; + } catch (error) { + attempt++; + logger.error(`Prisma connection attempt ${attempt} failed: ${error}`); + + if (attempt >= maxRetries) { + logger.error(`Max retries reached. Exiting... ❌`); + process.exit(1); + } + + const delay = baseDelay * Math.pow(2, attempt - 1); + logger.info(`Retrying in ${delay} ms...`); + await new Promise((resolve) => setTimeout(resolve, delay)); + } } } diff --git a/src/database/RedisClient.ts b/src/database/RedisClient.ts index 7a5a5df..bc2bd36 100644 --- a/src/database/RedisClient.ts +++ b/src/database/RedisClient.ts @@ -23,13 +23,27 @@ export class RedisDatabaseClient implements IDatabaseClient { return this.client; } - async connect(): Promise { - try { - await this.getClient().connect(); - logger.info(`Redis connected successfully! ✅`); - } catch (error) { - logger.error(`Redis connection failed - ${error} ❌`); - process.exit(1); + async connect(maxRetries = 10, baseDelay = 500): Promise { + let attempt = 0; + + while (attempt < maxRetries) { + try { + await this.getClient().connect(); + logger.info(`Redis connected successfully! ✅`); + return; + } catch (error) { + attempt++; + logger.error(`Redis connection attempt ${attempt} failed: ${error}`); + + if (attempt >= maxRetries) { + logger.error(`Max retries reached. Exiting... ❌`); + process.exit(1); + } + + const delay = baseDelay * Math.pow(2, attempt - 1); + logger.info(`Retrying in ${delay} ms...`); + await new Promise((resolve) => setTimeout(resolve, delay)); + } } } From 651106695340ba2c755b11856d82c186c8b9bdda Mon Sep 17 00:00:00 2001 From: MuhammedMagdyy Date: Tue, 2 Sep 2025 19:30:19 +0300 Subject: [PATCH 2/4] feat: add retry and backoff utility function for future maintenance --- src/utils/functions.ts | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 src/utils/functions.ts diff --git a/src/utils/functions.ts b/src/utils/functions.ts new file mode 100644 index 0000000..24491bb --- /dev/null +++ b/src/utils/functions.ts @@ -0,0 +1,36 @@ +import { logger } from './logger'; + +export async function retryAndBackoff( + fn: () => Promise, + serviceName: string, + maxRetries = 10, + baseDelay = 500, +): Promise { + let attempt = 0; + + while (attempt < maxRetries) { + try { + const result = await fn(); + logger.info(`${serviceName} connected successfully! ✅`); + return result; + } catch (error) { + attempt++; + logger.error( + `${serviceName} connection attempt ${attempt} failed: ${error}`, + ); + + if (attempt >= maxRetries) { + logger.error(`Max retries reached for ${serviceName}. Exiting... ❌`); + process.exit(1); + } + + const delay = baseDelay * Math.pow(2, attempt); + logger.info(`Retrying ${serviceName} in ${delay} ms...`); + await new Promise((resolve) => setTimeout(resolve, delay)); + } + } + + throw new Error( + `${serviceName} connection failed after ${maxRetries} retries`, + ); +} From db9fd2ad29406b5f89ffdc081a09c0027bfcd59a Mon Sep 17 00:00:00 2001 From: MuhammedMagdyy Date: Tue, 2 Sep 2025 19:30:25 +0300 Subject: [PATCH 3/4] refactor: simplify connection logic for Prisma and Redis clients using retry and backoff utility --- src/database/PrismaClient.ts | 25 +++---------------------- src/database/RedisClient.ts | 25 +++---------------------- 2 files changed, 6 insertions(+), 44 deletions(-) diff --git a/src/database/PrismaClient.ts b/src/database/PrismaClient.ts index fa9ec66..fd94632 100644 --- a/src/database/PrismaClient.ts +++ b/src/database/PrismaClient.ts @@ -1,6 +1,7 @@ import { PrismaClient } from '@prisma/client'; import { IDatabaseClient } from '../interfaces'; import { logger } from '../utils'; +import { retryAndBackoff } from '../utils/functions'; export class PrismaDatabaseClient implements IDatabaseClient { private static instance: PrismaDatabaseClient; @@ -22,28 +23,8 @@ export class PrismaDatabaseClient implements IDatabaseClient { return this.client; } - async connect(maxRetries = 10, baseDelay = 500): Promise { - let attempt = 0; - - while (attempt < maxRetries) { - try { - await this.getClient().$connect(); - logger.info(`Prisma connected successfully! ✅`); - return; - } catch (error) { - attempt++; - logger.error(`Prisma connection attempt ${attempt} failed: ${error}`); - - if (attempt >= maxRetries) { - logger.error(`Max retries reached. Exiting... ❌`); - process.exit(1); - } - - const delay = baseDelay * Math.pow(2, attempt - 1); - logger.info(`Retrying in ${delay} ms...`); - await new Promise((resolve) => setTimeout(resolve, delay)); - } - } + async connect(): Promise { + await retryAndBackoff(() => this.getClient().$connect(), 'Prisma'); } async disconnect(): Promise { diff --git a/src/database/RedisClient.ts b/src/database/RedisClient.ts index bc2bd36..f5174d6 100644 --- a/src/database/RedisClient.ts +++ b/src/database/RedisClient.ts @@ -2,6 +2,7 @@ import { createClient, RedisClientType } from 'redis'; import { redisUrl } from '../config'; import { IDatabaseClient } from '../interfaces'; import { logger } from '../utils'; +import { retryAndBackoff } from '../utils/functions'; export class RedisDatabaseClient implements IDatabaseClient { private static instance: RedisDatabaseClient; @@ -23,28 +24,8 @@ export class RedisDatabaseClient implements IDatabaseClient { return this.client; } - async connect(maxRetries = 10, baseDelay = 500): Promise { - let attempt = 0; - - while (attempt < maxRetries) { - try { - await this.getClient().connect(); - logger.info(`Redis connected successfully! ✅`); - return; - } catch (error) { - attempt++; - logger.error(`Redis connection attempt ${attempt} failed: ${error}`); - - if (attempt >= maxRetries) { - logger.error(`Max retries reached. Exiting... ❌`); - process.exit(1); - } - - const delay = baseDelay * Math.pow(2, attempt - 1); - logger.info(`Retrying in ${delay} ms...`); - await new Promise((resolve) => setTimeout(resolve, delay)); - } - } + async connect(): Promise { + await retryAndBackoff(() => this.getClient().connect(), 'Redis'); } async disconnect(): Promise { From b9e915f850bc0de0cab59d273cd4d380221758dc Mon Sep 17 00:00:00 2001 From: MuhammedMagdyy Date: Tue, 2 Sep 2025 19:40:18 +0300 Subject: [PATCH 4/4] fix: correct exponential backoff calculation --- src/utils/functions.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/utils/functions.ts b/src/utils/functions.ts index 24491bb..fb7962a 100644 --- a/src/utils/functions.ts +++ b/src/utils/functions.ts @@ -24,12 +24,13 @@ export async function retryAndBackoff( process.exit(1); } - const delay = baseDelay * Math.pow(2, attempt); + const delay = baseDelay * Math.pow(2, attempt - 1); logger.info(`Retrying ${serviceName} in ${delay} ms...`); await new Promise((resolve) => setTimeout(resolve, delay)); } } + // Will not reach here, just for type safety | TODO: Maybe remove this in the future throw new Error( `${serviceName} connection failed after ${maxRetries} retries`, );