Merged in feature/redis-queue-deletes (pull request #2397)
Feature/redis queue deletes * feat: add queuing for deletes * merge * . * . * . Approved-by: Linus Flood
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
import ioredis from "ioredis";
|
||||
|
||||
import { env, redisConfig } from "@/env";
|
||||
|
||||
export const redis = new ioredis({
|
||||
host: redisConfig.host,
|
||||
port: redisConfig.port,
|
||||
username: redisConfig.username,
|
||||
password: redisConfig.password,
|
||||
maxRetriesPerRequest: 1, // Avoid excessive retries,
|
||||
tls: !env.IS_DEV
|
||||
? {
|
||||
rejectUnauthorized: true,
|
||||
}
|
||||
: undefined,
|
||||
lazyConnect: true,
|
||||
commandTimeout: 1_000,
|
||||
socketTimeout: 5_000,
|
||||
});
|
||||
|
||||
export const bullmqredis = new ioredis({
|
||||
host: redisConfig.host,
|
||||
port: redisConfig.port,
|
||||
username: redisConfig.username,
|
||||
password: redisConfig.password,
|
||||
maxRetriesPerRequest: null, // Avoid excessive retries,
|
||||
tls: !env.IS_DEV
|
||||
? {
|
||||
rejectUnauthorized: true,
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
@@ -0,0 +1,84 @@
|
||||
import { redis, bullmqredis } from ".";
|
||||
import z from "zod";
|
||||
import { loggerModule } from "@/utils/logger";
|
||||
|
||||
import { Worker, Queue } from "bullmq";
|
||||
import { timeout } from "@/utils/timeout";
|
||||
import { env } from "@/env";
|
||||
import { sentry } from "@/server/sentry.server.config";
|
||||
|
||||
const DELETE_JOB = "deleteQueueJob";
|
||||
const deleteQueueLogger = loggerModule("deleteQueue");
|
||||
|
||||
const deleteQueueSchema = z.object({
|
||||
pattern: z.string().min(1, "Pattern must be at least 1 character long"),
|
||||
});
|
||||
|
||||
const deleteQueue = new Queue(DELETE_JOB, { connection: bullmqredis });
|
||||
const worker = new Worker(
|
||||
DELETE_JOB,
|
||||
async (job) => {
|
||||
if (job.name === "delete") {
|
||||
const { pattern } = deleteQueueSchema.parse(job.data);
|
||||
deleteQueueLogger.info(
|
||||
`Job: ${job.id} processing. With pattern: ${pattern}`,
|
||||
{ pattern, jobId: job.id },
|
||||
);
|
||||
|
||||
const now = performance.now();
|
||||
const deletedCount = await deleteWithPattern(pattern);
|
||||
const elapsed = performance.now() - now;
|
||||
|
||||
deleteQueueLogger.info(
|
||||
`Job: ${job.id} completed. Deleted ${deletedCount} keys for pattern '${pattern}' in ${elapsed.toFixed(2)}ms.`,
|
||||
{ deletedCount, pattern, elapsed, jobId: job.id },
|
||||
);
|
||||
}
|
||||
},
|
||||
{ connection: bullmqredis },
|
||||
);
|
||||
|
||||
worker.on("failed", (job, error) => {
|
||||
deleteQueueLogger.error(`Job failed: ${job?.id} with ${error.message}`, {
|
||||
error,
|
||||
jobId: job?.id,
|
||||
pattern: job?.data?.pattern,
|
||||
});
|
||||
|
||||
sentry.captureException(error);
|
||||
});
|
||||
|
||||
export async function queueDelete({ pattern }: { pattern: string }) {
|
||||
deleteQueue.add("delete", { pattern });
|
||||
}
|
||||
|
||||
async function deleteWithPattern(pattern: string) {
|
||||
let cursor = "0";
|
||||
const SCAN_SIZE = env.DELETE_BATCH_SIZE;
|
||||
|
||||
let totalDeleteCount = 0;
|
||||
|
||||
do {
|
||||
const [newCursor, foundKeys] = await redis.scan(
|
||||
cursor,
|
||||
"MATCH",
|
||||
pattern,
|
||||
"COUNT",
|
||||
SCAN_SIZE,
|
||||
);
|
||||
|
||||
cursor = newCursor;
|
||||
if (foundKeys.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const deleteCount = await redis.unlink(foundKeys);
|
||||
|
||||
totalDeleteCount += deleteCount;
|
||||
|
||||
// Rate limiting to avoid overwhelming the Redis server
|
||||
await timeout(100);
|
||||
} while (cursor !== "0");
|
||||
|
||||
return totalDeleteCount;
|
||||
}
|
||||
Reference in New Issue
Block a user