Our stack is nodejs with MySQL we're using MySQL connections pooling our MySQL database is managed on AWS aurora .
in case of auto failover the master DB is changed the hostname stays the same but the connections inside the pool stays connected to the wrong DB.
The only why we found in order to reset the connection is to roll our servers.
this is a demonstration of a solution I think could solve this issue
but I prefer a solution without the set interval
const mysql = require('mysql');
class MysqlAdapter {
constructor() {
this.connectionType = 'MASTER';
this.waitingForAutoFaileOverSwitch = false;
this.poolCluster = mysql.createPoolCluster();
this.poolCluster.add(this.connectionType, {
host: 'localhost',
user: 'root',
password: 'root',
database: 'app'
});
this.intervalID = setInterval(() => {
if(this.waitingForAutoFaileOverSwitch) return;
this.excute('SHOW VARIABLES LIKE \'read_only\';').then(res => {
// if MASTER is set to read only is on then its mean a fail over is accoure and swe need to switch all connection in poll to secondry database
if (res[0].Value === 'ON') {
this.waitingForAutoFaileOverSwitch = true
this.poolCluster.end(() => {
this. waitingForAutoFaileOverSwitch = false
});
};
});
}, 5000);
}
async excute(query) {
// delay all incoming request until pool kill all connection to read only database
if (this.waitingForAutoFaileOverSwitch) {
return new Promise((resolve, reject) => {
setTimeout(() => {
this.excute(query).then(res => {
resolve(res);
});
}, 1000);
});
}
return new Promise((resolve, reject) => {
this.poolCluster.getConnection(this.connectionType, (err, connection) => {
if (err) {
reject(err);
}
connection.query(query, (err, rows) => {
connection.release();
if (err) {
reject(err);
}
resolve(rows);
});
});
});
}
}
const adapter = new MysqlAdapter();
Is there any other programmable way to reset the connection inside the pool?
Is there any notification we can listing to In case of auto-failover?
Instead of manually monitoring the DB health, as you have also hinted, ideally we subscribe to failover events published by AWS RDS Aurora.
There are multiple failover events listed here for the DB cluster: Amazon RDS event categories and event messages
You can use and test to see which one of them is the most reliable in your use case for triggering poolCluster.end() though.
Related
I'm fairly new to how database connections work using nodejs, and I'm having issues with database connections that aren't being closed properly. I've asked a few questions on here before about it, and it seems like everyone is telling me to use pool instead of the way I have been doing it. The only problem is that when I search online about using pool from promise-mysql, everyone seems to use a very simple and generic approach, but I'm using it within a complex application using sockets. So I'm wondering how I can switch my old approach using createConnection() to using pool instead, in hopes of clearing up these connection issues.
Each time I call a socket it makes a connection to the database and then releases it after it is complete, or so it seems. It sounds like this is not a very scalable approach, and that using pool will help run multiple queries in parallel.
db.js:
import mysql from 'promise-mysql';
import env from '../../../env.config.json';
const db = async (sql, descriptor, serializedParameters = []) => {
return new Promise( async (resolve, reject) => {
try {
const connection = await mysql.createConnection({
host: env.DB.HOST,
user: env.DB.USER,
password: env.DB.PASSWORD,
database: env.DB.NAME,
port: env.DB.PORT
})
if (connection && env.ENV === "development") {
//console.log(/*"There is a connection to the db for: ", descriptor*/);
}
let result;
if(serializedParameters.length > 0) {
result = await connection.query(sql, serializedParameters)
} else result = await connection.query(sql);
connection.end();
resolve(result);
} catch (e) {
console.log("ERROR pool.db: " + e);
reject(e);
};
});
}
export default db;
This is an example of how I would create a connection to query the db
inventory.js:
import db from '../API/db';
export const selectAllFromBuildItems = () => {
return new Promise( async (resolve, reject) => {
try {
const getAllBuildItems = "SELECT * FROM mydb.build_items;"
const response = await db(getAllBuildItems, "AllBuildItems");
resolve(response);
} catch (e) {
console.log("ERROR inventory.selectAllFromBuildItems: " + e);
reject(e);
}
});
};
How can I change my code so that I use a pool instead. I have a lot of different queries that can be called from our application so I'm not quite sure what the right approach for this would be. I saw some people say that I should create the pool once and then use it throughout the application, but I don't know where that would go. If anyone has any suggestions on how I can make this switch, that would help me out a lot. Thanks!
Create the pool. Better if you create once when you run your application.
If it is in different file then you have export here and import in required file.
var pool = mysql.createPool({
host: env.DB.HOST,
user: env.DB.USER,
password: env.DB.PASSWORD,
database: env.DB.NAME,
connectionLimit: 10
});
I had to create this prototype function as the library had a bug of close connection was not returning the connection to the pool.
pool.prototype.releaseConnection = function releaseConnection(connection) {
return this.pool.releaseConnection(connection.connection);
};
Funtion for getting connection from the pool that is created earlier.
If you want you can call pool.getConnection() in all your query functions.
function connect() {
return pool.getConnection().then(function(connection) {
return connection
}).catch(function(e) {
console.log("Error Creating Connection");
throw e;
});
}
Now this is your query function to get data from dd.
function selectAllFromBuildItems() {
var sql_query = `SELECT * FROM mydb.build_items`;
return connect().then(function(conn) {
return conn.query(sql_query).then(function(rows) {
pool.releaseConnection(conn);
return rows;
});
}).catch(function(e) {
console.log("ERROR inventory.selectAllFromBuildItems: " + e);
throw e;
});
}
Update: Descriptions are added. Hope this helps you.
I need to know how to disconnect from my MySQL database after lots of individual callbacks have finished. I have a node.js cron script running on AWS EC2 which accesses s3 buckets and MySQL databases on AWS RDS. The cron script looks something like this:
const mysql = require("mysql2"),
AWS = require("aws-sdk"),
s3 = new AWS.S3(),
connection = mysql.connect({...});
connection.connect();
connection.query(`SELECT ... LIMIT 100`, (error, results) => {
if (error) throw new Error(error);
for (let idx in results) {
const row = results[idx],
Key = `my/key/${row.id}`;
s3.getObject({Bucket, Key}, (error, object) => {
// do more things, with more callbacks
});
}
});
setTimeout(() => connection.end(), 10000); // disconnect database in 10 seconds
The script doesn't exit until I disconnect from the database using connection.end(). I can't disconnect as normal e.g. after the for loop, because the various callbacks are still running. I need to know when they're all finished. Currently I just disconnect after 10 seconds because everything should have completed by then. If I don't do that then I end up with lots of never-ending processes running.
Do I need to set flags & counts of each thing, and then use setInterval or something until they're all finished and it's safe to disconnect? OK to do but is that the right approach when using callbacks, promises & thens?
You can do it with counters or flags as you said, or with Promise.all:
const mysql = require("mysql2"),
AWS = require("aws-sdk"),
s3 = new AWS.S3(),
connection = mysql.connect({...});
function doQuery(){
connection.connect();
return new Promise((resolve, reject)=>{
connection.query(`SELECT ... LIMIT 100`, (error, results) => {
if (error) { return reject(new Error(error)); }
resolve(results)
});
})
}
doQuery()
.then(results => {
const jobs = results.map(row => {
const Key = `my/key/${row.id}`;
return new Promise((resolve, reject) => {
s3.getObject({Bucket, Key}, (error, object) => {
// do more things, with more callbacks
resolve('ok')
});
})
})
return Promise.all(jobs)
})
.finally(()=>{
connection.end()
})
I just wanted to post as well that Promise.all() is definitely a great way to go, however it's not the only approach.
In this day & age, where the cost of connecting to & disconnecting from your database can be very cheap, I find it simpler to just connect on every query and disconnect after:
const dbOneQuery = (sql, bindVars, callback) => {
const dbConnection = getConnection(); // mysql2.createConnection etc
dbConnection.query(sql, bindVars, (error, result) => {
dbConnection.end();
if (callback) callback(error, result);
});
};
and that way there aren't any connections held open to be closed.
If in future I move to persistent connections again, I can just change what getConnection() does and use something that overrides .end() etc..
For me this approach has been simpler overall compared to managing a single shared connection to the database, with no real downsides.
I'm using node with mysql and I have a route that does:
const mysql = require("./mysql");
router.post("/register_user", (req, res) => {
mysql.register(req.body).then((result) => {
// stuff
});
});
mysql.js:
const mysql = require("mysql");
const connection = mysql.createConnection("mysql://...");
exports.register = (req) => {
const user = { name: req.name };
return new Promise((resolve, reject) => {
// make sure user doesn't exist already
connection.query('...', [user], (err, data) => {
...
if (isNewUser) {
connection.query('INSERT INTO USER...', user, (insertErr, rows) => {
...
resolve(rows);
connection.end();
}
}
});
});
}
This works perfectly when I register the first user in my app. But immediately after, if I log out (on the web app), then register a new user, I get an error saying:
Error: Cannot enqueue Query after invoking quit.
Why doesn't this create a new connection?
I assume you are using the following NPM module mysql
If it is the case then could you simply use MySQL pooling connections ?
Rather than creating and managing connections one-by-one, this module also provides built-in connection pooling using mysql.createPool(config).
So instead of calling connection.end(); you would be calling connection.release(); instead to return connection to the pool of open connections.
I am using Meteor.js, Apollo/GraphQL and a MySQL database with the mysql npm package as the driver.
I am creating a connection pool as follows:
import { createPool } from 'mysql';
const pool = createPool({
host: 'localhost',
user: 'root',
password: 'asdfasdf',
database: 'soundcraft',
});
export function query(sql) {
// const connection = this;
return new Promise((resolve, reject) => {
pool.query(sql, (err, results) => {
if (err) {
reject(err);
} else {
resolve(results);
}
});
});
}
I'm not sure what's happening but after a few database requests (equal to the connection limit) it will just freeze and give no error report, and it will stay frozen until the server is restarted.
EDIT: After some reading and finding this issue it doesn't seem have anything to do with the connection being released, rather it is the fact that the connection remains open by MySQL.
It seems impractical to just increase the connection limit as eventually surely the user will still use up their quota of connections.
It doesn't look like I'm approaching this problem in the right way
I use node js with mysql and want to avoid that the app crash on connection errors.At the moment i use this :
function mysql_handleDisconnect() {
mysql_connection = mysql.createConnection(mysql_config_obj); // Recreate the connection, since
// the old one cannot be reused.
mysql_connection.connect(function(err) { // The server is either down
if(err) { // or restarting (takes a while sometimes).
console.log('error when connecting to db:', err);
mysql_handleDisconnect(); // We introduce a delay before attempting to reconnect,
} // to avoid a hot loop, and to allow our node script to
}); // process asynchronous requests in the meantime.
// If you're also serving http, display a 503 error.
mysql_connection.on('error', function(err) {
console.log('db error', err);
if(err.code === 'PROTOCOL_CONNECTION_LOST') { // Connection to the MySQL server is usually
mysql_handleDisconnect(); // lost due to either server restart, or a
} else { // connnection idle timeout (the wait_timeout
throw err; // server variable configures this)
}
});
}
mysql_handleDisconnect(mysql_connection);
so this is blocking because it leads to a hot loop if the connection is closed.my problem is, if i add a setTimeout to reestablish connection just every 2 seconds i could get an fatal error when i do a query with "mysql_connection.query('SELECT ...')".in this case the app crashes.
So my question is,if there's a possibility to check the connection before i do a query?
Try using below code in every microservice before doing anything:
if(connection.state === 'disconnected'){
return respond(null, { status: 'fail', message: 'server down'});
}
State of connection to DB could fall in 2 states:
disconnected (when due to DB server down or wrong config use for DB connection is wrong)
authenticated (when DB connection is successfully created to DB server).
So either check state == 'disconnected' or state == 'authenticated'
I know this is an old question but I have found connection.ping( (err) => {...}) to be very useful for health-checks made from load balancers and whatnot.
Every time, while I'm pushing my code in production, the mysql connection is lost. It is a very common problem in production, or local.
My solution is that At every query established the db connection and remove connection after completing the db query.
My solution is to establish the db connection before every query, and then remove the connection after completing the db query.
Step1: Here is the code for dbConnection.js
//this code is for conenct to db
const mysql = require('mysql2');
require('dotenv').config();
module.exports.stablishedConnection = ()=>{
return new Promise((resolve,reject)=>{
const con = mysql.createConnection( {
host: process.env.DB_HOST||localhost,
user: process.env.DB_USER_NAME||myUserName ,
password: process.env.DB_PASSWORD||mypassword,
database: process.env.DB_NAME||mydb
});
con.connect((err) => {
if(err){
reject(err);
}
resolve(con);
});
})
}
module.exports.closeDbConnection =(con)=> {
con.destroy();
}
Step2: For Router.js I am import the db connection and handle the promise
const router = require('express').Router();
const {stablishedConnection,closeDbConnection} =require('../db/dbConnection');
router.get('/user/:sId/:userId',function(req,res){
stablishedConnection()
.then((db)=>{
console.log("Db connection stablished");
db.query(`select * from user WHERE sent_id=${req.params.sId} AND user_id=${req.params.userId}`, null, function (err,data) {
if (!data) {
res.status(200).json({sucess:false,err});
}else{
res.status(200).json({sucess:true,data});
closeDbConnection(db);
console.log("Db Connection close Successfully");
}
})
}).catch((error)=>{
console.log("Db not connected successfully",error);
});
});
router.get('/sen/:userId',function(req,res){
stablishedConnection()
.then((db)=>{
console.log("Db connection stablished");
db.query(`select * from sen WHERE user_id=${req.params.userId}`, null, function (err,data) {
if (!data) {
res.status(200).json({sucess:false,err});
}else{
res.status(200).json({sucess:true,data});
closeDbConnection(db);
console.log("Db Connection close Successfully");
}
})
}).catch((error)=>{
console.log("Db not connected successfully",error);
});
});
router.get('/language',(req,res)=>{
stablishedConnection()
.then((db)=>{
console.log("Db connection stablished");
db.query("select * from language", null, function (err,data) {
if (!data) {
res.status(200).json({sucess:false,err});
}else{
res.status(200).json({sucess:true,data});
closeDbConnection(db);
console.log("Db Connection close Successfully")
}
})
}).catch((error)=>{
console.log("Db not connected successfully",error);
});
})
module.exports = router;
This is perfectly run If you want to create and close connection at every query ..
I solved this problem like this:
let connection = mysql.createConnection(DB_CONFIG);
function runDBQuery() {
const disconnected = await new Promise(resolve => {
connection.ping(err => {
resolve(err);
});
});
if (disconnected) {
connection = mysql.createConnection(DB_CONFIG);
}
... use actual connection
}