mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
fix(db): share one Prisma client across the server bundles
A production build loads src/lib/db.ts once per server bundle, and the client was only parked on globalThis outside production. The running process held three clients: about 28 MB of query-compiler state each on a heap capped near 520 MB, and three pg pools each sized to the full per-process connection budget. Park the client on globalThis in every environment so each copy of the module resolves the same one.
This commit is contained in:
1 parent
4cad43e4d5
commit
5cbb080262
2 files changed
+39
-5
No files matched your search
@@ -2,7 +2,7 @@
|
||||
* Pool configuration stays unit-tested here so connection ceilings and wait
|
||||
* timeouts cannot silently fall back to separate library defaults.
|
||||
*/
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import {
|
||||
buildSessionOptions,
|
||||
@@ -129,3 +129,32 @@ describe("buildSessionOptions", () => {
|
||||
expect(buildSessionOptions()).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("process-wide Prisma client", () => {
|
||||
const globalSlot = globalThis as unknown as { prisma?: unknown };
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllEnvs();
|
||||
delete globalSlot.prisma;
|
||||
vi.resetModules();
|
||||
});
|
||||
|
||||
it("shares one client across separate copies of the module in production", async () => {
|
||||
// A production build loads this module once per server bundle; each copy
|
||||
// must resolve the same client rather than constructing its own.
|
||||
vi.stubEnv("NODE_ENV", "production");
|
||||
vi.stubEnv(
|
||||
"DATABASE_URL",
|
||||
"postgresql://healthlog:test@db:5432/healthlog?connection_limit=20",
|
||||
);
|
||||
delete globalSlot.prisma;
|
||||
|
||||
vi.resetModules();
|
||||
const first = (await import("../db")).prisma;
|
||||
vi.resetModules();
|
||||
const second = (await import("../db")).prisma;
|
||||
|
||||
expect(second).toBe(first);
|
||||
expect(globalSlot.prisma).toBe(first);
|
||||
});
|
||||
});
|
||||
+9
-4
@@ -106,11 +106,16 @@ function createPrismaClient() {
|
||||
return new PrismaClient({ adapter });
|
||||
}
|
||||
|
||||
// One client per process, in production too. A production build gives every
|
||||
// server bundle its own copy of this module (the route handlers, the RSC/SSR
|
||||
// renderer, and the instrumentation bundle that boots the workers), so a
|
||||
// module-scoped client alone meant three of them: three query-compiler states
|
||||
// of roughly 28 MB each on a heap that V8 caps near 520 MB in a 1 GiB
|
||||
// container, and three pg pools each sized to the whole per-process
|
||||
// connection budget above. Parking the client on `globalThis` makes every
|
||||
// copy of the module resolve the same one.
|
||||
export const prisma = globalForPrisma.prisma ?? createPrismaClient();
|
||||
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
globalForPrisma.prisma = prisma;
|
||||
}
|
||||
globalForPrisma.prisma = prisma;
|
||||
|
||||
// Prisma's `InputJsonValue` requires an explicit index signature that
|
||||
// typed application shapes (Zod-validated, hand-written interfaces)
|
||||
|
||||
Reference in new issue
Block a user