@voicenter-team/mysql-dynamic-cluster
Version:
Galera cluster with implementation of dynamic choose mysql server for queries, caching, hashing it and metrics
235 lines • 9.39 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.ClusterHashing = void 0;
const Logger_1 = __importDefault(require("../utils/Logger"));
const Timer_1 = require("../utils/Timer");
const fs_1 = require("fs");
const path_1 = require("path");
class ClusterHashing {
/**
* @param cluster cluster for what hashing data
* @param clusterName cluster name used for prefix
* @param options cluster settings
*/
constructor(cluster, clusterName, options) {
this.connected = false;
this._serviceNodeMap = new Map(); // key: serviceID; value: nodeID
this._databaseVersion = 1;
Logger_1.default.debug("Configuring hashing in cluster...");
this._cluster = cluster;
this._nextCheckTime = options.nextCheckTime;
this._database = `${clusterName}_${options.dbName}`;
this._timer = new Timer_1.Timer(this._checkHashing.bind(this));
Logger_1.default.debug("Cluster hashing configured");
}
/**
* Activate hashing and create helper db if needed
*/
connect() {
return __awaiter(this, void 0, void 0, function* () {
try {
if (!(yield this._isDatabaseVersionEquals())) {
yield this._cluster.query(`DROP SCHEMA IF EXISTS ${this._database};`, null, {
maxRetry: 1,
redis: false
});
yield this._createDB();
}
Logger_1.default.info(`Database ${this._database} created for hashing`);
yield this._insertNodes();
this._checkHashing();
this.connected = true;
}
catch (err) {
throw err;
}
});
}
/**
* Stop timer for hashing check
*/
stop() {
this._timer.dispose();
this.connected = false;
Logger_1.default.info("Checking hashing in the cluster stopped");
}
/**
* Update node for service in db
* @param serviceId service what need to hashing
* @param nodeId pool where hashing data
*/
updateNodeForService(serviceId, nodeId) {
return __awaiter(this, void 0, void 0, function* () {
try {
yield this._cluster.query('CALL SP_NodeServiceUpdate(?, ?);', [serviceId, nodeId], {
maxRetry: 1,
database: this._database,
redis: false
});
this._serviceNodeMap.set(serviceId, nodeId);
}
catch (e) {
Logger_1.default.error(e.message);
}
});
}
/**
* Get node / pool from hashing by service ID
* @param serviceId service ID
*/
getNodeByService(serviceId) {
return this._serviceNodeMap.get(serviceId);
}
/**
* Create database for hashing
* @private
*/
_createDB() {
return __awaiter(this, void 0, void 0, function* () {
try {
// const extraPath = '';
const extraPath = '../';
const sqlLocations = {
tables: extraPath + '../../assets/sql/create_hashing_database/tables/',
routines: extraPath + '../../assets/sql/create_hashing_database/routines/',
metadata: extraPath + '../../assets/sql/create_hashing_database/metadata/'
};
Logger_1.default.debug(`Creating database ${this._database} and procedures for hashing...`);
yield this._cluster.query(`CREATE SCHEMA IF NOT EXISTS \`${this._database}\` COLLATE utf8_general_ci;`, null, { maxRetry: 1, redis: false });
const sqls = [];
sqls.push(...this._readFilesInDir((0, path_1.join)(__dirname, sqlLocations.tables)).fileContents);
const routinesSqls = this._readFilesInDir((0, path_1.join)(__dirname, sqlLocations.routines));
sqls.push(...routinesSqls.fileContents);
const sqlsMetadata = this._readFilesInDir((0, path_1.join)(__dirname, sqlLocations.metadata)).fileContents;
const sqlsDrop = [];
routinesSqls.fileNames.forEach(name => {
sqlsDrop.push(`DROP PROCEDURE IF EXISTS ${name};`);
});
yield this._cluster.pools[0].multiStatementQuery(sqlsDrop, { database: this._database });
yield this._cluster.pools[0].multiStatementQuery(sqls, { database: this._database });
yield this._cluster.pools[0].multiStatementQuery(sqlsMetadata, { database: this._database });
yield this._cluster.query(`INSERT INTO metadata (version) VALUES (${this._databaseVersion});`, null, {
maxRetry: 1,
database: this._database,
redis: false
});
}
catch (e) {
throw e;
}
});
}
/**
* Check if database version is the same in the server
* @private
*/
_isDatabaseVersionEquals() {
var _a;
return __awaiter(this, void 0, void 0, function* () {
try {
const resDb = yield this._cluster.query(`show databases where \`Database\` = '${this._database}';`, null, {
maxRetry: 1,
redis: false
});
if (resDb.length < 1)
return false;
const res = yield this._cluster.query(`SELECT version FROM metadata;`, null, {
maxRetry: 1,
database: this._database,
redis: false
});
const serverVersion = (_a = res[0]) === null || _a === void 0 ? void 0 : _a.version;
return serverVersion === this._databaseVersion;
}
catch (e) {
Logger_1.default.error(e.message);
return false;
}
});
}
/**
* Read content in files which in folder
* @param dirname path to folder
* @private
*/
_readFilesInDir(dirname) {
const fullFileNames = (0, fs_1.readdirSync)(dirname);
const fileNames = [];
const fileContents = [];
fullFileNames.forEach(filename => {
fileContents.push((0, fs_1.readFileSync)(dirname + filename).toString());
fileNames.push((0, path_1.parse)(filename).name);
});
return { fileNames, fileContents };
}
/**
* Create helper db
* @private
*/
_insertNodes() {
return __awaiter(this, void 0, void 0, function* () {
this._cluster.pools.forEach(pool => {
try {
this._cluster.query('CALL SP_NodeInsert( ? , ? , ? , ? );', [pool.id, pool.name, pool.host, pool.port], {
maxRetry: 1,
database: this._database,
redis: false
});
}
catch (e) {
Logger_1.default.error(e.message);
}
});
});
}
/**
* Update hashing data from db
*/
_checkHashing() {
return __awaiter(this, void 0, void 0, function* () {
try {
if (!this._timer.active)
return;
Logger_1.default.debug("checking async status in cluster");
const result = yield this._cluster.query(`SELECT FN_GetServiceNodeMapping() AS Result;`, null, {
maxRetry: 1,
database: this._database,
redis: false
});
const res = result[0].Result;
res === null || res === void 0 ? void 0 : res.forEach(obj => {
this._serviceNodeMap.set(obj.ServiceID, obj.NodeID);
});
this._nextCheckHashing();
}
catch (err) {
Logger_1.default.error("Something wrong while checking hashing status in cluster.\n Message: " + err.message);
this._nextCheckHashing();
}
});
}
/**
* Activate next hashing check
* @private
*/
_nextCheckHashing() {
this._timer.start(this._nextCheckTime);
}
}
exports.ClusterHashing = ClusterHashing;
//# sourceMappingURL=ClusterHashing.js.map