Open1
Prismaでconnection poolとかconnection limitとかを対策したい

new PrismaClient()をするとコネクションが無限に作られてしまう。
デフォルトのコネクションプール・接続タイムアウト設定だと、コンテナの特殊な動作環境では適切に動作しないことがある。特にキューワーカーをprismaで作る際に、大量のコネクションがキューごとに作られてしまうなど問題があり、主にワーカーノード向けの実装。
/**
* Enhanced Prisma Client Service with Connection Pool Management
*
* This service provides:
* - Connection pool configuration via environment variables
* - Graceful shutdown handling
* - Connection retry logic
* - Health check functionality
* - Proper cleanup and error handling
*
* Environment Variables:
* - DATABASE_CONNECTION_POOL_SIZE: Max connections in pool (default: 10)
* - DATABASE_CONNECTION_TIMEOUT: Connection timeout in seconds (default: 30)
* - DATABASE_POOL_TIMEOUT: Pool timeout in seconds (default: 2)
*/
import { PrismaClient } from '@prisma/client';
let prisma: PrismaClient | null = null;
export const getPrismaClient = (): PrismaClient => {
if (!prisma) {
// Build connection URL with pool parameters
const databaseUrl = process.env.DATABASE_URL;
const connectionPoolSize = parseInt(
process.env.DATABASE_CONNECTION_POOL_SIZE || '10',
10,
);
const connectionTimeout = parseInt(
process.env.DATABASE_CONNECTION_TIMEOUT || '30',
10,
);
const poolTimeout = parseInt(process.env.DATABASE_POOL_TIMEOUT || '2', 10);
// Add connection pool parameters to URL if not already present
let enhancedUrl = databaseUrl;
if (databaseUrl && !databaseUrl.includes('connection_limit')) {
const separator = databaseUrl.includes('?') ? '&' : '?';
enhancedUrl = `${databaseUrl}${separator}connection_limit=${connectionPoolSize}&pool_timeout=${poolTimeout}&connect_timeout=${connectionTimeout}`;
}
prisma = new PrismaClient({
// Connection pool configuration
datasources: {
db: {
url: enhancedUrl,
},
},
// Logging configuration
log:
process.env.NODE_ENV === 'development'
? ['query', 'info', 'warn', 'error']
: ['warn', 'error'],
// Error formatting
errorFormat: 'pretty',
});
// Test connection on initialization
prisma.$connect().catch((error) => {
console.error('Failed to connect to database on initialization:', error);
});
// Graceful shutdown handling
const gracefulShutdown = async () => {
if (prisma) {
console.log('Disconnecting Prisma Client...');
try {
await prisma.$disconnect();
prisma = null;
console.log('Prisma Client disconnected');
} catch (error) {
console.error('Error during Prisma Client disconnect:', error);
prisma = null;
}
}
};
// Register shutdown handlers (only once)
if (!process.listenerCount('SIGINT')) {
process.on('SIGINT', gracefulShutdown);
}
if (!process.listenerCount('SIGTERM')) {
process.on('SIGTERM', gracefulShutdown);
}
if (!process.listenerCount('beforeExit')) {
process.on('beforeExit', gracefulShutdown);
}
}
return prisma;
};
// Function to manually disconnect (useful for testing)
export const disconnectPrismaClient = async (): Promise<void> => {
if (prisma) {
await prisma.$disconnect();
prisma = null;
}
};
// Function to execute database operations with retry logic
export const withRetry = async <T>(
operation: () => Promise<T>,
maxRetries: number = 3,
delay: number = 1000,
): Promise<T> => {
let lastError: Error | undefined = undefined;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
lastError = error as Error;
console.warn(
`Database operation failed (attempt ${attempt}/${maxRetries}):`,
error,
);
if (attempt === maxRetries) {
break;
}
// Exponential backoff
const waitTime = delay * Math.pow(2, attempt - 1);
await new Promise((resolve) => setTimeout(resolve, waitTime));
}
}
throw (
lastError ?? new Error('Database operation failed after maximum retries')
);
};
// Function to check connection health with retry
export const checkDatabaseConnection = async (): Promise<boolean> => {
try {
await withRetry(
async () => {
const client = getPrismaClient();
await client.$queryRaw`SELECT 1`;
},
2,
500,
);
return true;
} catch (error) {
console.error('Database connection check failed after retries:', error);
return false;
}
};
// Function to reset connection (useful when connection is stale)
export const resetPrismaConnection = async (): Promise<void> => {
if (prisma) {
try {
await prisma.$disconnect();
} catch (error) {
console.warn('Error disconnecting stale connection:', error);
}
prisma = null;
}
// Next call to getPrismaClient() will create a new connection
};
使い方はこれからgetPrismaClient()するだけ。SQLのログもでる。