fix: cap Prisma pool during next build to stop deploy SSG timeouts
Next build workers were each opening up to DATABASE_POOL_SIZE connections and exhausting MySQL (pool active=0), hanging sitemap generation. Cap build pool to 5, fail connect faster, limit SSG concurrency, and make sitemap dynamic. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
1 parent
803e8f36c1
commit
77931db775
5 files changed
+53
-3
No files matched your search
@@ -1,6 +1,10 @@
|
||||
import type { MetadataRoute } from "next";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
// Built at request time — avoids competing with SSG workers for scarce DB
|
||||
// connections during `next build` (pool timeouts killed deploy on sitemap).
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
||||
const appUrl = process.env.APP_URL ?? "http://localhost:3000";
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { resolvePrismaConnectionLimit } from "./prisma-pool";
|
||||
|
||||
describe("resolvePrismaConnectionLimit", () => {
|
||||
it("caps the pool during production build phases", () => {
|
||||
expect(resolvePrismaConnectionLimit(40, "phase-production-build")).toBe(5);
|
||||
expect(
|
||||
resolvePrismaConnectionLimit(40, "phase-production-compile"),
|
||||
).toBe(5);
|
||||
expect(resolvePrismaConnectionLimit(3, "phase-production-build")).toBe(3);
|
||||
});
|
||||
|
||||
it("keeps the configured pool outside build", () => {
|
||||
expect(resolvePrismaConnectionLimit(40, undefined)).toBe(40);
|
||||
expect(resolvePrismaConnectionLimit(40, "phase-development-server")).toBe(
|
||||
40,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Cap pool size during `next build` so N workers × pool don't exhaust MySQL.
|
||||
* Kept free of Prisma/env imports so unit tests can exercise it in isolation.
|
||||
*/
|
||||
export function resolvePrismaConnectionLimit(
|
||||
poolSize: number,
|
||||
phase = process.env.NEXT_PHASE,
|
||||
): number {
|
||||
if (
|
||||
phase === "phase-production-build" ||
|
||||
phase === "phase-production-compile"
|
||||
) {
|
||||
return Math.min(poolSize, 5);
|
||||
}
|
||||
return poolSize;
|
||||
}
|
||||
+12
-3
@@ -1,20 +1,29 @@
|
||||
import { PrismaMariaDb } from "@prisma/adapter-mariadb";
|
||||
import { env } from "@/env";
|
||||
import { PrismaClient } from "@/generated/prisma/client";
|
||||
import { resolvePrismaConnectionLimit } from "@/lib/prisma-pool";
|
||||
|
||||
const globalForPrisma = globalThis as unknown as { prisma?: PrismaClient };
|
||||
|
||||
function createPrismaClient(): PrismaClient {
|
||||
const url = new URL(env.DATABASE_URL);
|
||||
const isBuild =
|
||||
process.env.NEXT_PHASE === "phase-production-build" ||
|
||||
process.env.NEXT_PHASE === "phase-production-compile";
|
||||
const connectionLimit = resolvePrismaConnectionLimit(env.DATABASE_POOL_SIZE);
|
||||
// Fail fast during SSG so Next can retry/fallback instead of hanging 30s+.
|
||||
const connectTimeout = isBuild
|
||||
? Math.min(env.DATABASE_CONNECT_TIMEOUT_MS, 5_000)
|
||||
: env.DATABASE_CONNECT_TIMEOUT_MS;
|
||||
const adapter = new PrismaMariaDb({
|
||||
host: url.hostname,
|
||||
port: Number(url.port) || 3306,
|
||||
user: decodeURIComponent(url.username),
|
||||
password: decodeURIComponent(url.password),
|
||||
database: url.pathname.replace(/^\//, ""),
|
||||
connectionLimit: env.DATABASE_POOL_SIZE,
|
||||
connectTimeout: env.DATABASE_CONNECT_TIMEOUT_MS,
|
||||
acquireTimeout: env.DATABASE_CONNECT_TIMEOUT_MS,
|
||||
connectionLimit,
|
||||
connectTimeout,
|
||||
acquireTimeout: connectTimeout,
|
||||
idleTimeout: env.DATABASE_IDLE_TIMEOUT_MS,
|
||||
});
|
||||
return new PrismaClient({
|
||||
|
||||
Reference in new issue
Block a user