From 0506d5847e2e0f3fb96ad375d04c8f8ca5b1dff2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20J=C3=A4derberg?= Date: Tue, 6 May 2025 11:38:41 +0000 Subject: [PATCH] Merged in fix/redis-shutdown-graceful (pull request #1969) Fix/redis shutdown graceful * fix: shutdown redis gracefully when container restarts * throttle scans to redis to avoid overwhelming it Approved-by: Anton Gunnarsson --- apps/redis-api/src/index.ts | 4 ++++ apps/redis-api/src/routes/api/cache.ts | 10 +++++++++- apps/redis-api/src/shutdown.ts | 16 ++++++++++++++++ apps/redis-api/src/utils/timeout.ts | 3 +++ 4 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 apps/redis-api/src/shutdown.ts create mode 100644 apps/redis-api/src/utils/timeout.ts diff --git a/apps/redis-api/src/index.ts b/apps/redis-api/src/index.ts index ce85ca2e4..6583ecf4e 100644 --- a/apps/redis-api/src/index.ts +++ b/apps/redis-api/src/index.ts @@ -11,6 +11,10 @@ import serverTiming from "@elysiajs/server-timing"; import { AuthenticationError } from "@/errors/AuthenticationError"; import { ModelValidationError } from "@/errors/ModelValidationError"; +import { setupShutdown } from "@/shutdown"; + +setupShutdown(); + const app = new Elysia() .use(serverTiming()) .error("AUTHENTICATION_ERROR", AuthenticationError) diff --git a/apps/redis-api/src/routes/api/cache.ts b/apps/redis-api/src/routes/api/cache.ts index a74a46940..f32a445de 100644 --- a/apps/redis-api/src/routes/api/cache.ts +++ b/apps/redis-api/src/routes/api/cache.ts @@ -5,6 +5,7 @@ import { redis } from "@/services/redis"; import { ModelValidationError } from "@/errors/ModelValidationError"; import { loggerModule } from "@/utils/logger"; import { truncate } from "@/utils/truncate"; +import { timeout } from "@/utils/timeout"; const MIN_LENGTH = 1; @@ -92,10 +93,13 @@ function validateKey(key: string) { if (parsedKey.length < MIN_LENGTH) { throw new ModelValidationError( - "Key has to be atleast 1 character long" + "Key has to be at least 1 character long" ); } + if (parsedKey.includes("*")) { + throw new ModelValidationError("Key cannot contain wildcards"); + } if (parsedKey.includes("*")) { throw new ModelValidationError("Key cannot contain wildcards"); } @@ -115,6 +119,10 @@ async function deleteWithPattern(pattern: string) { "COUNT", 5000 ); + + // Throttle calls to Redis to avoid overwhelming it + await timeout(50); + cursor = newCursor; keys.push(...foundKeys); } while (cursor !== "0"); diff --git a/apps/redis-api/src/shutdown.ts b/apps/redis-api/src/shutdown.ts new file mode 100644 index 000000000..9f9bbcd3e --- /dev/null +++ b/apps/redis-api/src/shutdown.ts @@ -0,0 +1,16 @@ +import { loggerModule } from "@/utils/logger"; +import { redis } from "@/services/redis"; + +const shutdownLogger = loggerModule("shutdown"); + +export function setupShutdown() { + process.on("SIGINT", shutdown); + process.on("SIGTERM", shutdown); +} + +async function shutdown() { + shutdownLogger.info("Shutting down..."); + shutdownLogger.info("Closing Redis connection..."); + await redis.quit(); + process.exit(0); +} diff --git a/apps/redis-api/src/utils/timeout.ts b/apps/redis-api/src/utils/timeout.ts new file mode 100644 index 000000000..e6c99d779 --- /dev/null +++ b/apps/redis-api/src/utils/timeout.ts @@ -0,0 +1,3 @@ +export function timeout(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +}