UNPKG

claude-flow-novice

Version:

Claude Flow Novice - Advanced orchestration platform for multi-agent AI workflows with CFN Loop architecture Includes CodeSearch (hybrid SQLite + pgvector), mem0/memgraph specialists, and all CFN skills.

390 lines (389 loc) 13.3 kB
/** * Connection Pool Manager - Phase 6 Performance Optimization * * Implements enterprise-grade connection pooling for PostgreSQL and Redis * to achieve 3-5x throughput improvement over direct connections. * * Performance targets: * - Direct connection: ~50ms overhead per query * - Pooled connection: ~5ms overhead per query * - 3-5x throughput improvement * * Features: * - PostgreSQL pg-pool with configurable max connections (default 20) * - Redis ioredis with connection pooling * - Connection health checks * - Graceful shutdown handlers * - Error recovery with reconnection logic * - Prometheus metrics exposure * - TypeScript strict mode (no any types) */ import { Pool } from 'pg'; import Redis from 'ioredis'; /** * PostgreSQL connection pool singleton */ export let pgPool = null; /** * Redis connection pool singleton */ export let redisPool = null; /** * Initialization state tracking */ let isInitialized = false; let isShuttingDown = false; let initializationPromise = null; /** * Metrics tracking */ let postgresCommandsProcessed = 0; let redisCommandsProcessed = 0; /** * Initialize connection pools * Thread-safe initialization with promise-based mutex */ export async function initializePools(config) { // Return if already initialized if (isInitialized && pgPool && redisPool) { console.log('Connection pools already initialized'); return; } // Wait for in-progress initialization if (initializationPromise) { console.log('Waiting for in-progress initialization...'); return initializationPromise; } // Atomic initialization with promise-based mutex initializationPromise = (async ()=>{ try { console.log('Initializing connection pools...'); // Validate configuration validatePoolConfig(config); // Initialize PostgreSQL pool await initializePostgresPool(config.postgres); // Initialize Redis pool await initializeRedisPool(config.redis); // Register shutdown handlers registerShutdownHandlers(); isInitialized = true; console.log('Connection pools initialized successfully'); } catch (error) { console.error('Failed to initialize connection pools:', error); // Cleanup on failure await shutdownPools(); throw error; } finally{ // Clear lock after initialization completes or fails initializationPromise = null; } })(); return initializationPromise; } /** * Validate pool configuration */ function validatePoolConfig(config) { // Validate PostgreSQL config if (config.postgres.max < 4) { throw new Error(`Invalid PostgreSQL max connections: ${config.postgres.max}. Minimum allowed is 4.`); } if (config.postgres.max > 100) { throw new Error(`Invalid PostgreSQL max connections: ${config.postgres.max}. Maximum allowed is 100.`); } if (config.postgres.idleTimeoutMillis < 1000) { throw new Error(`Invalid idle timeout: ${config.postgres.idleTimeoutMillis}ms. Minimum allowed is 1000ms.`); } if (config.postgres.connectionTimeoutMillis < 1000) { throw new Error(`Invalid connection timeout: ${config.postgres.connectionTimeoutMillis}ms. Minimum allowed is 1000ms.`); } // Validate Redis config if (config.redis.maxRetriesPerRequest < 0) { throw new Error(`Invalid max retries: ${config.redis.maxRetriesPerRequest}. Must be >= 0.`); } } /** * Initialize PostgreSQL connection pool */ async function initializePostgresPool(config) { const poolConfig = { host: config.host, port: config.port, database: config.database, user: config.user, password: config.password, max: config.max, idleTimeoutMillis: config.idleTimeoutMillis, connectionTimeoutMillis: config.connectionTimeoutMillis, // Additional pool settings for production allowExitOnIdle: false, application_name: 'cfn-connection-pool' }; pgPool = new Pool(poolConfig); // Handle pool errors with reconnection logic pgPool.on('error', (err, client)=>{ console.error('Unexpected error on idle PostgreSQL client:', err.message); // Error will trigger reconnection on next query }); // Connection event tracking pgPool.on('connect', (client)=>{ console.log('New PostgreSQL client connected to pool'); }); pgPool.on('acquire', (client)=>{ // Connection acquired from pool }); pgPool.on('remove', (client)=>{ console.log('PostgreSQL client removed from pool'); }); // Test connection with timeout try { const client = await pgPool.connect(); try { await client.query('SELECT 1 AS health_check'); console.log('PostgreSQL connection pool health check passed'); } finally{ client.release(); } } catch (err) { const error = err; console.error('Failed to initialize PostgreSQL connection pool:', error.message); throw new Error(`PostgreSQL pool initialization failed: ${error.message}`); } } /** * Initialize Redis connection pool */ async function initializeRedisPool(config) { const redisOptions = { host: config.host, port: config.port, password: config.password, maxRetriesPerRequest: config.maxRetriesPerRequest, // Connection pooling settings lazyConnect: false, keepAlive: 30000, connectTimeout: 10000, retryStrategy: (times)=>{ if (times > 10) { console.error('Redis connection retry limit exceeded'); return undefined; // Stop retrying } // Exponential backoff: 100ms, 200ms, 400ms, 800ms, ... const delay = Math.min(100 * Math.pow(2, times), 3000); console.log(`Redis reconnection attempt ${times} in ${delay}ms`); return delay; }, reconnectOnError: (err)=>{ // Reconnect on specific errors const targetErrors = [ 'READONLY', 'ECONNREFUSED', 'ETIMEDOUT' ]; return targetErrors.some((target)=>err.message.includes(target)); }, enableReadyCheck: true, enableOfflineQueue: true }; redisPool = new Redis(redisOptions); // Handle Redis events redisPool.on('error', (err)=>{ console.error('Redis connection error:', err.message); }); redisPool.on('ready', ()=>{ console.log('Redis connection ready'); }); redisPool.on('connect', ()=>{ console.log('Redis client connected'); }); redisPool.on('reconnecting', (delay)=>{ console.log(`Redis reconnecting in ${delay}ms`); }); redisPool.on('close', ()=>{ console.log('Redis connection closed'); }); // Test connection with timeout try { const result = await redisPool.ping(); if (result !== 'PONG') { throw new Error(`Unexpected ping response: ${result}`); } console.log('Redis connection pool health check passed'); } catch (err) { const error = err; console.error('Failed to initialize Redis connection pool:', error.message); throw new Error(`Redis pool initialization failed: ${error.message}`); } } /** * Register graceful shutdown handlers */ function registerShutdownHandlers() { const shutdownHandler = async (signal)=>{ if (isShuttingDown) { return; } console.log(`Received ${signal}, shutting down connection pools...`); await shutdownPools(); process.exit(0); }; process.on('SIGTERM', ()=>shutdownHandler('SIGTERM')); process.on('SIGINT', ()=>shutdownHandler('SIGINT')); // Handle uncaught errors process.on('uncaughtException', async (err)=>{ console.error('Uncaught exception:', err); await shutdownPools(); process.exit(1); }); process.on('unhandledRejection', async (reason)=>{ console.error('Unhandled rejection:', reason); await shutdownPools(); process.exit(1); }); } /** * Gracefully shutdown all connection pools */ export async function shutdownPools() { if (isShuttingDown) { console.log('Shutdown already in progress'); return; } console.log('Initiating graceful connection pool shutdown...'); isShuttingDown = true; const shutdownPromises = []; // Shutdown PostgreSQL pool if (pgPool) { shutdownPromises.push(pgPool.end().then(()=>{ console.log('PostgreSQL connection pool closed'); pgPool = null; }).catch((err)=>{ console.error('Error closing PostgreSQL pool:', err.message); })); } // Shutdown Redis pool if (redisPool) { shutdownPromises.push(redisPool.quit().then(()=>{ console.log('Redis connection pool closed'); redisPool = null; }).catch((err)=>{ console.error('Error closing Redis pool:', err.message); })); } await Promise.all(shutdownPromises); isInitialized = false; isShuttingDown = false; console.log('Connection pool shutdown complete'); } /** * Get pool metrics for Prometheus monitoring */ export function getPoolMetrics() { if (!pgPool || !redisPool) { throw new Error('Connection pools not initialized. Call initializePools first.'); } return { postgres: { totalConnections: pgPool.totalCount, idleConnections: pgPool.idleCount, waitingRequests: pgPool.waitingCount, activeConnections: pgPool.totalCount - pgPool.idleCount }, redis: { status: redisPool.status, connectedClients: 1, commandsProcessed: redisCommandsProcessed }, timestamp: new Date().toISOString() }; } /** * Execute PostgreSQL query with pooled connection * Automatically handles connection acquisition and release */ export async function executePostgresQuery(query, params) { if (!pgPool) { throw new Error('PostgreSQL pool not initialized. Call initializePools first.'); } if (isShuttingDown) { throw new Error('Connection pool is shutting down'); } let client = null; try { client = await pgPool.connect(); const result = await client.query(query, params); postgresCommandsProcessed++; return result.rows; } catch (err) { const error = err; console.error('PostgreSQL query error:', error.message); throw new Error(`PostgreSQL query failed: ${error.message}`); } finally{ if (client) { client.release(); } } } /** * Execute Redis command with pooled connection * Automatically handles reconnection on failure */ export async function executeRedisCommand(command, ...args) { if (!redisPool) { throw new Error('Redis pool not initialized. Call initializePools first.'); } if (isShuttingDown) { throw new Error('Connection pool is shutting down'); } try { // Type-safe command execution using unknown cast const redisClient = redisPool; const result = await redisClient[command](...args); redisCommandsProcessed++; return result; } catch (err) { const error = err; console.error(`Redis ${command} command error:`, error.message); throw new Error(`Redis command failed: ${error.message}`); } } /** * Health check for all connection pools */ export async function healthCheck() { const health = { postgres: false, redis: false, details: { postgres: 'not initialized', redis: 'not initialized' } }; // Check PostgreSQL if (pgPool) { try { await executePostgresQuery('SELECT 1 AS health_check'); health.postgres = true; health.details.postgres = 'healthy'; } catch (err) { const error = err; health.details.postgres = error.message; } } // Check Redis if (redisPool) { try { await redisPool.ping(); health.redis = true; health.details.redis = 'healthy'; } catch (err) { const error = err; health.details.redis = error.message; } } return health; } /** * Get PostgreSQL pool instance (for advanced use cases) * Most code should use executePostgresQuery instead */ export function getPostgresPool() { if (!pgPool) { throw new Error('PostgreSQL pool not initialized. Call initializePools first.'); } return pgPool; } /** * Get Redis pool instance (for advanced use cases) * Most code should use executeRedisCommand instead */ export function getRedisPool() { if (!redisPool) { throw new Error('Redis pool not initialized. Call initializePools first.'); } return redisPool; } //# sourceMappingURL=connection-pool.js.map