UNPKG

inceptum

Version:

hipages take on the foundational library for enterprise-grade apps written in NodeJS

216 lines 8.42 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.InstrumentedConnectionPool = exports.InstrumentedFactory = exports.ConnectionPool = exports.DEFAULT_CONNECTION_POOL_OPTIONS = void 0; const prom_client_1 = require("prom-client"); const prometheus_extended_gauge_1 = require("prometheus-extended-gauge"); const generic_pool_1 = require("generic-pool"); const ErrorUtil_1 = require("../util/ErrorUtil"); /** * Sensible defaults for the connection pool options */ exports.DEFAULT_CONNECTION_POOL_OPTIONS = { max: 10, min: 2, maxWaitingClients: 10, testOnBorrow: true, acquireTimeoutMillis: 1000, evictionRunIntervalMillis: 30000, numTestsPerRun: 2, idleTimeoutMillis: 30000, }; class ConnectionPool { } exports.ConnectionPool = ConnectionPool; const connectErrorsCounter = new prom_client_1.Counter({ name: 'db_pool_connect_errors_counter', help: 'Number of times a connection attempt fails', labelNames: ['poolName', 'readonly'], }); const acquireErrorsCounter = new prom_client_1.Counter({ name: 'db_pool_acquire_errors_counter', help: 'Number of times an acquire attempt fails', labelNames: ['poolName', 'readonly'], }); const validateFailedCounter = new prom_client_1.Counter({ name: 'db_pool_validate_failed_counter', help: 'Number of times a validation fails', labelNames: ['poolName', 'readonly'], }); const activeGauge = new prometheus_extended_gauge_1.ExtendedGauge({ name: 'db_pool_active_connections_gauge', help: 'Number of active connections in the pool', labelNames: ['poolName', 'readonly'], average: true, max: true, min: false, bucketSizeMillis: 1000, numBuckets: 60, }); const totalGauge = new prom_client_1.Gauge({ name: 'db_pool_total_connections_gauge', help: 'Number of total connections in the pool', labelNames: ['poolName', 'readonly'], }); const acquireTimeHistogram = new prom_client_1.Histogram({ name: 'db_pool_acquire_time_histogram', help: 'Time required to acquire a connection from the pool', labelNames: ['poolName', 'readonly'], buckets: [0.003, 0.005, 0.01, 0.05, 0.1, 0.3], }); const connectTimeHistogram = new prom_client_1.Histogram({ name: 'db_pool_connect_time_histogram', help: 'Time required to establish a new connection to the DB', labelNames: ['poolName', 'readonly'], buckets: [0.003, 0.005, 0.01, 0.05, 0.1, 0.3], }); const useTimeHistogram = new prom_client_1.Histogram({ name: 'db_pool_use_time_histogram', help: 'Time a connection is kept out of the pool', labelNames: ['poolName', 'readonly'], buckets: [0.003, 0.005, 0.01, 0.05, 0.1, 0.3], }); const maxConnectionLimitGauge = new prom_client_1.Gauge({ name: 'db_pool_max_pool_size_gauge', help: 'Max number of total connections in the pool', labelNames: ['poolName', 'readonly'], }); class InstrumentedFactory { constructor(factory, name, readonly) { this.factory = factory; const labels = [name, readonly ? 'true' : 'false']; this.connectTimeHistogram = connectTimeHistogram.labels(...labels); this.connectErrorsCounter = connectErrorsCounter.labels(...labels); this.totalGauge = totalGauge.labels(...labels); this.validateFailedCounter = validateFailedCounter.labels(...labels); } async create() { const timer = this.connectTimeHistogram.startTimer(); try { const connection = await this.factory.create(); this.totalGauge.inc(); return connection; } catch (e) { this.connectErrorsCounter.inc(); throw e; } finally { timer(); } } async destroy(client) { try { return await this.factory.destroy(client); } finally { this.totalGauge.dec(); } } async validate(client) { if (this.factory.validate) { const resp = await this.factory.validate(client); if (!resp) { this.validateFailedCounter.inc(); } return resp; } return true; } } exports.InstrumentedFactory = InstrumentedFactory; var PoolStatus; (function (PoolStatus) { PoolStatus[PoolStatus["NOT_STARTED"] = 0] = "NOT_STARTED"; PoolStatus[PoolStatus["STARTED"] = 1] = "STARTED"; PoolStatus[PoolStatus["STOPPED"] = 2] = "STOPPED"; })(PoolStatus || (PoolStatus = {})); class InstrumentedConnectionPool extends ConnectionPool { constructor(factory, options, name, readonly) { super(); this.status = PoolStatus.NOT_STARTED; this.name = name; this.readonly = readonly; this.options = Object.assign(Object.assign({}, exports.DEFAULT_CONNECTION_POOL_OPTIONS), options); // force to int because env values from k8s are string. const maxString = this.options.max.toString(); this.options.max = parseInt(maxString, 10); const labels = [name, readonly ? 'true' : 'false']; const instrumentedFactory = new InstrumentedFactory(factory, name, readonly); this.pool = (0, generic_pool_1.createPool)(instrumentedFactory, this.getGenericPoolOptions()); this.acquireTimeHistogram = acquireTimeHistogram.labels(...labels); this.acquireErrorsCounter = acquireErrorsCounter.labels(...labels); this.useTimeHistogram = useTimeHistogram.labels(...labels); this.activeGauge = activeGauge.labels(...labels); this.maxConnectionLimitGauge = maxConnectionLimitGauge.labels(...labels); } getName() { return this.name; } isReadonly() { return this.readonly; } async getConnection() { if (this.status !== PoolStatus.STARTED) { throw new Error(`Can't acquire connections from connection pool ${this.name}. The pool is not started`); } const timer = this.acquireTimeHistogram.startTimer(); try { const connection = await this.pool.acquire(); connection['__useTimer'] = this.useTimeHistogram.startTimer(); this.activeGauge.inc(); this.maxConnectionLimitGauge.set(this.options.max); return connection; } catch (e) { this.acquireErrorsCounter.inc(); throw new ErrorUtil_1.ExtendedError(`Could not aquire connection from pool ${this.name}-${this.readonly ? 'write' : 'read'} using user ${this.options.connectionConfig.user}`, e); } finally { timer(); } } getGenericPoolOptions() { return { acquireTimeoutMillis: this.options.acquireTimeoutMillis, autostart: false, evictionRunIntervalMillis: this.options.evictionRunIntervalMillis, fifo: false, idleTimeoutMillis: this.options.idleTimeoutMillis, max: this.options.max, maxWaitingClients: this.options.maxWaitingClients, min: this.options.min, numTestsPerRun: this.options.numTestsPerRun, softIdleTimeoutMillis: this.options.softIdleTimeoutMillis, testOnBorrow: this.options.testOnBorrow, }; } release(connection) { this.activeGauge.dec(); if (connection['__useTimer']) { connection['__useTimer'](); } this.pool.release(connection); } async start() { if (this.status !== PoolStatus.NOT_STARTED) { throw new Error(`Can't start a connection pool that isn't in NOT_STARTED state. Pool: ${this.name}, Current Status: ${this.status}`); } this.status = PoolStatus.STARTED; this.pool['start'](); // The pool needs to get into a good state. Waiting a bit has proven a good solution. return new Promise((resolve) => setTimeout(resolve, 30)); } async stop() { if (this.status !== PoolStatus.STARTED) { throw new Error(`Can't stop a connection pool that isn't in STARTED state. Pool: ${this.name}, Current Status: ${this.status}`); } this.status = PoolStatus.STOPPED; await this.pool.drain(); await this.pool.clear(); } getOptions() { return Object.assign({}, this.options); } } exports.InstrumentedConnectionPool = InstrumentedConnectionPool; //# sourceMappingURL=ConnectionPool.js.map