From fec54aab1f331ae7ca64f508f44cafca2d811e39 Mon Sep 17 00:00:00 2001 From: Tien Tran Date: Mon, 28 Sep 2026 23:28:12 -0700 Subject: [PATCH] perf: eliminate findUnique roundtrip with optimistic direct insert & eager prisma connect --- src/app/api/shorten/route.ts | 140 ++++++++++++++++++++--------------- src/lib/prisma.ts | 3 + 2 files changed, 83 insertions(+), 60 deletions(-) diff --git a/src/app/api/shorten/route.ts b/src/app/api/shorten/route.ts index 3853f0d..5ee128f 100644 --- a/src/app/api/shorten/route.ts +++ b/src/app/api/shorten/route.ts @@ -2,7 +2,7 @@ import { NextResponse } from "next/server"; import bcrypt from "bcryptjs"; import { prisma } from "@/lib/prisma"; import { getCurrentUser } from "@/lib/auth"; -import { generateUniqueShortCode } from "@/lib/short-code"; +import { generateRandomCode, getDefaultUrlLength } from "@/lib/short-code"; import { generateQRCodeDataUrl } from "@/lib/qr"; export async function POST(req: Request) { @@ -41,37 +41,6 @@ export async function POST(req: Request) { ); } - // Determine short code - let shortCode = ""; - if (custom_code && typeof custom_code === "string") { - const trimmedCode = custom_code.trim(); - if (!/^[a-zA-Z0-9_-]{3,20}$/.test(trimmedCode)) { - return NextResponse.json( - { success: false, message: "Mã rút gọn chỉ chứa chữ cái, chữ số, dấu gạch dưới hoặc gạch ngang (từ 3-20 ký tự)" }, - { status: 422 } - ); - } - - // Check collision - const existing = await prisma.shortUrl.findUnique({ - where: { shortCode: trimmedCode }, - }); - if (existing) { - return NextResponse.json({ success: false, message: "Mã rút gọn này đã được sử dụng. Vui lòng chọn mã khác." }, { status: 409 }); - } - shortCode = trimmedCode; - } else { - // Auto-generate using configured default_url_length - shortCode = await generateUniqueShortCode(); - } - const tCodeGen = performance.now(); - - // Hash password if set - let hashedPassword: string | null = null; - if (password && typeof password === "string" && password.trim().length >= 4) { - hashedPassword = await bcrypt.hash(password.trim(), 10); - } - // Process Tags let tagConnectList: { name: string }[] = []; if (tags && typeof tags === "string") { @@ -93,32 +62,84 @@ export async function POST(req: Request) { } } - // Create ShortUrl in DB immediately (instant response) - const createdUrl = await prisma.shortUrl.create({ - data: { - originalUrl: url.trim(), - shortCode, - title: null, - password: hashedPassword, - expiresAt: expirationDate, - displayOnPortal: Boolean(display_on_portal), - userId: user ? user.id : null, - tags: { - connectOrCreate: tagConnectList.map((tag) => ({ - where: { name: tag.name }, - create: { name: tag.name }, - })), - }, - }, - include: { - tags: true, - }, - }); - const tDbCreate = performance.now(); + // Hash password if set + let hashedPassword: string | null = null; + if (password && typeof password === "string" && password.trim().length >= 4) { + hashedPassword = await bcrypt.hash(password.trim(), 10); + } + + let customShortCode = ""; + if (custom_code && typeof custom_code === "string") { + const trimmedCode = custom_code.trim(); + if (!/^[a-zA-Z0-9_-]{3,20}$/.test(trimmedCode)) { + return NextResponse.json( + { success: false, message: "Mã rút gọn chỉ chứa chữ cái, chữ số, dấu gạch dưới hoặc gạch ngang (từ 3-20 ký tự)" }, + { status: 422 } + ); + } + customShortCode = trimmedCode; + } + + // Prepare insert data + const insertData: any = { + originalUrl: url.trim(), + title: null, + password: hashedPassword, + expiresAt: expirationDate, + displayOnPortal: Boolean(display_on_portal), + userId: user ? user.id : null, + }; + + if (tagConnectList.length > 0) { + insertData.tags = { + connectOrCreate: tagConnectList.map((tag) => ({ + where: { name: tag.name }, + create: { name: tag.name }, + })), + }; + } + + let finalShortCode = customShortCode; + let createdUrl: any = null; + + if (customShortCode) { + const existing = await prisma.shortUrl.findUnique({ + where: { shortCode: customShortCode }, + select: { id: true }, + }); + if (existing) { + return NextResponse.json({ success: false, message: "Mã rút gọn này đã được sử dụng. Vui lòng chọn mã khác." }, { status: 409 }); + } + + createdUrl = await prisma.shortUrl.create({ + data: { ...insertData, shortCode: customShortCode }, + include: tagConnectList.length > 0 ? { tags: true } : undefined, + }); + } else { + // Optimistic single-roundtrip direct insert (No pre-checking query needed) + const length = await getDefaultUrlLength(); + for (let attempt = 0; attempt < 3; attempt++) { + const candidateCode = generateRandomCode(length + (attempt > 0 ? 1 : 0)); + try { + createdUrl = await prisma.shortUrl.create({ + data: { ...insertData, shortCode: candidateCode }, + include: tagConnectList.length > 0 ? { tags: true } : undefined, + }); + finalShortCode = candidateCode; + break; + } catch (err: any) { + if (err.code === "P2002" && attempt < 2) { + continue; // Collision retry + } + throw err; + } + } + } + const tDb = performance.now(); // Build absolute short URL const appUrl = (process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000").replace(/\/$/, ""); - const fullShortUrl = `${appUrl}/${shortCode}`; + const fullShortUrl = `${appUrl}/${finalShortCode}`; // Generate QR Code const qrCodeDataUrl = await generateQRCodeDataUrl(fullShortUrl, { size: 240 }); @@ -128,7 +149,7 @@ export async function POST(req: Request) { success: true, data: { id: createdUrl.id, - short_code: shortCode, + short_code: finalShortCode, short_url: fullShortUrl, original_url: createdUrl.originalUrl, title: createdUrl.title, @@ -136,13 +157,12 @@ export async function POST(req: Request) { expires_at: createdUrl.expiresAt, is_password_protected: Boolean(createdUrl.password), display_on_portal: createdUrl.displayOnPortal, - tags: createdUrl.tags.map((t) => t.name), + tags: createdUrl.tags ? createdUrl.tags.map((t: any) => t.name) : [], }, _timings: { auth_ms: Math.round(tAuth - t0), - code_gen_ms: Math.round(tCodeGen - tAuth), - db_create_ms: Math.round(tDbCreate - tCodeGen), - qr_ms: Math.round(tQr - tDbCreate), + db_insert_ms: Math.round(tDb - tAuth), + qr_ms: Math.round(tQr - tDb), total_server_ms: Math.round(tQr - t0), }, }); diff --git a/src/lib/prisma.ts b/src/lib/prisma.ts index 8ef5ecf..683e53e 100644 --- a/src/lib/prisma.ts +++ b/src/lib/prisma.ts @@ -11,3 +11,6 @@ export const prisma = }); globalForPrisma.prisma = prisma; + +// Eagerly initiate database connection in background to avoid cold handshake latency +prisma.$connect().catch(() => {});