My very simple Node.js code doesn't seem like its connection pool work as it's supposed to do. _connectionQueue of Pool object just gets longer and longer infinitely, and app dies. I mean it does make a pool and there are pre-made connections already, but they are not reusable or insert requests are too many and fast? I'm not sure..
I've tried to put some more connectionLimit like following :
let state = { pool: null }
export const connect = () => {
state.pool = mysql.createPool({
connectionLimit: 200,
host: process.env.DATABASE_HOST || 'localhost',
user: process.env.DATABASE_USER || 'root',
password: process.env.DATABASE_PASSWORD || 'password',
database: process.env.DATABASE_NAME || 'database'
})
}
export const get = () => state.pool
Mostly given job of this server is subscription and insertion. It subscribes several MQTT topics and just tries to insert messages into RDB. About 100 messages arrives every second, and that code looks like this.
mqttClient.on('message', function (topic, message) {
if(topic.includes('sensor')){
try {
const data = JSON.parse(message.toString())
if(validate(data.uuid)){
const params = [data.a, data.b, data.c, ...]
sensor.setStatus(params)
}
} catch(err){
console.error(err)
}
}
}
export const setStatus = (params) => {
const SQL = `INSERT INTO ...`
db.get().query(SQL, params, (err, result) => {
if (err) console.error(err)
})
}
Then, I see this through chrome-devtools
Object
pool: Pool
config: PoolConfig {acquireTimeout: 10000, connectionConfig: ConnectionConfig, waitForConnections: true, connectionLimit: 200, queueLimit: 0}
domain: null
_acquiringConnections: []
_allConnections: (200) [PoolConnection, PoolConnection, …]
_closed: false
_connectionQueue: (11561) [ƒ, ƒ, ƒ, ƒ, …]
_events: {}
_eventsCount: 0
_freeConnections: []
_maxListeners: undefined
__proto__: EventEmitter
__proto__: Object
I've put console.log into setStatus like following :
export const setStatus = (params) => {
const SQL = `INSERT INTO ...`
console.log(`allConnections=${db.get()._allConnections.length}, connectionQueue=${db.get()._connectionQueue.length}`)
db.get().query(SQL, params, (err, result) => {
if (err) console.error(err)
})
}
, and got these.
allConnections=200, connectionQueue=29
allConnections=200, connectionQueue=30
allConnections=200, connectionQueue=31
allConnections=200, connectionQueue=32
allConnections=200, connectionQueue=33
allConnections=200, connectionQueue=34
...
It seems like server created a connection pool very well, but not using those connections. Instead, trying to create a new connection more and more all the time and those requests just get stuck in _connectionQueue.
It appears you are creating a new pool every time you'd like to make a query. The common model is to create a pool once when the application starts, then use connections from that pool as needed (one pool, many connections).
Also if you're using a simple DB model you can simplify access to the pool by making it global. Below is an alternate to your code you might try:
app.js
const mysql = require('mysql');
const connection = mysql.createPool({
host: process.env.DB_HOST || '127.0.0.1',
user: process.env.DB_USER || 'local_user',
password: process.env.DB_PASSWORD || 'local_password',
database: process.env.DB_NAME || 'local_database'
});
global.db = connection;
modules.js
export const setStatus = (params) => {
let SQL = `INSERT INTO ...`
db.query(SQL, params, (err, result) => {
if (err) console.error(err)
console.log(result)
})
}
Documentation for further reference :: https://github.com/mysqljs/mysql#pooling-connections
Edit 1 - Log pool events
db.on('acquire', function (connection) {
console.log('Connection %d acquired', connection.threadId);
});
db.on('connection', function (connection) {
console.log('Pool id %d connected', connection.threadId);
});
db.on('enqueue', function () {
console.log('Waiting for available connection slot');
});
db.on('release', function (connection) {
console.log('Connection %d released', connection.threadId);
});
Related
I get an error when running the integration tests:
0 passing (17s)
1 failure
1) Registration page
register new users allowed and update status in the database:
TypeError: Net.connect is not a function
at new Connection (webpack:///./node_modules/mysql2/lib/connection.js:50:0)
at ./node_modules/mysql2/index.js.exports.createConnection (webpack:///./node_modules/mysql2/index.js:10:0)
at Context.eval (webpack:///./cypress/integration/registration.spec.js:23:34)
Here is my environment:
MySQL Workbench
MySQL Server 8.0.29
I raised local backendless, I have access to the database. Here is my code:
const mysql2 = require('mysql2');
describe('Registration page', () => {
beforeEach(() => {
// visit the registration page
cy.visit('http://localhost:3000/registration');
});
it('register new users allowed and update status in the database', () => {
// fill out the registration form
cy.get('input[name="fullName"]').type("Nazar Dmytryshyn")
cy.get('input[type="email"]').type('testuser#example.com');
cy.get('input[name="pwd"]').type('testpassword');
cy.get('input[name="confirmPassword"]').type('testpassword');
// submit the form
cy.get('button[class="btn btn-success"]').click();
// check that the user is redirected to the login page
cy.url().should('include', '/login');
// create a connection to the test database
const connection = mysql2.createConnection({
host: '127.0.0.1:3306',
user: 'root',
password: 'rootpassword',
database: 'local1'
});
// open the connection
connection.connect();
// update the developer status in the database
connection.query(
'UPDATE `main_backendless`.`Developer` SET `developerStatusId` = "1" WHERE (`email` = "testuser#example.com")',
(error, results) => {
if (error) throw error;
expect(results.affectedRows).to.equal(1);
}
);
// close the connection
connection.end();
});
});
I checked this data 10 times, it is correct and I can connect to the database through MySQL WorkBench
host: '127.0.0.1:3306',
user: 'root',
password: 'rootpassword',
database: 'main_backendless'
I will be grateful for any ideas that can be achieved!
I recommend using the cypress-mysql, which hides a lot of the implementation details for you.
If you try to roll your own task, you may end up with an undefined return value.
Install
npm install cypress-mysql
//or
yarn add cypress-mysql
Configure
The release notes are out of date, here is the configuration for Cypress 10+
// cypress.config.js
const { defineConfig } = require("cypress");
const mysql = require('cypress-mysql');
module.exports = defineConfig({
// ...
e2e: {
setupNodeEvents(on, config) {
mysql.configurePlugin(on);
},
"env": {
"db": {
"host": "localhost",
"user": "user",
"password": "password",
"database": "database"
}
}
})
// cypress/support/e2e.js
const mysql = require('cypress-mysql');
mysql.addCommands();
Test
const sql = 'UPDATE "main_backendless.Developer" SET "developerStatusId" = "1" WHERE ("email" = "testuser#example.com")'
cy.query(sql).then(res => {
expect(res.affectedRows).to.equal(1)
});
If you want to use a task to call the mySql library, you must return a Promise from the task.
This is because the mysql calls are asynchronous, and the only way Cypress knows to wait for them is to get a promise returned from your code.
cypress.config.js
const { defineConfig } = require("cypress")
const mysql2 = require('mysql2')
const connection = mysql2.createConnection({
host: '127.0.0.1:3306',
user: 'root',
password: 'rootpassword',
database: 'local1'
})
module.exports = defineConfig({
e2e: {
setupNodeEvents(on, config) {
on('task', {
mySql: (sql) => {
return new Promise((resolve, reject) => {
connection.query(sql, (error, results) => {
if (error) {
reject(error)
} else {
resolve(results.affectedRows)
})
})
})
}
})
},
})
it('tests with mysql', () => {
cy.task('mySql', 'sql staement here')
.then(result => {
expect(result).to.equal(1);
})
})
With Promise-wrapper
Alternatively, mysql2 provides a promise-wrapper that can simplify your code:
const { defineConfig } = require("cypress")
const mysql = require('mysql2/promise') // different import here
const connection = mysql2.createConnection({
host: '127.0.0.1:3306',
user: 'root',
password: 'rootpassword',
database: 'local1'
})
module.exports = defineConfig({
e2e: {
setupNodeEvents(on, config) {
on('task', {
mySql: async (sql) => { // async here
const result = await connection.execute(sql) // await here
return result;
}
})
},
})
The issue is that you're using a nodejs library with Cypress. Cypress tests execute inside a browser and cannot directly utilize nodejs libraries within a test.
To do what you want to need to create a cy.task() to be able to execute code in nodejs.
Assuming you're using js, create a file with a function to use the sql connector
// runSql.js
const mysql2 = require('mysql2');
function runSql(sql) {
const connection = mysql2.createConnection({
host: '127.0.0.1:3306',
user: 'root',
password: 'rootpassword',
database: 'local1'
});
connection.connect();
let rows;
connection.query(sql, (error, results) => {
if (error) throw error;
rows = results.affectedRows
});
connection.end();
return rows;
}
module.exports = runSql;
Then in the cypress.config.js file
const runSql = require('./runSql.js');
module.exports = defineConfig({
// ...
e2e: {
setupNodeEvents(on, config) {
on('task', {
runSql
});
},
});
Now to call the task in a test
describe('Registration page', () => {
it('test', () => {
const sql = 'UPDATE `main_backendless`.`Developer` SET `developerStatusId` = "1" WHERE (`email` = "testuser#example.com")';
cy.task('runSql', sql).then((rows) => {
expect(rows).to.equal(1);
});
});
});
I get an error when running the integration tests:
0 passing (17s)
1 failure
1) Registration page
register new users allowed and update status in the database:
TypeError: Net.connect is not a function
at new Connection (webpack:///./node_modules/mysql2/lib/connection.js:50:0)
at ./node_modules/mysql2/index.js.exports.createConnection (webpack:///./node_modules/mysql2/index.js:10:0)
at Context.eval (webpack:///./cypress/integration/registration.spec.js:23:34)
Here is my environment:
MySQL Workbench
MySQL Server 8.0.29
I raised local backendless, I have access to the database. Here is my code:
const mysql2 = require('mysql2');
describe('Registration page', () => {
beforeEach(() => {
// visit the registration page
cy.visit('http://localhost:3000/registration');
});
it('register new users allowed and update status in the database', () => {
// fill out the registration form
cy.get('input[name="fullName"]').type("Nazar Dmytryshyn")
cy.get('input[type="email"]').type('testuser#example.com');
cy.get('input[name="pwd"]').type('testpassword');
cy.get('input[name="confirmPassword"]').type('testpassword');
// submit the form
cy.get('button[class="btn btn-success"]').click();
// check that the user is redirected to the login page
cy.url().should('include', '/login');
// create a connection to the test database
const connection = mysql2.createConnection({
host: '127.0.0.1:3306',
user: 'root',
password: 'rootpassword',
database: 'local1'
});
// open the connection
connection.connect();
// update the developer status in the database
connection.query(
'UPDATE `main_backendless`.`Developer` SET `developerStatusId` = "1" WHERE (`email` = "testuser#example.com")',
(error, results) => {
if (error) throw error;
expect(results.affectedRows).to.equal(1);
}
);
// close the connection
connection.end();
});
});
I checked this data 10 times, it is correct and I can connect to the database through MySQL WorkBench
host: '127.0.0.1:3306',
user: 'root',
password: 'rootpassword',
database: 'main_backendless'
I will be grateful for any ideas that can be achieved!
I recommend using the cypress-mysql, which hides a lot of the implementation details for you.
If you try to roll your own task, you may end up with an undefined return value.
Install
npm install cypress-mysql
//or
yarn add cypress-mysql
Configure
The release notes are out of date, here is the configuration for Cypress 10+
// cypress.config.js
const { defineConfig } = require("cypress");
const mysql = require('cypress-mysql');
module.exports = defineConfig({
// ...
e2e: {
setupNodeEvents(on, config) {
mysql.configurePlugin(on);
},
"env": {
"db": {
"host": "localhost",
"user": "user",
"password": "password",
"database": "database"
}
}
})
// cypress/support/e2e.js
const mysql = require('cypress-mysql');
mysql.addCommands();
Test
const sql = 'UPDATE "main_backendless.Developer" SET "developerStatusId" = "1" WHERE ("email" = "testuser#example.com")'
cy.query(sql).then(res => {
expect(res.affectedRows).to.equal(1)
});
If you want to use a task to call the mySql library, you must return a Promise from the task.
This is because the mysql calls are asynchronous, and the only way Cypress knows to wait for them is to get a promise returned from your code.
cypress.config.js
const { defineConfig } = require("cypress")
const mysql2 = require('mysql2')
const connection = mysql2.createConnection({
host: '127.0.0.1:3306',
user: 'root',
password: 'rootpassword',
database: 'local1'
})
module.exports = defineConfig({
e2e: {
setupNodeEvents(on, config) {
on('task', {
mySql: (sql) => {
return new Promise((resolve, reject) => {
connection.query(sql, (error, results) => {
if (error) {
reject(error)
} else {
resolve(results.affectedRows)
})
})
})
}
})
},
})
it('tests with mysql', () => {
cy.task('mySql', 'sql staement here')
.then(result => {
expect(result).to.equal(1);
})
})
With Promise-wrapper
Alternatively, mysql2 provides a promise-wrapper that can simplify your code:
const { defineConfig } = require("cypress")
const mysql = require('mysql2/promise') // different import here
const connection = mysql2.createConnection({
host: '127.0.0.1:3306',
user: 'root',
password: 'rootpassword',
database: 'local1'
})
module.exports = defineConfig({
e2e: {
setupNodeEvents(on, config) {
on('task', {
mySql: async (sql) => { // async here
const result = await connection.execute(sql) // await here
return result;
}
})
},
})
The issue is that you're using a nodejs library with Cypress. Cypress tests execute inside a browser and cannot directly utilize nodejs libraries within a test.
To do what you want to need to create a cy.task() to be able to execute code in nodejs.
Assuming you're using js, create a file with a function to use the sql connector
// runSql.js
const mysql2 = require('mysql2');
function runSql(sql) {
const connection = mysql2.createConnection({
host: '127.0.0.1:3306',
user: 'root',
password: 'rootpassword',
database: 'local1'
});
connection.connect();
let rows;
connection.query(sql, (error, results) => {
if (error) throw error;
rows = results.affectedRows
});
connection.end();
return rows;
}
module.exports = runSql;
Then in the cypress.config.js file
const runSql = require('./runSql.js');
module.exports = defineConfig({
// ...
e2e: {
setupNodeEvents(on, config) {
on('task', {
runSql
});
},
});
Now to call the task in a test
describe('Registration page', () => {
it('test', () => {
const sql = 'UPDATE `main_backendless`.`Developer` SET `developerStatusId` = "1" WHERE (`email` = "testuser#example.com")';
cy.task('runSql', sql).then((rows) => {
expect(rows).to.equal(1);
});
});
});
I am having trouble with pooling mysql connections with nodeJS. As I understand it, when creating a pool, I should be able to use pool.query() to:
Get a connection
Run my query
Release the connection
However, the issue I am running into is the connections remain open and "sleeps" until the server closes the connection itself (60 seconds). Is there something I am doing wrong here? For context, I am connecting to Heroku clearDB and am viewing the connection on its dashboard.
Example: Upon login, I make a query to check login credentials to authenticate, another query to fetch one set of data, and another query to fetch another set. After logging in, the server is left with 2 connection in "sleep" mode. They do not disconnect until 60 seconds expire.
Here is my db.js file:
const mysql = require("mysql");
const dbConfig = require("./db.config.js");
const connection = mysql.createPool({
host: dbConfig.HOST,
user: dbConfig.USER,
password: dbConfig.PASSWORD,
database: dbConfig.DB,
port: dbConfig.PORT,
multipleStatements: true,
connectionLimit: 10
})
module.exports = connection;
Here is how I am making queries:
const sql = require("./db.js"); //Our db connection
---OMITTED---
return new Promise((resolve, reject) => {
const select_user = "SELECT user_id, first_name, last_name, username, password, is_admin, is_active FROM users WHERE username = ? LIMIT 1"
const params = [username]
const user_pass = password
sql.query(select_user, params, (err, res) => {
if (res) {
if (res.length) {
const user = res[0]
const hash = user.password
const is_active = user.is_active
if (is_active) {
bcrypt.compare(user_pass, hash).then(res => {
if (res) {
resolve({ result: true, info: { fname: user.first_name, lname: user.last_name, username: user.username, is_admin: user.is_admin, user_id: user.user_id } })
} else {
reject({ result: false, msg: "wrong_creds" })
}
}).catch(err => {
reject({ result: false, msg: err })
})
} else {
reject({ result: false, msg: "inactive" })
}
} else {
reject({ result: false, msg: "user_not_exist" })
}
} else {
console.log("MYSQL QUERY, COULD NOT SELECT USER FOR AUTHENTICATION")
reject({ result: false, msg: err })
}
})
})
I'm under the impression that pool.query() will close the connection for me after each query. Is this not the case? Am I missing something once the query has been completed?
I have also tried the manual way:
const select_string = "SELECT * FROM user_backlogs WHERE movie_id = ? AND user_id = ?"
const params = [movie_id, user_id]
sql.getConnection((err, connection) => {
if (err) reject(err)
else {
connection.query(select_string, params, (err, res) => {
connection.release()
if (err) reject(err)
else {
if (res.length) {
resolve([movie_id, "exists", res[0]["added_on"]])
} else {
resolve([movie_id, "not_exists"])
}
}
})
}
})
But still, the connections remain up until the server kicks them off. Any help is greatly appreciated.
The pool will not close the connection. It will release the connection, which me means it can be reused by another query. The pool does this to optimize performance, since it takes time to open a new connection.
If you want to close the connection after use, you can explicitly destroy it (connection.destroy()) and the pool will create a new one the next time you issue a query. You can find more information on this in the documentation under pooling connections.
I have an API server which runs different API calls to a mysql database: this is the file which entirely handles the connections and the queries:
const mysql = require('mysql')
const pool = mysql.createPool({
host: sqlhost,
port: sqlport,
user: sqluser,
password: sqlpsw,
database: sqldb,
charset: 'utf8mb4',
connectionLimit: 10
})
module.exports = {
get: function (sqlQuery, type) {
return new Promise((resolve, reject) => {
pool.getConnection((err, connection) => {
if (err) reject(new Error(505))
else {
connection.query(sqlQuery, function (err, results) {
connection.release()
if (err !== null) reject(new Error(503))
else resolve(results)
})
}
})
})
}
}
After some time I get the Error: ER_CON_COUNT_ERROR: Too many connections.
I know i may have used pool.query instead of the flow pool.getConnection --> connection.query --> connection.release but the result is (I guess) the same.
I have no idea what is causing the "connections leak". Any idea? Thanks
I'm trying to pull out the object/array myResults and I've tried as many methods as I can to pull this out but am unable to, can someone please help?
var express = require('express');
var router = express.Router();
var mysqlx = require('#mysql/xdevapi');
router.post('/email/:email', function (req, res, next){
let p_email = req.params.email
console.log (p_email)
mysqlx
.getSession( {
user: 'user',
password: 'password',
host: 'localhost',
port: '33060',
})
.then(function(session) {
const oracleDb = session.getSchema('nms2019local');
var myTable = oracleDb.getTable('operators');
myTable
.select (['email','password', 'admin'])
.where('email like :email')
.bind('email', p_email)
.execute(function (row) {
console.log(row);
const foundEmail = row[0]
const password = row[1]
const admin = row[2]
const myResults = {
foundEmail: foundEmail,
password: password,
admin: admin }
console.log(myResults);
return (myResults, console.log(myResults));
})
.catch(function(err) {
console.log(err);
})
return (myResults)
})
res.send('Oracle5 is here');
});
module.exports = router;
This is what nodemon shows:
test#email.com
POST /oracle5/email/test#email.com 200 14.621 ms - 15
[
'test#email.com',
'password',
1
]
{
foundEmail: 'test#email',
password: 'password',
admin: 1
}
[
{
getWarnings: [Function: getWarnings],
getWarningsCount: [Function: getWarningsCount],
fetchAll: [Function: fetchAll],
fetchOne: [Function: fetchOne],
getColumns: [Function: getColumns],
getResults: [Function: getResults],
nextResult: [Function: nextResult],
toArray: [Function: toArray]
}
It is probably something really simple but I've tried several iterations and just can't get the right combination to get the array out as an object. Also there is a .this() function that seems like it would be the best for what I'm trying to do but the documentation is horrid so if please give some information on that function as well it would be greatly appreciated.
You are using what I like to call a push-based cursor, where you provide a callback when calling execute().
This is useful for making sure that data not buffered at the driver/connector level. However, it uses a weird pattern and as soon as you want to buffer the result set at the application level, it requires you to keep track of multiple contexts, the one from the callback you provide (called whenever a row of the result set becomes available), and the one within the then() handler (called once the operation finishes) and share state between those contexts to make sure you have consistent results.
In this case, you can, for instance, declare a variable accessible in the scope of both contexts that will keep that shared state. That state is populated while processing the result set, and the final state can be returned back whenever the operation finishes.
mysqlx.getSession({ user: 'user', password: 'password', host: 'localhost', port: '33060' })
.then(function (session) {
const oracleDb = session.getSchema('nms2019local');
const myTable = oracleDb.getTable('operators');
// the variable should be accessible within the callback and the `then()` handler
const myResults = [];
myTable.select(['email', 'password', 'admin'])
.where('email like :email')
.bind('email', p_email)
.execute(function (row) {
// populate the state
myResults.push({ foundEmail: row[0], password: row[1], admin: row[2] });
})
.then(() => {
// return the final state
return next(myResults);
})
.catch(function(err) {
console.log(err);
});
});
I'm not sure about which version of the connector you are using, but since 8.0.18, you have an alternative (and probably more reasonable) interface. You can drop the callback provided to execute() and the client will automatically switch to a pull-based cursor mode, accessible in the then() handler. Which does not require to keep track of any state or context.
So, in that case, you can now have something like:
mysqlx.getSession({ user: 'user', password: 'password', host: 'localhost', port: '33060' })
.then(function (session) {
const oracleDb = session.getSchema('nms2019local');
const myTable = oracleDb.getTable('operators');
myTable.select(['email', 'password', 'admin'])
.where('email like :email')
.bind('email', p_email)
.execute()
.then(res => {
const myResults = res.fetchAll()
.map(function (row) {
return { foundEmail: row[0], password: row[1], admin: row[2] };
});
return next(myResults);
})
.catch(function(err) {
console.log(err);
});
});
Disclaimer: I'm the lead dev of the MySQL X DevAPI Connector/Node.js