chore: USE_POOL env var for api v2 prisma pooling

This commit is contained in:
cal.com
2026-01-09 17:55:25 +02:00
parent 8666efa29d
commit a97092619f
11 changed files with 93 additions and 71 deletions
-1
View File
@@ -1,6 +1,5 @@
import "./instrument";
import process from "node:process";
import {
API_VERSIONS,
API_VERSIONS_ENUM,
+1 -1
View File
@@ -1,5 +1,4 @@
import { getEnv } from "@/env";
import type { AppConfig } from "./type";
const loadConfig = (): AppConfig => {
@@ -31,6 +30,7 @@ const loadConfig = (): AppConfig => {
workerReadPoolMax: getEnv("DATABASE_READ_WORKER_POOL_MAX", 4),
workerWritePoolMax: getEnv("DATABASE_WRITE_WORKER_POOL_MAX", 6),
redisUrl: getEnv("REDIS_URL"),
usePool: getEnv("USE_POOL", "true") === "true",
},
next: {
authSecret: getEnv("NEXTAUTH_SECRET"),
+1
View File
@@ -18,6 +18,7 @@ export type AppConfig = {
workerReadPoolMax: number;
workerWritePoolMax: number;
redisUrl: string;
usePool: boolean;
};
next: {
authSecret: string;
+1 -2
View File
@@ -40,11 +40,10 @@ export type Environment = {
DATABASE_WRITE_WORKER_POOL_MAX: number;
ENABLE_SLOTS_WORKERS: string;
SLOTS_WORKER_POOL_SIZE: string;
USE_POOL: string;
};
export const getEnv = <K extends keyof Environment>(key: K, fallback?: Environment[K]): Environment[K] => {
// biome-ignore lint/style/noProcessEnv: <config file>
// biome-ignore lint/correctness/noProcessGlobal: <config file>
const value = process.env[key] as Environment[K] | undefined;
if (value === undefined) {
-4
View File
@@ -1,17 +1,13 @@
import "dotenv/config";
import { IncomingMessage, Server, ServerResponse } from "node:http";
import process from "node:process";
import { Logger } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { NestFactory } from "@nestjs/core";
import type { NestExpressApplication } from "@nestjs/platform-express";
import { WinstonModule } from "nest-winston";
import * as qs from "qs";
import type { AppConfig } from "@/config/type";
import { AppModule } from "./app.module";
import { bootstrap } from "./bootstrap";
import { loggerConfig } from "./lib/logger";
@@ -1,63 +1,66 @@
import type { OnModuleDestroy, OnModuleInit } from "@nestjs/common";
import { Injectable, Logger } from "@nestjs/common";
import { PrismaClient } from "@calcom/prisma/client";
import { Injectable, Logger, type OnModuleDestroy, type OnModuleInit } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { PrismaPg } from "@prisma/adapter-pg";
import { Pool } from "pg";
import { PrismaClient } from "@calcom/prisma/client";
const DB_MAX_POOL_CONNECTION = 10;
export interface PrismaServiceOptions {
readUrl?: string;
maxReadConnections?: number;
e2e?: boolean;
type: "main" | "worker";
}
@Injectable()
export class PrismaReadService implements OnModuleInit, OnModuleDestroy {
private logger = new Logger("PrismaReadService");
public prisma!: PrismaClient;
private pool!: Pool;
private options!: PrismaServiceOptions;
constructor(private configService?: ConfigService) {
constructor(configService?: ConfigService) {
if (configService) {
// Use ConfigService defaults
const readUrl = configService.get<string>("db.readUrl", { infer: true });
const poolMax = parseInt(
configService.get<number>("db.readPoolMax", { infer: true }) ?? DB_MAX_POOL_CONNECTION
configService.get<number>("db.readPoolMax", { infer: true }) ?? DB_MAX_POOL_CONNECTION,
10
);
const e2e = configService.get<boolean>("e2e", { infer: true }) ?? false;
const usePool = configService.get<boolean>("db.usePool", { infer: true }) ?? true;
this.setOptions({
readUrl,
maxReadConnections: poolMax,
e2e,
usePool,
type: "main",
});
}
}
setOptions(options: PrismaServiceOptions) {
this.options = options;
setOptions(options: PrismaServiceOptions): void {
const dbUrl = options.readUrl;
const isE2E = options.e2e ?? false;
const usePool = options.usePool ?? true;
this.pool = new Pool({
connectionString: dbUrl,
max: isE2E ? 1 : options.maxReadConnections ?? DB_MAX_POOL_CONNECTION,
idleTimeoutMillis: 300000,
});
if (usePool) {
let maxReadConnections = options.maxReadConnections ?? DB_MAX_POOL_CONNECTION;
if (isE2E) {
maxReadConnections = 1;
}
const adapter = new PrismaPg(this.pool);
this.prisma = new PrismaClient({ adapter });
this.pool = new Pool({
connectionString: dbUrl,
max: maxReadConnections,
idleTimeoutMillis: 300000,
});
const adapter = new PrismaPg(this.pool);
this.prisma = new PrismaClient({ adapter });
} else {
this.prisma = new PrismaClient({
datasourceUrl: dbUrl,
});
}
}
async onModuleInit() {
async onModuleInit(): Promise<void> {
if (!this.prisma) return;
try {
@@ -68,7 +71,7 @@ export class PrismaReadService implements OnModuleInit, OnModuleDestroy {
}
}
async onModuleDestroy() {
async onModuleDestroy(): Promise<void> {
try {
if (this.prisma) await this.prisma.$disconnect();
if (this.pool) await this.pool.end();
@@ -77,3 +80,11 @@ export class PrismaReadService implements OnModuleInit, OnModuleDestroy {
}
}
}
export interface PrismaServiceOptions {
readUrl?: string;
maxReadConnections?: number;
e2e?: boolean;
usePool?: boolean;
type: "main" | "worker";
}
@@ -1,7 +1,7 @@
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
import { Module } from "@nestjs/common";
import { ConfigModule, ConfigService } from "@nestjs/config";
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
@Module({
imports: [ConfigModule],
@@ -12,7 +12,7 @@ import { ConfigModule, ConfigService } from "@nestjs/config";
const service = new PrismaReadService();
service.setOptions({
readUrl: config.get<string>("db.readUrl", { infer: true }),
maxReadConnections: parseInt(config.get<number>("db.workerReadPoolMax", { infer: true })),
maxReadConnections: parseInt(config.get<number>("db.workerReadPoolMax", { infer: true }), 10),
e2e: config.get<boolean>("e2e", { infer: true }) ?? false,
type: "worker",
});
@@ -26,7 +26,7 @@ import { ConfigModule, ConfigService } from "@nestjs/config";
const service = new PrismaWriteService();
service.setOptions({
writeUrl: config.get<string>("db.writeUrl", { infer: true }),
maxWriteConnections: parseInt(config.get<number>("db.workerWritePoolMax", { infer: true })),
maxWriteConnections: parseInt(config.get<number>("db.workerWritePoolMax", { infer: true }), 10),
e2e: config.get<boolean>("e2e", { infer: true }) ?? false,
type: "worker",
});
@@ -1,16 +1,16 @@
import { OnModuleDestroy, OnModuleInit, Injectable, Logger } from "@nestjs/common";
import { PrismaClient } from "@calcom/prisma/client";
import { Injectable, Logger, OnModuleDestroy, OnModuleInit } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { PrismaPg } from "@prisma/adapter-pg";
import { Pool } from "pg";
import { PrismaClient } from "@calcom/prisma/client";
const DB_MAX_POOL_CONNECTION = 5;
export interface PrismaServiceOptions {
writeUrl?: string;
maxWriteConnections?: number;
e2e?: boolean;
usePool?: boolean;
type: "main" | "worker";
}
@@ -19,41 +19,54 @@ export class PrismaWriteService implements OnModuleInit, OnModuleDestroy {
private logger = new Logger("PrismaWriteService");
public prisma!: PrismaClient;
private pool!: Pool;
private options!: PrismaServiceOptions;
constructor(private configService?: ConfigService) {
constructor(configService?: ConfigService) {
if (configService) {
const writeUrl = configService.get<string>("db.writeUrl", { infer: true });
const poolMax = parseInt(
configService.get<number>("db.writePoolMax", { infer: true }) ?? DB_MAX_POOL_CONNECTION
String(configService.get<number>("db.writePoolMax", { infer: true }) ?? DB_MAX_POOL_CONNECTION),
10
);
const e2e = configService.get<boolean>("e2e", { infer: true }) ?? false;
const usePool = configService.get<boolean>("db.usePool", { infer: true }) ?? true;
this.setOptions({
writeUrl,
maxWriteConnections: poolMax,
e2e,
usePool,
type: "main",
});
}
}
setOptions(options: PrismaServiceOptions) {
this.options = options;
setOptions(options: PrismaServiceOptions): void {
const dbUrl = options.writeUrl;
const isE2E = options.e2e ?? false;
this.pool = new Pool({
connectionString: dbUrl,
max: isE2E ? 1 : options.maxWriteConnections ?? DB_MAX_POOL_CONNECTION,
idleTimeoutMillis: 300000,
});
const usePool = options.usePool ?? true;
const adapter = new PrismaPg(this.pool);
this.prisma = new PrismaClient({ adapter });
if (usePool) {
let maxWriteConnections = options.maxWriteConnections ?? DB_MAX_POOL_CONNECTION;
if (isE2E) {
maxWriteConnections = 1;
}
this.pool = new Pool({
connectionString: dbUrl,
max: maxWriteConnections,
idleTimeoutMillis: 300000,
});
const adapter = new PrismaPg(this.pool);
this.prisma = new PrismaClient({ adapter });
} else {
this.prisma = new PrismaClient({
datasourceUrl: dbUrl,
});
}
}
async onModuleInit() {
async onModuleInit(): Promise<void> {
if (!this.prisma) return;
try {
@@ -64,7 +77,7 @@ export class PrismaWriteService implements OnModuleInit, OnModuleDestroy {
}
}
async onModuleDestroy() {
async onModuleDestroy(): Promise<void> {
try {
if (this.prisma) await this.prisma.$disconnect();
if (this.pool) await this.pool.end();
@@ -1,7 +1,7 @@
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
import { Module } from "@nestjs/common";
import { ConfigModule, ConfigService } from "@nestjs/config";
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
@Module({
imports: [ConfigModule],
@@ -12,7 +12,7 @@ import { ConfigModule, ConfigService } from "@nestjs/config";
const service = new PrismaReadService();
service.setOptions({
readUrl: config.get<string>("db.readUrl", { infer: true }),
maxReadConnections: parseInt(config.get<number>("db.readPoolMax", { infer: true })),
maxReadConnections: parseInt(config.get<number>("db.readPoolMax", { infer: true }), 10),
e2e: config.get<boolean>("e2e", { infer: true }) ?? false,
type: "main",
});
@@ -26,7 +26,7 @@ import { ConfigModule, ConfigService } from "@nestjs/config";
const service = new PrismaWriteService();
service.setOptions({
writeUrl: config.get<string>("db.writeUrl", { infer: true }),
maxWriteConnections: parseInt(config.get<number>("db.writePoolMax", { infer: true })),
maxWriteConnections: parseInt(config.get<number>("db.writePoolMax", { infer: true }), 10),
e2e: config.get<boolean>("e2e", { infer: true }) ?? false,
type: "main",
});
@@ -1,5 +1,4 @@
import "dotenv/config";
import process from "node:process";
import { bootstrap } from "../bootstrap";
import { createNestApp } from "../main";
+14 -10
View File
@@ -78,16 +78,6 @@
"linter": { "enabled": false },
"formatter": { "enabled": false }
},
{
"includes": ["apps/api/v2/**/*.ts", "apps/api/v2/**/*.controller.ts"],
"linter": {
"rules": {
"style": {
"useImportType": "off"
}
}
}
},
{
"includes": ["**/*.tsx"],
"javascript": {
@@ -158,6 +148,20 @@
}
}
},
{
"includes": ["apps/api/v2/**/*.ts", "apps/api/v2/**/*.controller.ts"],
"linter": {
"rules": {
"style": {
"useImportType": "off",
"noProcessEnv": "off"
},
"correctness": {
"noProcessGlobal": "off"
}
}
}
},
{
"includes": ["**/*.test.ts", "**/*.test.tsx", "**/*.e2e-spec.ts"],
"linter": {