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 bcrypt from "bcryptjs";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
import { getCurrentUser } from "@/lib/auth";
|
import { getCurrentUser } from "@/lib/auth";
|
||||||
import { generateUniqueShortCode } from "@/lib/short-code";
|
import { generateRandomCode, getDefaultUrlLength } from "@/lib/short-code";
|
||||||
import { generateQRCodeDataUrl } from "@/lib/qr";
|
import { generateQRCodeDataUrl } from "@/lib/qr";
|
||||||
|
|
||||||
export async function POST(req: Request) {
|
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
|
// Process Tags
|
||||||
let tagConnectList: { name: string }[] = [];
|
let tagConnectList: { name: string }[] = [];
|
||||||
if (tags && typeof tags === "string") {
|
if (tags && typeof tags === "string") {
|
||||||
@@ -93,32 +62,84 @@ export async function POST(req: Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create ShortUrl in DB immediately (instant response)
|
// Hash password if set
|
||||||
const createdUrl = await prisma.shortUrl.create({
|
let hashedPassword: string | null = null;
|
||||||
data: {
|
if (password && typeof password === "string" && password.trim().length >= 4) {
|
||||||
originalUrl: url.trim(),
|
hashedPassword = await bcrypt.hash(password.trim(), 10);
|
||||||
shortCode,
|
}
|
||||||
title: null,
|
|
||||||
password: hashedPassword,
|
let customShortCode = "";
|
||||||
expiresAt: expirationDate,
|
if (custom_code && typeof custom_code === "string") {
|
||||||
displayOnPortal: Boolean(display_on_portal),
|
const trimmedCode = custom_code.trim();
|
||||||
userId: user ? user.id : null,
|
if (!/^[a-zA-Z0-9_-]{3,20}$/.test(trimmedCode)) {
|
||||||
tags: {
|
return NextResponse.json(
|
||||||
connectOrCreate: tagConnectList.map((tag) => ({
|
{ 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ự)" },
|
||||||
where: { name: tag.name },
|
{ status: 422 }
|
||||||
create: { name: tag.name },
|
);
|
||||||
})),
|
}
|
||||||
},
|
customShortCode = trimmedCode;
|
||||||
},
|
}
|
||||||
include: {
|
|
||||||
tags: true,
|
// Prepare insert data
|
||||||
},
|
const insertData: any = {
|
||||||
});
|
originalUrl: url.trim(),
|
||||||
const tDbCreate = performance.now();
|
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
|
// Build absolute short URL
|
||||||
const appUrl = (process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000").replace(/\/$/, "");
|
const appUrl = (process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000").replace(/\/$/, "");
|
||||||
const fullShortUrl = `${appUrl}/${shortCode}`;
|
const fullShortUrl = `${appUrl}/${finalShortCode}`;
|
||||||
|
|
||||||
// Generate QR Code
|
// Generate QR Code
|
||||||
const qrCodeDataUrl = await generateQRCodeDataUrl(fullShortUrl, { size: 240 });
|
const qrCodeDataUrl = await generateQRCodeDataUrl(fullShortUrl, { size: 240 });
|
||||||
@@ -128,7 +149,7 @@ export async function POST(req: Request) {
|
|||||||
success: true,
|
success: true,
|
||||||
data: {
|
data: {
|
||||||
id: createdUrl.id,
|
id: createdUrl.id,
|
||||||
short_code: shortCode,
|
short_code: finalShortCode,
|
||||||
short_url: fullShortUrl,
|
short_url: fullShortUrl,
|
||||||
original_url: createdUrl.originalUrl,
|
original_url: createdUrl.originalUrl,
|
||||||
title: createdUrl.title,
|
title: createdUrl.title,
|
||||||
@@ -136,13 +157,12 @@ export async function POST(req: Request) {
|
|||||||
expires_at: createdUrl.expiresAt,
|
expires_at: createdUrl.expiresAt,
|
||||||
is_password_protected: Boolean(createdUrl.password),
|
is_password_protected: Boolean(createdUrl.password),
|
||||||
display_on_portal: createdUrl.displayOnPortal,
|
display_on_portal: createdUrl.displayOnPortal,
|
||||||
tags: createdUrl.tags.map((t) => t.name),
|
tags: createdUrl.tags ? createdUrl.tags.map((t: any) => t.name) : [],
|
||||||
},
|
},
|
||||||
_timings: {
|
_timings: {
|
||||||
auth_ms: Math.round(tAuth - t0),
|
auth_ms: Math.round(tAuth - t0),
|
||||||
code_gen_ms: Math.round(tCodeGen - tAuth),
|
db_insert_ms: Math.round(tDb - tAuth),
|
||||||
db_create_ms: Math.round(tDbCreate - tCodeGen),
|
qr_ms: Math.round(tQr - tDb),
|
||||||
qr_ms: Math.round(tQr - tDbCreate),
|
|
||||||
total_server_ms: Math.round(tQr - t0),
|
total_server_ms: Math.round(tQr - t0),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,3 +11,6 @@ export const prisma =
|
|||||||
});
|
});
|
||||||
|
|
||||||
globalForPrisma.prisma = 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