From 46363fead183f8e4a78cbc39419dea9452a45a06 Mon Sep 17 00:00:00 2001 From: Izan Gil <66965250+SrIzan10@users.noreply.github.com> Date: Tue, 24 Dec 2024 17:42:00 +0100 Subject: [PATCH] chore: performance improvements moving ratelimit to redis --- src/app/api/feedback/[projectId]/route.ts | 27 ++++++++++++++--------- src/instrumentation.ts | 23 +++++++++++++++++++ src/lib/forms/actions.ts | 5 +++++ 3 files changed, 45 insertions(+), 10 deletions(-) create mode 100644 src/instrumentation.ts diff --git a/src/app/api/feedback/[projectId]/route.ts b/src/app/api/feedback/[projectId]/route.ts index 4b6fabc..8491ae3 100644 --- a/src/app/api/feedback/[projectId]/route.ts +++ b/src/app/api/feedback/[projectId]/route.ts @@ -1,4 +1,5 @@ import prisma from '@/lib/db'; +import redis from '@/lib/db/redis'; import ratelimit from '@/lib/ratelimit'; export const dynamic = 'force-dynamic'; @@ -6,22 +7,19 @@ export const dynamic = 'force-dynamic'; export async function POST(request: Request, { params }: { params: Promise<{ projectId: string }> }) { const { projectId } = await params; const body = await request.json(); - const queryProject = await prisma.project.findFirst({ - where: { - id: projectId, - }, - }); - if (!queryProject) { + + const ratelimitInfo = await redis.get(`ratelimit:${projectId}`); + if (!ratelimitInfo) { return Response.json({ success: false, error: 'Project not found' }, { status: 404 }); } - // TODO: optimize to query redis instead of prisma first. + const [rateLimitReq, rateLimitTime] = ratelimitInfo.split(':').map(Number); const ip = request.headers.get('x-forwarded-for') || request.headers.get('cf-connecting-ip'); const queryRL = await ratelimit( ip!, - queryProject.id, - queryProject.rateLimitReq, - queryProject.rateLimitTime + projectId, + rateLimitReq, + rateLimitTime ); if (queryRL.exceeded) { return Response.json( @@ -33,6 +31,15 @@ export async function POST(request: Request, { params }: { params: Promise<{ pro ); } + const queryProject = await prisma.project.findFirst({ + where: { + id: projectId, + }, + }); + if (!queryProject) { + return Response.json({ success: false, error: 'Project not found' }, { status: 404 }); + } + // Convert customKeys to regular array and add message const customKeys = [...queryProject.customData, 'message']; const bodyKeys = Object.keys(body); diff --git a/src/instrumentation.ts b/src/instrumentation.ts new file mode 100644 index 0000000..aadb7f9 --- /dev/null +++ b/src/instrumentation.ts @@ -0,0 +1,23 @@ +import prisma from './lib/db'; + +export async function register() { + try { + if (process.env.NEXT_RUNTIME === 'nodejs') { + const { default: Redis } = await import('ioredis'); + const redis = new Redis(process.env.REDIS_URL!); + + console.log('Uploading all ratelimits to redis...'); + const ratelimits = (await prisma.project.findMany()).map((pr) => { + return { req: pr.rateLimitReq, time: pr.rateLimitTime, project: pr.id }; + }); + + const multi = redis.multi(); + multi.del('ratelimit:*'); + ratelimits.forEach((rl) => { + multi.set(`ratelimit:${rl.project}`, `${rl.req}:${rl.time}`); + }); + await multi.exec(); + console.log('All ratelimits uploaded to redis!'); + } + } catch {} +} diff --git a/src/lib/forms/actions.ts b/src/lib/forms/actions.ts index c72816f..201ee5b 100644 --- a/src/lib/forms/actions.ts +++ b/src/lib/forms/actions.ts @@ -13,6 +13,7 @@ import prisma from '../db'; import zodVerify from '../zodVerify'; import { createSchema } from './zod'; import { octokitApp } from '../octokitApp'; +import redis from '../db/redis'; export async function create(prev: any, formData: FormData) { const zod = await zodVerify(createSchema, formData); @@ -35,6 +36,8 @@ export async function create(prev: any, formData: FormData) { }, }, }); + redis.set(`ratelimit:${dbCreate.id}`, `${dbCreate.rateLimitReq}:${dbCreate.rateLimitTime}`); + return { success: true, id: dbCreate.id }; } @@ -79,6 +82,8 @@ export async function ratelimitChange(prev: any, formData: FormData) { rateLimitTime: zod.data.duration, }, }); + redis.set(`ratelimit:${zod.data.id}`, `${zod.data.requests}:${zod.data.duration}`); + return { success: true, id: dbUpdate.id }; }