perf: eliminate findUnique roundtrip with optimistic direct insert & eager prisma connect
This commit is contained in:
1 parent
bb135dbc9e
commit
fec54aab1f
2 files changed
+83
-60
No files matched your search
@@ -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),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -11,3 +11,6 @@ export const prisma =
|
||||
});
|
||||
|
||||
globalForPrisma.prisma = prisma;
|
||||
|
||||
// Eagerly initiate database connection in background to avoid cold handshake latency
|
||||
prisma.$connect().catch(() => {});
|
||||
Reference in new issue
Block a user