UNPKG

@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
"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