From 2ecfb8d3ff343ed20c178d4f168bb114588c6177 Mon Sep 17 00:00:00 2001 From: Ritwij Aryan Parmar <88580521+RitwijParmar@users.noreply.github.com> Date: Wed, 10 Jun 2026 14:56:03 -0400 Subject: [PATCH] fix(api): reuse redis client in health checks (#3671) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Gergő Móricz --- .../src/controllers/v0/admin/redis-health.ts | 5 ++-- apps/api/src/services/worker/nuq.ts | 7 +++++- apps/api/src/services/worker/redis.ts | 24 +++++++++++++++++-- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/apps/api/src/controllers/v0/admin/redis-health.ts b/apps/api/src/controllers/v0/admin/redis-health.ts index 53a281b82..038da4394 100644 --- a/apps/api/src/controllers/v0/admin/redis-health.ts +++ b/apps/api/src/controllers/v0/admin/redis-health.ts @@ -1,8 +1,7 @@ import { Request, Response } from "express"; -import { config } from "../../../config"; -import Redis from "ioredis"; import { logger } from "../../../lib/logger"; import { redisRateLimitClient } from "../../../services/rate-limiter"; +import { getRedisConnection } from "../../../services/queue-service"; export async function redisHealthController(req: Request, res: Response) { const retryOperation = async (operation, retries = 3) => { @@ -18,7 +17,7 @@ export async function redisHealthController(req: Request, res: Response) { }; try { - const queueRedis = new Redis(config.REDIS_URL!); + const queueRedis = getRedisConnection(); const testKey = "test"; const testValue = "test"; diff --git a/apps/api/src/services/worker/nuq.ts b/apps/api/src/services/worker/nuq.ts index cf0cfceb0..86465f06d 100644 --- a/apps/api/src/services/worker/nuq.ts +++ b/apps/api/src/services/worker/nuq.ts @@ -6,6 +6,7 @@ import { withSpan, setSpanAttributes } from "../../lib/otel-tracer"; import amqp from "amqplib"; import { v5 as uuidv5, validate as isUUID } from "uuid"; import { config } from "../../config"; +import { nuqRedis } from "./redis"; // === Basics @@ -1746,6 +1747,10 @@ export const crawlGroup = new NuQJobGroup("nuq.group_crawl"); // === Cleanup export async function nuqShutdown() { - await scrapeQueue.shutdown(); + await Promise.all([ + scrapeQueue.shutdown(), + crawlFinishedQueue.shutdown(), + nuqRedis.shutdown(), + ]); await nuqPool.end(); } diff --git a/apps/api/src/services/worker/redis.ts b/apps/api/src/services/worker/redis.ts index 4b9d300db..8bab92908 100644 --- a/apps/api/src/services/worker/redis.ts +++ b/apps/api/src/services/worker/redis.ts @@ -51,8 +51,8 @@ return 1`, } as const; type ScriptHashes = { - [K in keyof typeof luaScripts]: { - [K2 in keyof (typeof luaScripts)[K]]: string; + -readonly [K in keyof typeof luaScripts]: { + -readonly [K2 in keyof (typeof luaScripts)[K]]: string; }; }; @@ -123,6 +123,24 @@ export const ensureRedis = async () => { return initPromise; }; +export const shutdownRedis = async () => { + initPromise = null; + for (const key of Object.keys(scripts) as (keyof ScriptHashes)[]) { + scripts[key] = {} as ScriptHashes[typeof key]; + } + + if (redis.status === "wait" || redis.status === "end") { + return; + } + + try { + await redis.quit(); + } catch (err) { + logger.warn("Error while closing NuQ Redis connection", { err }); + redis.disconnect(); + } +}; + export const semaphoreKeys = (teamId: string) => { return { leases: `nuq:sema:{${teamId}}:leases`, @@ -147,10 +165,12 @@ type NuQRedis = Redis & { scripts: typeof scripts; runScript: typeof runScript; ensure: typeof ensureRedis; + shutdown: typeof shutdownRedis; }; export const nuqRedis: NuQRedis = Object.assign(redis, { scripts, runScript, ensure: ensureRedis, + shutdown: shutdownRedis, });