UNPKG

@hotmeshio/hotmesh

Version:

Permanent-Memory Workflows & AI Agents

215 lines (214 loc) 8.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.PostgresConnection = void 0; const __1 = require(".."); const utils_1 = require("../../../modules/utils"); class PostgresConnection extends __1.AbstractConnection { constructor() { super(...arguments); this.defaultOptions = { host: 'postgres', port: 5432, user: 'postgres', password: 'password', database: 'hotmesh', max: 20, idleTimeoutMillis: 30000, }; } /** * Get comprehensive connection statistics for monitoring taskQueue pooling effectiveness */ static getConnectionStats() { const taskQueueDetails = Array.from(this.taskQueueConnections.entries()).map(([key, connection]) => ({ key, connectionId: connection.getConnectionId() || 'unknown', reusedCount: connection.reusedCount || 0, })); return { totalPoolClients: this.poolClientInstances.size, totalConnections: this.connectionInstances.size, taskQueueConnections: this.taskQueueConnections.size, taskQueueDetails, }; } /** * Log current connection statistics - useful for debugging connection pooling */ static logConnectionStats(logger) { const stats = this.getConnectionStats(); const message = `PostgreSQL Connection Stats: ${stats.totalConnections} total connections, ${stats.taskQueueConnections} taskQueue pools, ${stats.totalPoolClients} pool clients`; if (logger) { logger.info('postgres-connection-stats', { ...stats, message, }); } else { console.log(message, stats); } } /** * Check taskQueue pooling effectiveness - returns metrics about connection reuse */ static getPoolingEffectiveness() { const stats = this.getConnectionStats(); const totalReuses = stats.taskQueueDetails.reduce((sum, detail) => sum + detail.reusedCount, 0); const averageReusesPerPool = stats.taskQueueConnections > 0 ? totalReuses / stats.taskQueueConnections : 0; const poolingEfficiency = stats.totalConnections > 0 ? stats.taskQueueConnections / stats.totalConnections * 100 : 0; return { totalConnections: stats.totalConnections, taskQueuePools: stats.taskQueueConnections, totalReuses, averageReusesPerPool: Math.round(averageReusesPerPool * 100) / 100, poolingEfficiency: Math.round(poolingEfficiency * 100) / 100, }; } async createConnection(clientConstructor, options, config = {}) { try { let connection; if (config.provider === 'postgres.poolclient' || PostgresConnection.isPoolClient(clientConstructor)) { // It's a PoolClient connection = clientConstructor; if (config.connect) { const client = await clientConstructor.connect(); //register the connection singularly to be 'released' later PostgresConnection.poolClientInstances.add(client); this.poolClientInstance = client; } } else { // It's a Client connection = new clientConstructor(options); await connection.connect(); await connection.query('SELECT 1'); } //register the connection statically to be 'ended' later PostgresConnection.connectionInstances.add(connection); return connection; } catch (error) { PostgresConnection.logger.error(`postgres-provider-connection-failed`, { host: options.host ?? 'unknown', database: options.database ?? 'unknown', port: options.port ?? 'unknown', error, }); throw new Error(`postgres-provider-connection-failed: ${error.message}`); } } getClient() { if (!this.connection) { throw new Error('Postgres client is not connected'); } return this.poolClientInstance || this.connection; } /** * Get the connection ID for monitoring purposes */ getConnectionId() { return this.id; } static async disconnectAll() { //log stats //this.logConnectionStats(); if (!this.disconnecting) { this.disconnecting = true; await this.disconnectPoolClients(); await this.disconnectConnections(); this.taskQueueConnections.clear(); this.disconnecting = false; } } static async disconnectPoolClients() { Array.from(this.poolClientInstances.values()).map((instance) => { instance.release(); }); this.poolClientInstances.clear(); } static async disconnectConnections() { Array.from(this.connectionInstances.values()).map((instance) => { instance.end(); }); this.connectionInstances.clear(); } async closeConnection(connection) { //no-op (handled by disconnectAll) } static isPoolClient(client) { return !(isNaN(client?.totalCount) && isNaN(client?.idleCount)); } /** * Creates a taskQueue-based connection key for connection pooling. * This allows multiple providers (store, sub, stream) to reuse the same connection * when they share the same taskQueue and database configuration. */ static createTaskQueueConnectionKey(taskQueue, options, provider) { const configHash = (0, utils_1.hashOptions)(options); const providerType = provider?.split('.')[0] || 'postgres'; return `${providerType}:${taskQueue || 'default'}:${configHash}`; } /** * Gets or creates a PostgreSQL connection based on taskQueue and database configuration. * If a connection already exists for the same taskQueue + config, it will be reused. * This optimization reduces connection overhead for PostgreSQL providers. */ static async getOrCreateTaskQueueConnection(id, taskQueue, clientConstructor, options, config = {}) { // Only use taskQueue pooling for PostgreSQL providers if (!taskQueue || !config.provider?.startsWith('postgres')) { return await this.connect(id, clientConstructor, options, config); } const connectionKey = this.createTaskQueueConnectionKey(taskQueue, options, config.provider); // Check if we already have a connection for this taskQueue + config if (this.taskQueueConnections.has(connectionKey)) { const existingConnection = this.taskQueueConnections.get(connectionKey); // Track reuse count for monitoring existingConnection.reusedCount = (existingConnection.reusedCount || 0) + 1; this.logger.debug('postgres-connection-reused', { connectionKey, taskQueue, originalId: existingConnection.id, newId: id, reuseCount: existingConnection.reusedCount, }); return existingConnection; } // Create new connection and cache it for the taskQueue const newConnection = await this.connect(id, clientConstructor, options, config); // Initialize reuse tracking newConnection.reusedCount = 0; this.taskQueueConnections.set(connectionKey, newConnection); this.logger.debug('postgres-connection-created-for-taskqueue', { connectionKey, taskQueue, connectionId: id, }); return newConnection; } static async getTransactionClient(transactionClient) { let client; let type; if (PostgresConnection.isPoolClient(transactionClient)) { type = 'poolclient'; client = await transactionClient.connect(); } else { type = 'client'; client = transactionClient; } return [type, client]; } } exports.PostgresConnection = PostgresConnection; //statically track all clients (//call 'release') PostgresConnection.poolClientInstances = new Set(); //statically track all connections (//call 'end') PostgresConnection.connectionInstances = new Set(); //track connections by taskQueue + database config for reuse PostgresConnection.taskQueueConnections = new Map();