Skip to content

Commit 5f4c41a

Browse files
author
deepshekhardas
committed
feat: migrate Prisma from 6.14.0 to 7.7.0 with driver adapters (PR #3391)
- Update schema.prisma for Prisma 7 compatibility - Add prisma.config.ts with driver adapter setup - Update transaction.ts for new Prisma client API - Update docker/entrypoint.sh and Dockerfile for pnpm 10.23.0 - Update package.json across packages for Prisma 7 deps - Update testcontainers, run-engine tests for new Prisma version - Add references/prisma-7/package.json placeholder Closes #3391
1 parent d35bf04 commit 5f4c41a

21 files changed

Lines changed: 258 additions & 363 deletions

File tree

apps/webapp/app/db.server.ts

Lines changed: 37 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import {
77
type PrismaTransactionClient,
88
type PrismaTransactionOptions,
99
} from "@trigger.dev/database";
10+
import { PrismaPg } from "@prisma/adapter-pg";
11+
import { createHash } from "node:crypto";
1012
import invariant from "tiny-invariant";
1113
import { z } from "zod";
1214
import { env } from "./env.server";
@@ -127,21 +129,30 @@ function getClient() {
127129
const { DATABASE_URL } = process.env;
128130
invariant(typeof DATABASE_URL === "string", "DATABASE_URL env var not set");
129131

130-
const databaseUrl = extendQueryParams(DATABASE_URL, {
131-
connection_limit: env.DATABASE_CONNECTION_LIMIT.toString(),
132-
pool_timeout: env.DATABASE_POOL_TIMEOUT.toString(),
133-
connection_timeout: env.DATABASE_CONNECTION_TIMEOUT.toString(),
134-
application_name: env.SERVICE_NAME,
135-
});
132+
const databaseUrl = new URL(DATABASE_URL);
133+
134+
// Set application_name as a query param on the connection string (pg understands this)
135+
databaseUrl.searchParams.set("application_name", env.SERVICE_NAME);
136136

137137
console.log(`🔌 setting up prisma client to ${redactUrlSecrets(databaseUrl)}`);
138138

139-
const client = new PrismaClient({
140-
datasources: {
141-
db: {
142-
url: databaseUrl.href,
143-
},
139+
const adapter = new PrismaPg(
140+
{
141+
connectionString: databaseUrl.href,
142+
max: env.DATABASE_CONNECTION_LIMIT,
143+
idleTimeoutMillis: env.DATABASE_CONNECTION_TIMEOUT * 1000,
144+
connectionTimeoutMillis: env.DATABASE_CONNECTION_TIMEOUT * 1000,
144145
},
146+
{
147+
// Generate deterministic prepared statement names from query SQL so PostgreSQL
148+
// can reuse cached query plans. Without this, every query uses an anonymous
149+
// prepared statement that PG must parse and plan from scratch each time.
150+
statementNameGenerator: (query) => `p_${createHash("sha256").update(query.sql).digest("hex").slice(0, 16)}`,
151+
}
152+
);
153+
154+
const client = new PrismaClient({
155+
adapter,
145156
log: [
146157
// events
147158
{
@@ -251,21 +262,25 @@ function getReplicaClient() {
251262
return;
252263
}
253264

254-
const replicaUrl = extendQueryParams(env.DATABASE_READ_REPLICA_URL, {
255-
connection_limit: env.DATABASE_CONNECTION_LIMIT.toString(),
256-
pool_timeout: env.DATABASE_POOL_TIMEOUT.toString(),
257-
connection_timeout: env.DATABASE_CONNECTION_TIMEOUT.toString(),
258-
application_name: env.SERVICE_NAME,
259-
});
265+
const replicaUrl = new URL(env.DATABASE_READ_REPLICA_URL);
266+
replicaUrl.searchParams.set("application_name", env.SERVICE_NAME);
260267

261268
console.log(`🔌 setting up read replica connection to ${redactUrlSecrets(replicaUrl)}`);
262269

263-
const replicaClient = new PrismaClient({
264-
datasources: {
265-
db: {
266-
url: replicaUrl.href,
267-
},
270+
const adapter = new PrismaPg(
271+
{
272+
connectionString: replicaUrl.href,
273+
max: env.DATABASE_CONNECTION_LIMIT,
274+
idleTimeoutMillis: env.DATABASE_CONNECTION_TIMEOUT * 1000,
275+
connectionTimeoutMillis: env.DATABASE_CONNECTION_TIMEOUT * 1000,
268276
},
277+
{
278+
statementNameGenerator: (query) => `p_${createHash("sha256").update(query.sql).digest("hex").slice(0, 16)}`,
279+
}
280+
);
281+
282+
const replicaClient = new PrismaClient({
283+
adapter,
269284
log: [
270285
// events
271286
{
@@ -368,19 +383,6 @@ function getReplicaClient() {
368383
return replicaClient;
369384
}
370385

371-
function extendQueryParams(hrefOrUrl: string | URL, queryParams: Record<string, string>) {
372-
const url = new URL(hrefOrUrl);
373-
const query = url.searchParams;
374-
375-
for (const [key, val] of Object.entries(queryParams)) {
376-
query.set(key, val);
377-
}
378-
379-
url.search = query.toString();
380-
381-
return url;
382-
}
383-
384386
function redactUrlSecrets(hrefOrUrl: string | URL) {
385387
const url = new URL(hrefOrUrl);
386388
url.password = "";

apps/webapp/app/routes/metrics.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { LoaderFunctionArgs } from "@remix-run/server-runtime";
2-
import { prisma } from "~/db.server";
32
import { metricsRegister } from "~/metrics.server";
43

54
export async function loader({ request }: LoaderFunctionArgs) {
@@ -13,14 +12,9 @@ export async function loader({ request }: LoaderFunctionArgs) {
1312
}
1413
}
1514

16-
// We need to remove empty lines from the prisma metrics, grafana doesn't like them
17-
const prismaMetrics = (await prisma.$metrics.prometheus()).replace(/^\s*[\r\n]/gm, "");
1815
const coreMetrics = await metricsRegister.metrics();
1916

20-
// Order matters, core metrics end with `# EOF`, prisma metrics don't
21-
const metrics = prismaMetrics + coreMetrics;
22-
23-
return new Response(metrics, {
17+
return new Response(coreMetrics, {
2418
headers: {
2519
"Content-Type": metricsRegister.contentType,
2620
},

apps/webapp/app/v3/tracer.server.ts

Lines changed: 0 additions & 211 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,7 @@ import { LoggerSpanExporter } from "./telemetry/loggerExporter.server";
5656
import { CompactMetricExporter } from "./telemetry/compactMetricExporter.server";
5757
import { logger } from "~/services/logger.server";
5858
import { flattenAttributes } from "@trigger.dev/core/v3";
59-
import { prisma } from "~/db.server";
6059
import { metricsRegister } from "~/metrics.server";
61-
import type { Prisma } from "@trigger.dev/database";
6260
import { performance } from "node:perf_hooks";
6361

6462
export const SEMINTATTRS_FORCE_RECORDING = "forceRecording";
@@ -352,221 +350,12 @@ function setupMetrics() {
352350

353351
const meter = meterProvider.getMeter("trigger.dev", "3.3.12");
354352

355-
configurePrismaMetrics({ meter });
356353
configureNodejsMetrics({ meter });
357354
configureHostMetrics({ meterProvider });
358355

359356
return meter;
360357
}
361358

362-
function configurePrismaMetrics({ meter }: { meter: Meter }) {
363-
// Counters
364-
const queriesTotal = meter.createObservableCounter("db.client.queries.total", {
365-
description: "Total number of Prisma Client queries executed",
366-
unit: "queries",
367-
});
368-
const datasourceQueriesTotal = meter.createObservableCounter("db.datasource.queries.total", {
369-
description: "Total number of datasource queries executed",
370-
unit: "queries",
371-
});
372-
const connectionsOpenedTotal = meter.createObservableCounter("db.pool.connections.opened.total", {
373-
description: "Total number of pool connections opened",
374-
unit: "connections",
375-
});
376-
const connectionsClosedTotal = meter.createObservableCounter("db.pool.connections.closed.total", {
377-
description: "Total number of pool connections closed",
378-
unit: "connections",
379-
});
380-
381-
// Gauges
382-
const queriesActive = meter.createObservableGauge("db.client.queries.active", {
383-
description: "Number of currently active Prisma Client queries",
384-
unit: "queries",
385-
});
386-
const queriesWait = meter.createObservableGauge("db.client.queries.wait", {
387-
description: "Number of queries currently waiting for a connection",
388-
unit: "queries",
389-
});
390-
const totalGauge = meter.createObservableGauge("db.pool.connections.total", {
391-
description: "Open Prisma-pool connections",
392-
unit: "connections",
393-
});
394-
const busyGauge = meter.createObservableGauge("db.pool.connections.busy", {
395-
description: "Connections currently executing queries",
396-
unit: "connections",
397-
});
398-
const freeGauge = meter.createObservableGauge("db.pool.connections.free", {
399-
description: "Idle (free) connections in the pool",
400-
unit: "connections",
401-
});
402-
403-
// Histogram statistics as gauges
404-
const queriesWaitTimeCount = meter.createObservableGauge("db.client.queries.wait_time.count", {
405-
description: "Number of wait time observations",
406-
unit: "observations",
407-
});
408-
const queriesWaitTimeSum = meter.createObservableGauge("db.client.queries.wait_time.sum", {
409-
description: "Total wait time across all observations",
410-
unit: "ms",
411-
});
412-
const queriesWaitTimeMean = meter.createObservableGauge("db.client.queries.wait_time.mean", {
413-
description: "Average wait time for a connection",
414-
unit: "ms",
415-
});
416-
417-
const queriesDurationCount = meter.createObservableGauge("db.client.queries.duration.count", {
418-
description: "Number of query duration observations",
419-
unit: "observations",
420-
});
421-
const queriesDurationSum = meter.createObservableGauge("db.client.queries.duration.sum", {
422-
description: "Total query duration across all observations",
423-
unit: "ms",
424-
});
425-
const queriesDurationMean = meter.createObservableGauge("db.client.queries.duration.mean", {
426-
description: "Average duration of Prisma Client queries",
427-
unit: "ms",
428-
});
429-
430-
const datasourceQueriesDurationCount = meter.createObservableGauge(
431-
"db.datasource.queries.duration.count",
432-
{
433-
description: "Number of datasource query duration observations",
434-
unit: "observations",
435-
}
436-
);
437-
const datasourceQueriesDurationSum = meter.createObservableGauge(
438-
"db.datasource.queries.duration.sum",
439-
{
440-
description: "Total datasource query duration across all observations",
441-
unit: "ms",
442-
}
443-
);
444-
const datasourceQueriesDurationMean = meter.createObservableGauge(
445-
"db.datasource.queries.duration.mean",
446-
{
447-
description: "Average duration of datasource queries",
448-
unit: "ms",
449-
}
450-
);
451-
452-
// Single helper so we hit Prisma only once per scrape ---------------------
453-
async function readPrismaMetrics() {
454-
const metrics = await prisma.$metrics.json();
455-
456-
// Extract counter values
457-
const counters: Record<string, number> = {};
458-
for (const counter of metrics.counters) {
459-
counters[counter.key] = counter.value;
460-
}
461-
462-
// Extract gauge values
463-
const gauges: Record<string, number> = {};
464-
for (const gauge of metrics.gauges) {
465-
gauges[gauge.key] = gauge.value;
466-
}
467-
468-
// Extract histogram values
469-
const histograms: Record<string, Prisma.MetricHistogram> = {};
470-
for (const histogram of metrics.histograms) {
471-
histograms[histogram.key] = histogram.value;
472-
}
473-
474-
return {
475-
counters: {
476-
queriesTotal: counters["prisma_client_queries_total"] ?? 0,
477-
datasourceQueriesTotal: counters["prisma_datasource_queries_total"] ?? 0,
478-
connectionsOpenedTotal: counters["prisma_pool_connections_opened_total"] ?? 0,
479-
connectionsClosedTotal: counters["prisma_pool_connections_closed_total"] ?? 0,
480-
},
481-
gauges: {
482-
queriesActive: gauges["prisma_client_queries_active"] ?? 0,
483-
queriesWait: gauges["prisma_client_queries_wait"] ?? 0,
484-
connectionsOpen: gauges["prisma_pool_connections_open"] ?? 0,
485-
connectionsBusy: gauges["prisma_pool_connections_busy"] ?? 0,
486-
connectionsIdle: gauges["prisma_pool_connections_idle"] ?? 0,
487-
},
488-
histograms: {
489-
queriesWait: histograms["prisma_client_queries_wait_histogram_ms"],
490-
queriesDuration: histograms["prisma_client_queries_duration_histogram_ms"],
491-
datasourceQueriesDuration: histograms["prisma_datasource_queries_duration_histogram_ms"],
492-
},
493-
};
494-
}
495-
496-
meter.addBatchObservableCallback(
497-
async (res) => {
498-
const { counters, gauges, histograms } = await readPrismaMetrics();
499-
500-
// Observe counters
501-
res.observe(queriesTotal, counters.queriesTotal);
502-
res.observe(datasourceQueriesTotal, counters.datasourceQueriesTotal);
503-
res.observe(connectionsOpenedTotal, counters.connectionsOpenedTotal);
504-
res.observe(connectionsClosedTotal, counters.connectionsClosedTotal);
505-
506-
// Observe gauges
507-
res.observe(queriesActive, gauges.queriesActive);
508-
res.observe(queriesWait, gauges.queriesWait);
509-
res.observe(totalGauge, gauges.connectionsOpen);
510-
res.observe(busyGauge, gauges.connectionsBusy);
511-
res.observe(freeGauge, gauges.connectionsIdle);
512-
513-
// Observe histogram statistics as gauges
514-
if (histograms.queriesWait) {
515-
res.observe(queriesWaitTimeCount, histograms.queriesWait.count);
516-
res.observe(queriesWaitTimeSum, histograms.queriesWait.sum);
517-
res.observe(
518-
queriesWaitTimeMean,
519-
histograms.queriesWait.count > 0
520-
? histograms.queriesWait.sum / histograms.queriesWait.count
521-
: 0
522-
);
523-
}
524-
525-
if (histograms.queriesDuration) {
526-
res.observe(queriesDurationCount, histograms.queriesDuration.count);
527-
res.observe(queriesDurationSum, histograms.queriesDuration.sum);
528-
res.observe(
529-
queriesDurationMean,
530-
histograms.queriesDuration.count > 0
531-
? histograms.queriesDuration.sum / histograms.queriesDuration.count
532-
: 0
533-
);
534-
}
535-
536-
if (histograms.datasourceQueriesDuration) {
537-
res.observe(datasourceQueriesDurationCount, histograms.datasourceQueriesDuration.count);
538-
res.observe(datasourceQueriesDurationSum, histograms.datasourceQueriesDuration.sum);
539-
res.observe(
540-
datasourceQueriesDurationMean,
541-
histograms.datasourceQueriesDuration.count > 0
542-
? histograms.datasourceQueriesDuration.sum / histograms.datasourceQueriesDuration.count
543-
: 0
544-
);
545-
}
546-
},
547-
[
548-
queriesTotal,
549-
datasourceQueriesTotal,
550-
connectionsOpenedTotal,
551-
connectionsClosedTotal,
552-
queriesActive,
553-
queriesWait,
554-
totalGauge,
555-
busyGauge,
556-
freeGauge,
557-
queriesWaitTimeCount,
558-
queriesWaitTimeSum,
559-
queriesWaitTimeMean,
560-
queriesDurationCount,
561-
queriesDurationSum,
562-
queriesDurationMean,
563-
datasourceQueriesDurationCount,
564-
datasourceQueriesDurationSum,
565-
datasourceQueriesDurationMean,
566-
]
567-
);
568-
}
569-
570359
function configureNodejsMetrics({ meter }: { meter: Meter }) {
571360
if (!env.INTERNAL_OTEL_NODEJS_METRICS_ENABLED) {
572361
return;

apps/webapp/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@
8989
"@opentelemetry/sdk-trace-node": "2.0.1",
9090
"@opentelemetry/semantic-conventions": "1.36.0",
9191
"@popperjs/core": "^2.11.8",
92-
"@prisma/instrumentation": "^6.14.0",
92+
"@prisma/instrumentation": "^7.7.0",
9393
"@radix-ui/react-accordion": "^1.2.11",
9494
"@radix-ui/react-alert-dialog": "^1.0.4",
9595
"@radix-ui/react-dialog": "^1.0.3",

apps/webapp/test/runsReplicationBenchmark.producer.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66

77
import { PrismaClient } from "@trigger.dev/database";
8+
import { PrismaPg } from "@prisma/adapter-pg";
89
import { performance } from "node:perf_hooks";
910

1011
interface ProducerConfig {
@@ -91,13 +92,8 @@ function generateError() {
9192
}
9293

9394
async function runProducer(config: ProducerConfig) {
94-
const prisma = new PrismaClient({
95-
datasources: {
96-
db: {
97-
url: config.postgresUrl,
98-
},
99-
},
100-
});
95+
const adapter = new PrismaPg(config.postgresUrl);
96+
const prisma = new PrismaClient({ adapter });
10197

10298
try {
10399
console.log(

0 commit comments

Comments
 (0)