UNPKG

8.04 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3const prom_client_1 = require("prom-client");
4const prometheus_extended_gauge_1 = require("prometheus-extended-gauge");
5const generic_pool_1 = require("generic-pool");
6/**
7 * Sensible defaults for the connection pool options
8 */
9exports.DEFAULT_CONNECTION_POOL_OPTIONS = {
10 max: 10,
11 min: 2,
12 maxWaitingClients: 10,
13 testOnBorrow: true,
14 acquireTimeoutMillis: 1000,
15 evictionRunIntervalMillis: 30000,
16 numTestsPerRun: 2,
17 idleTimeoutMillis: 30000,
18};
19class ConnectionPool {
20}
21exports.ConnectionPool = ConnectionPool;
22const connectErrorsCounter = new prom_client_1.Counter({
23 name: 'db_pool_connect_errors_counter',
24 help: 'Number of times a connection attempt fails',
25 labelNames: ['poolName', 'readonly'],
26});
27const acquireErrorsCounter = new prom_client_1.Counter({
28 name: 'db_pool_acquire_errors_counter',
29 help: 'Number of times an acquire attempt fails',
30 labelNames: ['poolName', 'readonly'],
31});
32const validateFailedCounter = new prom_client_1.Counter({
33 name: 'db_pool_validate_failed_counter',
34 help: 'Number of times a validation fails',
35 labelNames: ['poolName', 'readonly'],
36});
37const activeGauge = new prometheus_extended_gauge_1.ExtendedGauge({
38 name: 'db_pool_active_connections_gauge',
39 help: 'Number of active connections in the pool',
40 labelNames: ['poolName', 'readonly'],
41 average: true,
42 max: true,
43 min: false,
44 bucketSizeMillis: 1000,
45 numBuckets: 60,
46});
47const totalGauge = new prom_client_1.Gauge({
48 name: 'db_pool_total_connections_gauge',
49 help: 'Number of total connections in the pool',
50 labelNames: ['poolName', 'readonly'],
51});
52const acquireTimeHistogram = new prom_client_1.Histogram({
53 name: 'db_pool_acquire_time_histogram',
54 help: 'Time required to acquire a connection from the pool',
55 labelNames: ['poolName', 'readonly'],
56 buckets: [0.003, 0.005, 0.01, 0.05, 0.1, 0.3],
57});
58const connectTimeHistogram = new prom_client_1.Histogram({
59 name: 'db_pool_connect_time_histogram',
60 help: 'Time required to establish a new connection to the DB',
61 labelNames: ['poolName', 'readonly'],
62 buckets: [0.003, 0.005, 0.01, 0.05, 0.1, 0.3],
63});
64const useTimeHistogram = new prom_client_1.Histogram({
65 name: 'db_pool_use_time_histogram',
66 help: 'Time a connection is kept out of the pool',
67 labelNames: ['poolName', 'readonly'],
68 buckets: [0.003, 0.005, 0.01, 0.05, 0.1, 0.3],
69});
70const maxConnectionLimitGauge = new prom_client_1.Gauge({
71 name: 'db_pool_max_pool_size_gauge',
72 help: 'Max number of total connections in the pool',
73 labelNames: ['poolName', 'readonly'],
74});
75class InstrumentedFactory {
76 constructor(factory, name, readonly) {
77 this.factory = factory;
78 const labels = [name, readonly ? 'true' : 'false'];
79 this.connectTimeHistogram = connectTimeHistogram.labels(...labels);
80 this.connectErrorsCounter = connectErrorsCounter.labels(...labels);
81 this.totalGauge = totalGauge.labels(...labels);
82 this.validateFailedCounter = validateFailedCounter.labels(...labels);
83 }
84 async create() {
85 const timer = this.connectTimeHistogram.startTimer();
86 try {
87 const connection = await this.factory.create();
88 this.totalGauge.inc();
89 return connection;
90 }
91 catch (e) {
92 this.connectErrorsCounter.inc();
93 throw e;
94 }
95 finally {
96 timer();
97 }
98 }
99 async destroy(client) {
100 try {
101 return await this.factory.destroy(client);
102 }
103 finally {
104 this.totalGauge.dec();
105 }
106 }
107 async validate(client) {
108 if (this.factory.validate) {
109 const resp = await this.factory.validate(client);
110 if (!resp) {
111 this.validateFailedCounter.inc();
112 }
113 return resp;
114 }
115 return true;
116 }
117}
118exports.InstrumentedFactory = InstrumentedFactory;
119var PoolStatus;
120(function (PoolStatus) {
121 PoolStatus[PoolStatus["NOT_STARTED"] = 0] = "NOT_STARTED";
122 PoolStatus[PoolStatus["STARTED"] = 1] = "STARTED";
123 PoolStatus[PoolStatus["STOPPED"] = 2] = "STOPPED";
124})(PoolStatus || (PoolStatus = {}));
125class InstrumentedConnectionPool extends ConnectionPool {
126 constructor(factory, options, name, readonly) {
127 super();
128 this.status = PoolStatus.NOT_STARTED;
129 this.name = name;
130 this.readonly = readonly;
131 this.options = Object.assign({}, exports.DEFAULT_CONNECTION_POOL_OPTIONS, options);
132 // force to int because env values from k8s are string.
133 const maxString = this.options.max.toString();
134 this.options.max = parseInt(maxString, 10);
135 const labels = [name, readonly ? 'true' : 'false'];
136 const instrumentedFactory = new InstrumentedFactory(factory, name, readonly);
137 this.pool = generic_pool_1.createPool(instrumentedFactory, this.getGenericPoolOptions());
138 this.acquireTimeHistogram = acquireTimeHistogram.labels(...labels);
139 this.acquireErrorsCounter = acquireErrorsCounter.labels(...labels);
140 this.useTimeHistogram = useTimeHistogram.labels(...labels);
141 this.activeGauge = activeGauge.labels(...labels);
142 this.maxConnectionLimitGauge = maxConnectionLimitGauge.labels(...labels);
143 }
144 getName() {
145 return this.name;
146 }
147 isReadonly() {
148 return this.readonly;
149 }
150 async getConnection() {
151 if (this.status !== PoolStatus.STARTED) {
152 throw new Error(`Can't acquire connections from connection pool ${this.name}. The pool is not started`);
153 }
154 const timer = this.acquireTimeHistogram.startTimer();
155 try {
156 const connection = await this.pool.acquire();
157 connection['__useTimer'] = this.useTimeHistogram.startTimer();
158 this.activeGauge.inc();
159 this.maxConnectionLimitGauge.set(this.options.max);
160 return connection;
161 }
162 catch (e) {
163 this.acquireErrorsCounter.inc();
164 throw e;
165 }
166 finally {
167 timer();
168 }
169 }
170 getGenericPoolOptions() {
171 return {
172 acquireTimeoutMillis: this.options.acquireTimeoutMillis,
173 autostart: false,
174 evictionRunIntervalMillis: this.options.evictionRunIntervalMillis,
175 fifo: false,
176 idleTimeoutMillis: this.options.idleTimeoutMillis,
177 max: this.options.max,
178 maxWaitingClients: this.options.maxWaitingClients,
179 min: this.options.min,
180 numTestsPerRun: this.options.numTestsPerRun,
181 softIdleTimeoutMillis: this.options.softIdleTimeoutMillis,
182 testOnBorrow: this.options.testOnBorrow,
183 };
184 }
185 release(connection) {
186 this.activeGauge.dec();
187 if (connection['__useTimer']) {
188 connection['__useTimer']();
189 }
190 this.pool.release(connection);
191 }
192 async start() {
193 if (this.status !== PoolStatus.NOT_STARTED) {
194 throw new Error(`Can't start a connection pool that isn't in NOT_STARTED state. Pool: ${this.name}, Current Status: ${this.status}`);
195 }
196 this.status = PoolStatus.STARTED;
197 this.pool['start']();
198 // The pool needs to get into a good state. Waiting a bit has proven a good solution.
199 return new Promise((resolve) => setTimeout(resolve, 30));
200 }
201 async stop() {
202 if (this.status !== PoolStatus.STARTED) {
203 throw new Error(`Can't stop a connection pool that isn't in STARTED state. Pool: ${this.name}, Current Status: ${this.status}`);
204 }
205 this.status = PoolStatus.STOPPED;
206 await this.pool.drain();
207 await this.pool.clear();
208 }
209 getOptions() {
210 return Object.assign({}, this.options);
211 }
212}
213exports.InstrumentedConnectionPool = InstrumentedConnectionPool;
214//# sourceMappingURL=ConnectionPool.js.map
\No newline at end of file