AI Prompt: Forma3D.Connect — Phase 5o: Database Connection Pool Configuration¶
Purpose: This prompt instructs an AI to configure Prisma database connection pooling for optimal performance
Estimated Effort: 4-6 hours
Prerequisites: Phase 5n completed (Rate Limiting)
Output: Configured connection pool with monitoring and environment-specific tuning
Status: 🟡 PENDING
🎯 Mission¶
You are continuing development of Forma3D.Connect, building on the Phase 5n foundation. Your task is to implement Phase 5o: Database Connection Pool Configuration — specifically addressing TD-013 (Database Connection Pool Configuration) from the technical debt register.
Why This Matters:
Prisma connection pool using defaults can cause:
- Connection Exhaustion: Too few connections under load
- Resource Waste: Too many idle connections
- Timeout Errors: Default timeouts may not suit workload
- Hard to Debug: No visibility into pool state
Phase 5o delivers:
- Explicit connection pool configuration
- Environment-specific pool sizes
- Connection timeout configuration
- Pool monitoring and metrics
📋 Context: Technical Debt Item¶
TD-013: Database Connection Pool Configuration¶
| Attribute | Value |
|---|---|
| Type | Infrastructure Debt |
| Priority | Medium |
| Location | apps/api/src/database/prisma.service.ts |
| Interest Rate | Medium |
| Principal (Effort) | 4-6 hours |
Current Problem¶
Prisma connection pool uses defaults with no explicit configuration for: - Pool size - Connection timeout - Idle timeout
🛠️ Implementation Phases¶
Phase 1: Add Connection Pool Configuration Variables (1 hour)¶
Priority: Critical | Impact: High | Dependencies: None
1. Update Configuration Schema¶
Update apps/api/src/config/configuration.ts:
// === Database Connection Pool ===
@Transform(({ value }) => parseInt(value, 10))
@IsNumber()
@Min(1)
@Max(100)
DATABASE_POOL_SIZE: number = 10;
@Transform(({ value }) => parseInt(value, 10))
@IsNumber()
@Min(1000)
DATABASE_CONNECT_TIMEOUT_MS: number = 5000;
@Transform(({ value }) => parseInt(value, 10))
@IsNumber()
@Min(1000)
DATABASE_POOL_TIMEOUT_MS: number = 10000;
@Transform(({ value }) => parseInt(value, 10))
@IsNumber()
@Min(0)
DATABASE_IDLE_TIMEOUT_MS: number = 300000; // 5 minutes
2. Update AppConfigService¶
Add to apps/api/src/config/config.service.ts:
export interface DatabaseConfig {
url: string;
poolSize: number;
connectTimeoutMs: number;
poolTimeoutMs: number;
idleTimeoutMs: number;
}
// In AppConfigService class:
get database(): DatabaseConfig {
return {
url: this.configService.getOrThrow<string>('DATABASE_URL'),
poolSize: this.configService.get<number>('DATABASE_POOL_SIZE', 10),
connectTimeoutMs: this.configService.get<number>('DATABASE_CONNECT_TIMEOUT_MS', 5000),
poolTimeoutMs: this.configService.get<number>('DATABASE_POOL_TIMEOUT_MS', 10000),
idleTimeoutMs: this.configService.get<number>('DATABASE_IDLE_TIMEOUT_MS', 300000),
};
}
Phase 2: Configure Prisma with Pool Settings (2 hours)¶
Priority: High | Impact: High | Dependencies: Phase 1
1. Update DATABASE_URL with Pool Parameters¶
Connection pool settings are passed via URL query parameters in Prisma:
Before:
DATABASE_URL=postgresql://user:password@localhost:5432/forma3d_connect
After:
DATABASE_URL=postgresql://user:password@localhost:5432/forma3d_connect?connection_limit=10&connect_timeout=5&pool_timeout=10
However, for dynamic configuration, we'll build the URL programmatically.
2. Create Enhanced Prisma Service¶
Update apps/api/src/database/prisma.service.ts:
import { Injectable, OnModuleInit, OnModuleDestroy, Logger } from '@nestjs/common';
import { PrismaClient, Prisma } from '@prisma/client';
import { AppConfigService } from '../config/config.service';
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
private readonly logger = new Logger(PrismaService.name);
constructor(private readonly appConfig: AppConfigService) {
const dbConfig = appConfig.database;
// Build connection URL with pool parameters
const url = new URL(dbConfig.url);
url.searchParams.set('connection_limit', dbConfig.poolSize.toString());
url.searchParams.set('connect_timeout', Math.floor(dbConfig.connectTimeoutMs / 1000).toString());
url.searchParams.set('pool_timeout', Math.floor(dbConfig.poolTimeoutMs / 1000).toString());
super({
datasources: {
db: {
url: url.toString(),
},
},
log: appConfig.isProduction
? ['error', 'warn']
: ['query', 'info', 'warn', 'error'],
});
this.logger.log(`Database pool configured: size=${dbConfig.poolSize}, connectTimeout=${dbConfig.connectTimeoutMs}ms`);
}
async onModuleInit(): Promise<void> {
this.logger.log('Connecting to database...');
await this.$connect();
this.logger.log('Database connected successfully');
}
async onModuleDestroy(): Promise<void> {
this.logger.log('Disconnecting from database...');
await this.$disconnect();
this.logger.log('Database disconnected');
}
/**
* Health check for database connectivity
*/
async isHealthy(): Promise<boolean> {
try {
await this.$queryRaw`SELECT 1`;
return true;
} catch (error) {
this.logger.error('Database health check failed', error);
return false;
}
}
}
3. Alternative: Using Prisma Data Proxy (for Serverless)¶
If using serverless environments, consider Prisma Data Proxy:
// For serverless deployments
const prisma = new PrismaClient({
datasources: {
db: {
url: process.env.DATABASE_URL,
},
},
});
Phase 3: Add Pool Monitoring (1 hour)¶
Priority: Medium | Impact: Medium | Dependencies: Phase 2
1. Create Pool Metrics Service¶
Create apps/api/src/database/prisma-metrics.service.ts:
import { Injectable, Logger } from '@nestjs/common';
import { PrismaService } from './prisma.service';
import { Interval } from '@nestjs/schedule';
interface PoolMetrics {
timestamp: Date;
activeConnections: number;
idleConnections: number;
waitingRequests: number;
}
@Injectable()
export class PrismaMetricsService {
private readonly logger = new Logger(PrismaMetricsService.name);
private metrics: PoolMetrics[] = [];
private readonly maxMetricsHistory = 60; // Keep last 60 samples
constructor(private readonly prisma: PrismaService) {}
/**
* Collect pool metrics every 10 seconds
* Note: Prisma doesn't expose pool metrics directly,
* so we measure query latency as a proxy
*/
@Interval(10000)
async collectMetrics(): Promise<void> {
const start = Date.now();
try {
await this.prisma.$queryRaw`SELECT 1`;
const latency = Date.now() - start;
// High latency might indicate pool exhaustion
if (latency > 100) {
this.logger.warn(`Database query latency: ${latency}ms`);
}
// Store metric
this.metrics.push({
timestamp: new Date(),
activeConnections: 0, // Prisma doesn't expose this
idleConnections: 0,
waitingRequests: 0,
});
// Trim old metrics
if (this.metrics.length > this.maxMetricsHistory) {
this.metrics.shift();
}
} catch (error) {
this.logger.error('Failed to collect database metrics', error);
}
}
getMetrics(): PoolMetrics[] {
return [...this.metrics];
}
getLatestMetric(): PoolMetrics | null {
return this.metrics[this.metrics.length - 1] ?? null;
}
}
2. Add Metrics to Health Check¶
Update apps/api/src/health/health.controller.ts:
import { PrismaService } from '../database/prisma.service';
@Controller('health')
export class HealthController {
constructor(private readonly prisma: PrismaService) {}
@Get()
async check() {
const dbHealthy = await this.prisma.isHealthy();
return {
status: dbHealthy ? 'ok' : 'degraded',
timestamp: new Date().toISOString(),
checks: {
database: dbHealthy ? 'ok' : 'error',
},
};
}
@Get('ready')
async readiness() {
const dbHealthy = await this.prisma.isHealthy();
if (!dbHealthy) {
throw new ServiceUnavailableException('Database not available');
}
return { status: 'ready' };
}
}
Phase 4: Environment-Specific Configuration (1 hour)¶
Priority: Medium | Impact: Medium | Dependencies: Phase 1
1. Development Configuration¶
Create apps/api/.env.development:
# Development - smaller pool, verbose logging
DATABASE_POOL_SIZE=5
DATABASE_CONNECT_TIMEOUT_MS=10000
DATABASE_POOL_TIMEOUT_MS=30000
DATABASE_IDLE_TIMEOUT_MS=60000
2. Production Configuration¶
Document in apps/api/.env.example:
# === Database Connection Pool ===
# Adjust based on your database server's max_connections
# Formula: pool_size = (max_connections - reserved) / num_instances
DATABASE_POOL_SIZE=20
DATABASE_CONNECT_TIMEOUT_MS=5000
DATABASE_POOL_TIMEOUT_MS=10000
DATABASE_IDLE_TIMEOUT_MS=300000
3. Add Pool Sizing Guidance¶
Add comments to configuration:
/**
* Pool Size Guidelines:
* - Development: 5 connections
* - Staging: 10 connections
* - Production: 20-50 connections (depends on database max_connections)
*
* Formula: pool_size = (db_max_connections - 10) / number_of_app_instances
*
* Example: PostgreSQL with max_connections=100, 3 app instances
* pool_size = (100 - 10) / 3 = 30 per instance
*/
📁 Files to Create/Modify¶
Modified Files¶
apps/api/src/config/configuration.ts
apps/api/src/config/config.service.ts
apps/api/src/database/prisma.service.ts
apps/api/src/health/health.controller.ts
apps/api/.env.example
New Files¶
apps/api/src/database/prisma-metrics.service.ts
apps/api/.env.development (optional)
✅ Validation Checklist¶
- Pool configuration variables added
- PrismaService uses configured pool settings
- Health check includes database status
- Pool metrics collection implemented
- Environment-specific configs documented
-
pnpm nx build apipasses -
pnpm nx test apipasses - Application starts with configured pool
Final Verification¶
# Build passes
pnpm nx build api
# Tests pass
pnpm nx test api
# Start and check logs for pool configuration
pnpm nx serve api
# Look for: "Database pool configured: size=10, connectTimeout=5000ms"
# Health check works
curl http://localhost:3000/health
# Returns: {"status":"ok","checks":{"database":"ok"}}
📝 .env.example Addition¶
# === Database Connection Pool ===
# Pool size should be tuned based on:
# - Database max_connections setting
# - Number of application instances
# - Expected concurrent requests
DATABASE_POOL_SIZE=10
DATABASE_CONNECT_TIMEOUT_MS=5000
DATABASE_POOL_TIMEOUT_MS=10000
DATABASE_IDLE_TIMEOUT_MS=300000
END OF PROMPT
This prompt resolves TD-013 from the technical debt register by configuring the Prisma database connection pool.