@voicenter-team/mysql-dynamic-cluster
Version:
Galera cluster with implementation of dynamic choose mysql server for queries, caching, hashing it and metrics
268 lines • 13.2 kB
JavaScript
"use strict";
/**
* Created by Bohdan on Sep, 2021
*/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Pool = void 0;
const mysql2_1 = __importDefault(require("mysql2"));
const Logger_1 = __importDefault(require("../utils/Logger"));
const PoolStatus_1 = require("./PoolStatus");
const Metrics_1 = __importDefault(require("../metrics/Metrics"));
const MetricNames_1 = __importDefault(require("../metrics/MetricNames"));
const Events_1 = __importDefault(require("../utils/Events"));
const QueryTimer_1 = require("../utils/QueryTimer");
const Redis_1 = __importDefault(require("../Redis/Redis"));
// AKA galera node
class Pool {
/**
* @param settings pool settings
* @param clusterName cluster name used for prefix
*/
constructor(settings, clusterName) {
this.id = settings.id;
this.host = settings.host;
this.port = settings.port;
this.name = settings.name ? settings.name : `${this.host}:${this.port}`;
Logger_1.default.debug(`Configure pool named ${this.name} ${this.host}:${this.port}`);
this._user = settings.user;
this._password = settings.password;
this._database = settings.database;
this._queryTimeout = settings.queryTimeout;
this._slowQueryTime = settings.slowQueryTime;
this._redisFactor = settings.redisFactor;
this._redisExpire = settings.redisExpire;
this.connectionLimit = settings.connectionLimit;
this._status = new PoolStatus_1.PoolStatus(this, settings, false, this.connectionLimit);
Logger_1.default.info("configuration pool finished in host: " + this.host);
}
get status() {
return this._status;
}
/**
* Create pool connection
*/
connect() {
return __awaiter(this, void 0, void 0, function* () {
Logger_1.default.debug("Creating pool in host: " + this.host);
this._pool = mysql2_1.default.createPool({
host: this.host,
port: this.port,
user: this._user,
password: this._password,
database: this._database,
connectionLimit: this.connectionLimit
});
this.status.active = true;
this._connectEvents();
yield this.status.checkStatus();
if (this.status.isValid) {
Logger_1.default.info('Pool is connected');
Events_1.default.emit('pool_connected', this.id);
}
else {
throw new Error("pool in host " + this.host + " is not valid");
}
});
}
/**
* Connect events in pool
* @private
*/
_connectEvents() {
this._pool.on("connection", (connection) => {
this.status.availableConnectionCount--;
Logger_1.default.debug("Open connection");
Events_1.default.emit('connection', connection, this.id);
});
this._pool.on("release", (connection) => {
this.status.availableConnectionCount++;
Logger_1.default.debug("Connection closed");
Events_1.default.emit('release', connection, this.id);
});
this._pool.on('acquire', (connection) => {
Logger_1.default.debug("Connection is acquire");
Events_1.default.emit('acquire', connection, this.id);
});
}
/**
* Close pool connection
*/
disconnect() {
Logger_1.default.debug("closing pool in host: " + this.host);
this._pool.end((error) => {
if (error) {
Logger_1.default.error(error.message);
}
});
this.status.active = false;
this.status.stopTimerCheck();
Events_1.default.emit('pool_disconnected', this.id);
Logger_1.default.info("pool named " + this.name + " closed");
}
/**
* Pool query
* @param sql mysql query string
* @param queryOptions query options like timeout, database, multipleStatements etc
*/
query(sql, queryOptions) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
queryOptions = Object.assign({ timeout: this._queryTimeout, database: this._database, redisFactor: this._redisFactor, redisExpire: this._redisExpire }, queryOptions);
const poolMetricOption = {
pool: {
id: this.id,
name: this.name
}
};
if (queryOptions === null || queryOptions === void 0 ? void 0 : queryOptions.serviceId) {
poolMetricOption.service = {
id: queryOptions.serviceId,
name: (queryOptions === null || queryOptions === void 0 ? void 0 : queryOptions.serviceName) ? queryOptions.serviceName : String(queryOptions.serviceId)
};
}
const queryTimer = new QueryTimer_1.QueryTimer(MetricNames_1.default.pool.queryTime);
Metrics_1.default.inc(MetricNames_1.default.pool.allQueries, poolMetricOption);
Metrics_1.default.mark(MetricNames_1.default.pool.queryPerMinute, poolMetricOption);
queryTimer.start();
this._pool.getConnection((err, conn) => {
if (err) {
Metrics_1.default.inc(MetricNames_1.default.pool.errorQueries, poolMetricOption);
queryTimer.end();
queryTimer.save(poolMetricOption);
conn === null || conn === void 0 ? void 0 : conn.release();
reject(err);
}
if (!conn) {
Metrics_1.default.inc(MetricNames_1.default.pool.errorQueries, poolMetricOption);
queryTimer.end();
queryTimer.save(poolMetricOption);
conn === null || conn === void 0 ? void 0 : conn.release();
reject(new Error("Can't find connection. Maybe it was unexpectedly closed."));
}
// change database
Logger_1.default.debug("Changing database to " + queryOptions.database);
conn === null || conn === void 0 ? void 0 : conn.changeUser({ database: queryOptions.database }, (error) => {
if (error) {
Metrics_1.default.inc(MetricNames_1.default.pool.errorQueries, poolMetricOption);
queryTimer.end();
queryTimer.save(poolMetricOption);
conn.release();
reject(error);
}
});
Logger_1.default.debug(`Query in pool by host ${this.host}`);
conn === null || conn === void 0 ? void 0 : conn.query({ sql, timeout: queryOptions.timeout }, (error, result) => {
if (error) {
Metrics_1.default.inc(MetricNames_1.default.pool.errorQueries, poolMetricOption);
queryTimer.end();
queryTimer.save(poolMetricOption);
conn.release();
reject(error);
}
conn.release();
queryTimer.end();
queryTimer.save(poolMetricOption);
if (queryTimer.get() >= this._slowQueryTime) {
Logger_1.default.warn(`Query in pool named ${this.name} takes ${queryTimer.get()} sec`);
}
Metrics_1.default.inc(MetricNames_1.default.pool.successfulQueries, poolMetricOption);
if (queryOptions.redis) {
const redisExpired = new Date().getTime() + queryTimer.get() * 1000 * queryOptions.redisFactor;
const redisData = {
data: result,
expired: redisExpired
};
Redis_1.default.set(sql, JSON.stringify(redisData), queryOptions.redisExpire);
}
resolve(result);
});
});
}));
});
}
/**
* Pool query by mysql transaction
* @param sqls array of sql queries
* @param queryOptions query options like timeout, database etc.
*/
multiStatementQuery(sqls, queryOptions) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve, reject) => {
queryOptions = Object.assign({ timeout: this._queryTimeout, database: this._database }, queryOptions);
const poolMetricOption = {
pool: {
id: this.id,
name: this.name
}
};
Metrics_1.default.inc(MetricNames_1.default.pool.allQueries, poolMetricOption);
Metrics_1.default.mark(MetricNames_1.default.pool.queryPerMinute, poolMetricOption);
const results = [];
this._pool.getConnection((err, conn) => {
if (err) {
Metrics_1.default.inc(MetricNames_1.default.pool.errorQueries, poolMetricOption);
reject(err);
}
if (!conn) {
Metrics_1.default.inc(MetricNames_1.default.pool.errorQueries, poolMetricOption);
reject(new Error("Can't find connection. Maybe it was unexpectedly closed."));
}
// change database
Logger_1.default.debug("Changing database to " + queryOptions.database);
conn === null || conn === void 0 ? void 0 : conn.changeUser({ database: queryOptions.database }, (error) => {
if (error) {
Metrics_1.default.inc(MetricNames_1.default.pool.errorQueries, poolMetricOption);
conn.release();
reject(error);
}
});
Logger_1.default.debug("Start transaction in pool by host " + this.host);
conn === null || conn === void 0 ? void 0 : conn.beginTransaction(error => {
if (error) {
Metrics_1.default.inc(MetricNames_1.default.pool.errorQueries, poolMetricOption);
conn.release();
reject(error);
}
sqls.forEach(sql => {
conn.query({ sql, timeout: queryOptions.timeout }, (errorQ, result) => {
if (errorQ) {
conn.rollback(() => 0);
Metrics_1.default.inc(MetricNames_1.default.pool.errorQueries, poolMetricOption);
conn.release();
reject(errorQ);
}
results.push(result);
});
});
Logger_1.default.debug("Commit transaction in pool by host " + this.host);
conn.commit(errorC => {
if (errorC) {
conn.rollback(() => 0);
Metrics_1.default.inc(MetricNames_1.default.pool.errorQueries, poolMetricOption);
conn.release();
reject(errorC);
}
});
Metrics_1.default.inc(MetricNames_1.default.pool.successfulQueries, poolMetricOption);
conn.release();
resolve(results);
});
});
});
});
}
}
exports.Pool = Pool;
//# sourceMappingURL=Pool.js.map