added new di with connection pool and rebuild of the database/cache services
This commit is contained in:
parent
be6afef832
commit
09d907a10c
26 changed files with 4844 additions and 205 deletions
|
|
@ -8,6 +8,9 @@ import type {
|
|||
PostgreSQLConnectionOptions,
|
||||
QueryResult,
|
||||
TransactionCallback,
|
||||
PoolMetrics,
|
||||
ConnectionEvents,
|
||||
DynamicPoolConfig,
|
||||
} from './types';
|
||||
|
||||
/**
|
||||
|
|
@ -24,8 +27,12 @@ export class PostgreSQLClient {
|
|||
private readonly healthMonitor: PostgreSQLHealthMonitor;
|
||||
private readonly transactionManager: PostgreSQLTransactionManager;
|
||||
private isConnected = false;
|
||||
private readonly metrics: PoolMetrics;
|
||||
private readonly events?: ConnectionEvents;
|
||||
private dynamicPoolConfig?: DynamicPoolConfig;
|
||||
private poolMonitorInterval?: NodeJS.Timeout;
|
||||
|
||||
constructor(config: PostgreSQLClientConfig, options?: PostgreSQLConnectionOptions) {
|
||||
constructor(config: PostgreSQLClientConfig, options?: PostgreSQLConnectionOptions, events?: ConnectionEvents) {
|
||||
this.config = config;
|
||||
this.options = {
|
||||
retryAttempts: 3,
|
||||
|
|
@ -33,10 +40,20 @@ export class PostgreSQLClient {
|
|||
healthCheckInterval: 30000,
|
||||
...options,
|
||||
};
|
||||
this.events = events;
|
||||
|
||||
this.logger = getLogger('postgres-client');
|
||||
this.healthMonitor = new PostgreSQLHealthMonitor(this);
|
||||
this.transactionManager = new PostgreSQLTransactionManager(this);
|
||||
|
||||
this.metrics = {
|
||||
totalConnections: 0,
|
||||
activeConnections: 0,
|
||||
idleConnections: 0,
|
||||
waitingRequests: 0,
|
||||
errors: 0,
|
||||
created: new Date(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -63,17 +80,51 @@ export class PostgreSQLClient {
|
|||
client.release();
|
||||
|
||||
this.isConnected = true;
|
||||
this.logger.info('Successfully connected to PostgreSQL');
|
||||
|
||||
// Update metrics
|
||||
const poolConfig = this.config.poolSettings;
|
||||
this.metrics.totalConnections = poolConfig?.max || 10;
|
||||
this.metrics.idleConnections = poolConfig?.min || 2;
|
||||
|
||||
// Fire connection event
|
||||
if (this.events?.onConnect) {
|
||||
await Promise.resolve(this.events.onConnect());
|
||||
}
|
||||
|
||||
// Fire pool created event
|
||||
if (this.events?.onPoolCreated) {
|
||||
await Promise.resolve(this.events.onPoolCreated());
|
||||
}
|
||||
|
||||
this.logger.info('Successfully connected to PostgreSQL', {
|
||||
poolSize: this.metrics.totalConnections,
|
||||
});
|
||||
|
||||
// Start health monitoring
|
||||
this.healthMonitor.start();
|
||||
|
||||
// Setup error handlers
|
||||
this.setupErrorHandlers();
|
||||
|
||||
// Setup pool event listeners for metrics
|
||||
this.setupPoolMetrics();
|
||||
|
||||
// Start dynamic pool monitoring if enabled
|
||||
if (this.dynamicPoolConfig?.enabled) {
|
||||
this.startPoolMonitoring();
|
||||
}
|
||||
|
||||
return;
|
||||
} catch (error) {
|
||||
lastError = error as Error;
|
||||
this.metrics.errors++;
|
||||
this.metrics.lastError = lastError.message;
|
||||
|
||||
// Fire error event
|
||||
if (this.events?.onError) {
|
||||
await Promise.resolve(this.events.onError(lastError));
|
||||
}
|
||||
|
||||
this.logger.error(`PostgreSQL connection attempt ${attempt} failed:`, error);
|
||||
|
||||
if (this.pool) {
|
||||
|
|
@ -101,10 +152,22 @@ export class PostgreSQLClient {
|
|||
}
|
||||
|
||||
try {
|
||||
// Stop pool monitoring
|
||||
if (this.poolMonitorInterval) {
|
||||
clearInterval(this.poolMonitorInterval);
|
||||
this.poolMonitorInterval = undefined;
|
||||
}
|
||||
|
||||
this.healthMonitor.stop();
|
||||
await this.pool.end();
|
||||
this.isConnected = false;
|
||||
this.pool = null;
|
||||
|
||||
// Fire disconnect event
|
||||
if (this.events?.onDisconnect) {
|
||||
await Promise.resolve(this.events.onDisconnect());
|
||||
}
|
||||
|
||||
this.logger.info('Disconnected from PostgreSQL');
|
||||
} catch (error) {
|
||||
this.logger.error('Error disconnecting from PostgreSQL:', error);
|
||||
|
|
@ -411,4 +474,132 @@ export class PostgreSQLClient {
|
|||
private delay(ms: number): Promise<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current pool metrics
|
||||
*/
|
||||
getPoolMetrics(): PoolMetrics {
|
||||
// Update last used timestamp
|
||||
this.metrics.lastUsed = new Date();
|
||||
|
||||
// Update metrics from pool if available
|
||||
if (this.pool) {
|
||||
this.metrics.totalConnections = this.pool.totalCount;
|
||||
this.metrics.idleConnections = this.pool.idleCount;
|
||||
this.metrics.waitingRequests = this.pool.waitingCount;
|
||||
this.metrics.activeConnections = this.metrics.totalConnections - this.metrics.idleConnections;
|
||||
}
|
||||
|
||||
return { ...this.metrics };
|
||||
}
|
||||
|
||||
/**
|
||||
* Set dynamic pool configuration
|
||||
*/
|
||||
setDynamicPoolConfig(config: DynamicPoolConfig): void {
|
||||
this.dynamicPoolConfig = config;
|
||||
|
||||
if (config.enabled && this.isConnected && !this.poolMonitorInterval) {
|
||||
this.startPoolMonitoring();
|
||||
} else if (!config.enabled && this.poolMonitorInterval) {
|
||||
clearInterval(this.poolMonitorInterval);
|
||||
this.poolMonitorInterval = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start monitoring pool and adjust size dynamically
|
||||
*/
|
||||
private startPoolMonitoring(): void {
|
||||
if (!this.dynamicPoolConfig || this.poolMonitorInterval) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.poolMonitorInterval = setInterval(() => {
|
||||
this.evaluatePoolSize();
|
||||
}, this.dynamicPoolConfig.evaluationInterval);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup pool event listeners for metrics
|
||||
*/
|
||||
private setupPoolMetrics(): void {
|
||||
if (!this.pool) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Track when connections are acquired
|
||||
this.pool.on('acquire', () => {
|
||||
this.metrics.activeConnections++;
|
||||
this.metrics.idleConnections--;
|
||||
});
|
||||
|
||||
// Track when connections are released
|
||||
this.pool.on('release', () => {
|
||||
this.metrics.activeConnections--;
|
||||
this.metrics.idleConnections++;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate and adjust pool size based on usage
|
||||
*/
|
||||
private async evaluatePoolSize(): Promise<void> {
|
||||
if (!this.dynamicPoolConfig || !this.pool) {
|
||||
return;
|
||||
}
|
||||
|
||||
const metrics = this.getPoolMetrics();
|
||||
const { minSize, maxSize, scaleUpThreshold, scaleDownThreshold } = this.dynamicPoolConfig;
|
||||
const currentSize = metrics.totalConnections;
|
||||
const utilization = currentSize > 0 ? ((metrics.activeConnections / currentSize) * 100) : 0;
|
||||
|
||||
this.logger.debug('Pool utilization', {
|
||||
utilization: `${utilization.toFixed(1)}%`,
|
||||
active: metrics.activeConnections,
|
||||
total: currentSize,
|
||||
waiting: metrics.waitingRequests,
|
||||
});
|
||||
|
||||
// Scale up if utilization is high or there are waiting requests
|
||||
if ((utilization > scaleUpThreshold || metrics.waitingRequests > 0) && currentSize < maxSize) {
|
||||
const newSize = Math.min(currentSize + this.dynamicPoolConfig.scaleUpIncrement, maxSize);
|
||||
this.logger.info('Would scale up connection pool', { from: currentSize, to: newSize, utilization });
|
||||
// Note: pg module doesn't support dynamic resizing, would need reconnection
|
||||
}
|
||||
// Scale down if utilization is low
|
||||
else if (utilization < scaleDownThreshold && currentSize > minSize) {
|
||||
const newSize = Math.max(currentSize - this.dynamicPoolConfig.scaleDownIncrement, minSize);
|
||||
this.logger.info('Would scale down connection pool', { from: currentSize, to: newSize, utilization });
|
||||
// Note: pg module doesn't support dynamic resizing, would need reconnection
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable pool warmup on connect
|
||||
*/
|
||||
async warmupPool(): Promise<void> {
|
||||
if (!this.pool || !this.isConnected) {
|
||||
throw new Error('Client not connected');
|
||||
}
|
||||
|
||||
const minSize = this.config.poolSettings?.min || 2;
|
||||
const promises: Promise<void>[] = [];
|
||||
|
||||
// Create minimum connections by running parallel queries
|
||||
for (let i = 0; i < minSize; i++) {
|
||||
promises.push(
|
||||
this.pool.query('SELECT 1')
|
||||
.then(() => {
|
||||
this.logger.debug(`Warmed up connection ${i + 1}/${minSize}`);
|
||||
})
|
||||
.catch(error => {
|
||||
this.logger.warn(`Failed to warm up connection ${i + 1}`, { error });
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
await Promise.allSettled(promises);
|
||||
this.logger.info('Connection pool warmup complete', { connections: minSize });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
import { PostgreSQLClient } from './client';
|
||||
import type { PostgreSQLClientConfig, PostgreSQLConnectionOptions } from './types';
|
||||
import type { PostgreSQLClientConfig, PostgreSQLConnectionOptions, ConnectionEvents } from './types';
|
||||
|
||||
/**
|
||||
* Factory function to create a PostgreSQL client instance
|
||||
*/
|
||||
export function createPostgreSQLClient(
|
||||
config: PostgreSQLClientConfig,
|
||||
options?: PostgreSQLConnectionOptions
|
||||
options?: PostgreSQLConnectionOptions,
|
||||
events?: ConnectionEvents
|
||||
): PostgreSQLClient {
|
||||
return new PostgreSQLClient(config, options);
|
||||
return new PostgreSQLClient(config, options, events);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -16,9 +17,10 @@ export function createPostgreSQLClient(
|
|||
*/
|
||||
export async function createAndConnectPostgreSQLClient(
|
||||
config: PostgreSQLClientConfig,
|
||||
options?: PostgreSQLConnectionOptions
|
||||
options?: PostgreSQLConnectionOptions,
|
||||
events?: ConnectionEvents
|
||||
): Promise<PostgreSQLClient> {
|
||||
const client = createPostgreSQLClient(config, options);
|
||||
const client = createPostgreSQLClient(config, options, events);
|
||||
await client.connect();
|
||||
return client;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,9 @@ export type {
|
|||
Strategy,
|
||||
RiskLimit,
|
||||
AuditLog,
|
||||
PoolMetrics,
|
||||
ConnectionEvents,
|
||||
DynamicPoolConfig,
|
||||
} from './types';
|
||||
|
||||
// Factory functions
|
||||
|
|
@ -36,9 +39,4 @@ export {
|
|||
createAndConnectPostgreSQLClient,
|
||||
} from './factory';
|
||||
|
||||
// Singleton instance
|
||||
export {
|
||||
getPostgreSQLClient,
|
||||
connectPostgreSQL,
|
||||
disconnectPostgreSQL,
|
||||
} from './singleton';
|
||||
// Singleton pattern removed - use factory functions instead
|
||||
|
|
|
|||
|
|
@ -1,50 +0,0 @@
|
|||
import { PostgreSQLClient } from './client';
|
||||
import type { PostgreSQLClientConfig } from './types';
|
||||
|
||||
/**
|
||||
* Singleton PostgreSQL client instance
|
||||
* Provides global access to a single PostgreSQL connection pool
|
||||
*/
|
||||
let instance: PostgreSQLClient | null = null;
|
||||
|
||||
/**
|
||||
* Initialize the singleton PostgreSQL client
|
||||
*/
|
||||
export async function connectPostgreSQL(config?: PostgreSQLClientConfig): Promise<PostgreSQLClient> {
|
||||
if (!instance) {
|
||||
if (!config) {
|
||||
throw new Error('PostgreSQL client not initialized. Call connectPostgreSQL(config) first.');
|
||||
}
|
||||
instance = new PostgreSQLClient(config);
|
||||
await instance.connect();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the singleton PostgreSQL client instance
|
||||
* @throws Error if not initialized
|
||||
*/
|
||||
export function getPostgreSQLClient(): PostgreSQLClient {
|
||||
if (!instance) {
|
||||
throw new Error('PostgreSQL client not initialized. Call connectPostgreSQL(config) first.');
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the PostgreSQL client is initialized
|
||||
*/
|
||||
export function isInitialized(): boolean {
|
||||
return instance !== null && instance.connected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect and reset the singleton instance
|
||||
*/
|
||||
export async function disconnectPostgreSQL(): Promise<void> {
|
||||
if (instance) {
|
||||
await instance.disconnect();
|
||||
instance = null;
|
||||
}
|
||||
}
|
||||
|
|
@ -36,6 +36,36 @@ export interface PostgreSQLConnectionOptions {
|
|||
healthCheckInterval?: number;
|
||||
}
|
||||
|
||||
export interface PoolMetrics {
|
||||
totalConnections: number;
|
||||
activeConnections: number;
|
||||
idleConnections: number;
|
||||
waitingRequests: number;
|
||||
errors: number;
|
||||
lastError?: string;
|
||||
avgResponseTime?: number;
|
||||
created: Date;
|
||||
lastUsed?: Date;
|
||||
}
|
||||
|
||||
export interface ConnectionEvents {
|
||||
onConnect?: () => void | Promise<void>;
|
||||
onDisconnect?: () => void | Promise<void>;
|
||||
onError?: (error: Error) => void | Promise<void>;
|
||||
onPoolCreated?: () => void | Promise<void>;
|
||||
}
|
||||
|
||||
export interface DynamicPoolConfig {
|
||||
enabled: boolean;
|
||||
minSize: number;
|
||||
maxSize: number;
|
||||
scaleUpThreshold: number; // % of pool in use (0-100)
|
||||
scaleDownThreshold: number; // % of pool idle (0-100)
|
||||
scaleUpIncrement: number; // connections to add
|
||||
scaleDownIncrement: number; // connections to remove
|
||||
evaluationInterval: number; // ms between checks
|
||||
}
|
||||
|
||||
/**
|
||||
* Health Status Types
|
||||
*/
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue