Node JS Express - Oracle Connection Pooling (ORA-24418: Cannot open further sessions) - sql

I'm having issues with a the Oracle DB module:
https://github.com/oracle/node-oracledb/blob/master/doc/api.md
I have an application which has between 300 and 900 hits per hour (normally from around 100 users). The application has many $.post requests in the background to retrieve information from a database and display to the user.
I've recently switched to this module as it is Oracle's own (I was using https://github.com/joeferner/node-oracle previously).
Here's how I have it set out:
/bin/www
oracledb.createPool(
{
user : 'USER'
password : 'PASS',
connectString : 'DB:1521/SID:POOLED',
connectionClass : 'ARBITRARY_NAME',
poolMin : 1,
poolMax : 50,
poolTimeout : 300
},
function(err, pool)
{
if (err) {
console.log(err);
}
require('../libs/db')(pool); // Export pool to separate file
}
)
/libs/db.js
module.exports = function(pool) {
// Require oracle module for formatting
var oracledb = require('oracledb');
// Export acquire and query function
module.exports.acquire_and_query = function(sql, params, callback){
// ACQUIRE connection from pool
pool.getConnection(function(err, connection){
// NUMBER OF CONNCETIONS OPEN
console.log("ORACLE: CONNX OPEN: " + pool.connectionsOpen);
// NUMBER OF CONNEXTIONS IN USE
console.log("ORACLE: CONNX IN USE: " + pool.connectionsInUse);
if (err) {
console.log(err.message);
return;
}
// Use connection to QUERY db and return JSON object
connection.execute(sql, params, {maxRows: 1000, isAutoCommit: true, outFormat: oracledb.OBJECT}, function(err, result){
// Error Handling
if (err) {
console.log(err.message); // Log the error
return false; // Return false for our error handling
}
// Release the connection back to the pool
connection.release(function(err) {
if (err) {
console.log(err.message);
return;
}
})
// Return callback with rowset first, out bind paramaters second
return callback(result.rows, result.outBinds, result.rowsAffected);
})
})
}
}
This module "acquire_and_query" is called from with in our application, and has SQL and it's params passed in to be executed.
The Oracle DB has a maximum allowed of Pooled connections set to 80 (and we are not exceeding them) - and generally looks pretty happy.
The node application however is constantly throwing out an ORA-24418: Cannot open further sessions, and I am unsure how to resolve this.
Thanks.

Resolved - Issue: My poor coding!
I had unwittingly set a Socket.IO event to update the view for EVERYONE connected multiple times (rather than querying the database once, then sending the view over the socket...) sigh
I was also even more stupidly using a for loop in a transaction based query (which was INSERTing multiple data on each run)... Once I had changed this to a recursive pattern - it went ran swimmingly!
Good article here about for loops and recursive patterns: http://www.richardrodger.com/2011/04/21/node-js-how-to-write-a-for-loop-with-callbacks/#.VaQjJJNViko
Anyway - here's what I use now (and it works rather well)
Using node-oracledb v0.6 (https://github.com/oracle/node-oracledb) and Express 4 (http://expressjs.com/)
bin/www
/**
* Database
*/
// AS PER DOCUMENTATION: https://github.com/oracle/node-oracledb/blob/master/examples/dbconfig.js
var dbconfig = require("../libs/dbconfig.js");
oracledb.connectionClass = dbconfig.connectionClass,
oracledb.createPool({
user: dbconfig.user,
password: dbconfig.password,
connectString: dbconfig.connectString,
poolMax: 44,
poolMin: 2,
poolIncrement: 5,
poolTimeout: 4
}, function(err, pool) {
if (err) {
console.log("ERROR: ", new Date(), ": createPool() callback: " + err.message);
return;
}
require('../libs/oracledb.js')(pool);
});
libs/oracledb.js
module.exports = function(pool) {
////////////////////////////
// INSTANTIATE THE DRIVER //
////////////////////////////
var oracledb = require("oracledb");
//////////////////////
// GET A CONNECTION //
//////////////////////
var doConnect = function(callback) {
console.log("INFO: Module getConnection() called - attempting to retrieve a connection using the node-oracledb driver");
pool.getConnection(function(err, connection) {
// UNABLE TO GET CONNECTION - CALLBACK WITH ERROR
if (err) {
console.log("ERROR: Cannot get a connection: ", err);
return callback(err);
}
// If pool is defined - show connectionsOpen and connectionsInUse
if (typeof pool !== "undefined") {
console.log("INFO: Connections open: " + pool.connectionsOpen);
console.log("INFO: Connections in use: " + pool.connectionsInUse);
}
// Else everything looks good
// Obtain the Oracle Session ID, then return the connection
doExecute(connection, "SELECT SYS_CONTEXT('userenv', 'sid') AS session_id FROM DUAL", {}, function(err, result) {
// Something went wrong, releae the connection and return the error
if (err) {
console.log("ERROR: Unable to determine Oracle SESSION ID for this transaction: ", err);
releaseConnection(connection);
return callback(err);
}
// Log the connection ID (we do this to ensure the conncetions are being pooled correctly)
console.log("INFO: Connection retrieved from the database, SESSION ID: ", result.rows[0]['SESSION_ID']);
// Return the connection for use in model
return callback(err, connection);
});
});
}
/////////////
// EXECUTE //
/////////////
var doExecute = function(connection, sql, params, callback) {
connection.execute(sql, params, { autoCommit: false, outFormat: oracledb.OBJECT, maxRows:1000 }, function(err, result) {
// Something went wrong - handle the data and release the connection
if (err) {
console.log("ERROR: Unable to execute the SQL: ", err);
//releaseConnection(connection);
return callback(err);
}
// Return the result to the request initiator
// console.log("INFO: Result from Database: ", result)
return callback(err, result);
});
}
////////////
// COMMIT //
////////////
var doCommit = function(connection, callback) {
connection.commit(function(err) {
if (err) {
console.log("ERROR: Unable to COMMIT transaction: ", err);
}
return callback(err, connection);
});
}
//////////////
// ROLLBACK //
//////////////
var doRollback = function(connection, callback) {
connection.rollback(function(err) {
if (err) {
console.log("ERROR: Unable to ROLLBACK transaction: ", err);
}
return callback(err, connection);
});
}
//////////////////////////
// RELEASE A CONNECTION //
//////////////////////////
var doRelease = function(connection) {
connection.release(function(err) {
if (err) {
console.log("ERROR: Unable to RELEASE the connection: ", err);
}
return;
});
}
//////////////////////////////
// EXPORT THE FUNCTIONALITY //
//////////////////////////////
module.exports.doConnect = doConnect;
module.exports.doExecute = doExecute;
module.exports.doCommit = doCommit;
module.exports.doRollback = doRollback;
module.exports.doRelease = doRelease;
}
Example Usage
//////////////////////////////
// REQUIRE RELEVANT MODULES //
//////////////////////////////
var db = require("../libs/oracledb.js");
var oracledb = require('oracledb');
var sql = "";
///////////////////////////
// RETRIEVE CURRENT DATE //
///////////////////////////
module.exports.getCurDate = function(callback) {
sql = "SELECT CURRENT_DATE FROM DUAL";
db.doConnect(function(err, connection){
console.log("INFO: Database - Retrieving CURRENT_DATE FROM DUAL");
if (err) {
console.log("ERROR: Unable to get a connection ");
return callback(err);
} else {
db.doExecute(
connection, sql
, {} // PASS BIND PARAMS IN HERE - SEE ORACLEDB DOCS
, function(err, result) {
if (err) {
db.doRelease(connection); // RELEASE CONNECTION
return callback(err); // ERROR
} else {
db.doRelease(connection); // RELEASE CONNECTION
return callback(err, result.rows); // ALL IS GOOD
}
}
);
}
});
}

This error message is raised when the sessMax parameter supplied in OCISessionPoolCreate has been reached.
So, my first move would be verify if database sessions are being closed correctly.
When this error message arises execute the following three actions:
1.- (using sqlplus) show parameter sess
2.- (using sqlplus)
select username,machine,program,count(*) from v$session
group by username,machine ,program
order by 4 ;
3.- verify in alert.log if there are any other ORA- messages during this event.
Did you perform this steps ? (share your results)

Related

Node.js wait("await") for SQL database query before proceeding

I have spent a lot of time reading up on this but I simply don't get how to solve it.
I have an application that uses a token that is stored in a SQL database. I need that token before the application can proceed.
I'm trying to solve it with "await" but it doesn't work. The SQL query result is still retrieved "too late".
const pool = mysql.createPool({
user : 'xxxx', // e.g. 'my-db-user'
password : "xxxx", // e.g. 'my-db-password'
database : "xxxx", // e.g. 'my-database'
// If connecting via localhost, specify the ip
host : "xxxx"
// If connecting via unix domain socket, specify the path
//socketPath : `/cloudsql/xxxx`,
});
const isAuthorized = async (userId) => {
let query = "SELECT * FROM auth WHERE id = 2";
await pool.query(query, (error,results) => {
if (!results[0]) {
console.log("No results");
return
} else {
tokenyay=results[0].refreshtoken;
console.log("results: "+results[0].refreshtoken);
return results[0].refreshtoken;
}
});
await console.log("tokenyay: "+tokenyay);
if (tokenyay != null && tokenyay != '') {
refreshTokenStore[userId] = tokenyay;
}
console.log(userId);
console.log(refreshTokenStore[userId] ? true : false);
return refreshTokenStore[userId] ? true : false;
};
I don't know what your pool is, but judging from the callback, we can see that pool.query() method is not await-able.
You can manually create a Promise for it, though, which is await-able, for example
await new Promise((resolve, reject) => {
pool.query(query, (error, results) => {
if (error) reject(error);
if (!results[0]) {
console.log("No results");
resolve(); // give `undefined` to the `await...` and make it stop waiting
return;
} else {
tokenyay = results[0].refreshtoken;
console.log("results: " + results[0].refreshtoken);
resolve(results[0].refreshtoken);
}
})
});
Edit:
However, since the result of await is obtained from the value passed to the resolve, we don't need to assign tokenyay inside of the callback.
We can use tokenyay = await... instead.
tokenyay = await new Promise((resolve, reject) => {
pool.query(query, (error, results) => {
if (error) reject(error);
if (!results[0]) {
console.log("No results");
resolve(); // give `undefined` to the `await...` and make it stop waiting
return;
} else {
console.log("results: " + results[0].refreshtoken);
resolve(results[0].refreshtoken);
}
})
});

Query works but cant retrieve the data

I am new to Node.js (3 days total experience). I am using Node.js and the tedious package to query a database (azure SQL). I use the example as explained here: https://learn.microsoft.com/en-us/azure/azure-sql/database/connect-query-nodejs?tabs=macos
const connection = new Connection(config);
// Attempt to connect and execute queries if connection goes through
connection.on("connect", err => {
if (err) {
console.error(err.message);
} else {
console.log("Reading rows from the Table...");
// Read all rows from table
const request = new Request(
"SELECT * FROM clients",
(err, rowCount, columns) => {
if (err) {
console.error(err.message);
} else {
console.log(`${rowCount} row(s) returned`);
}
}
);
request.on("row", columns => {
columns.forEach(column => {
console.log("%s\t%s", column.metadata.colName, column.value);
});
});
connection.execSql(request);
}
});
I have two issues:
I do not know how to get the queried data into an object and
If I run the script it does print the items to the console, but it doesn't close the connection after it has done so. If I add a connection.close() at the bottom, it will close the connection before its done. I get the feeling that node.js executes everything at the same time (I am used to Python..).
Update
I found a way to close the connection, to my understanding the request object has several "events" that are predefined by the library. It seems I need to add the event "done" through request.on('done', ...) in order to make sure that it can even BE done. My updated code looks like this:
var connection = new Connection(config);
connection.connect(function(err) {
// If no error, then good to go...
executeStatement();
}
);
connection.on('debug', function(text) {
//remove commenting below to get full debugging.
//console.log(text);
}
);
function executeStatement() {
request = new Request("SELECT * FROM clients", function(err, rowCount) {
if (err) {
console.log(err);
} else {
console.log(rowCount + ' rows');
}
connection.close();
});
request.on('row', function(rows) {
_.forEach(rows, function(value, collection){
console.log(value)
console.log(value.value);
console.log(value.metadata.colName)
console.log(collection)
})
});
request.on('done', function(rowCount, more) {
console.log(rowCount + ' rows returned');
});
// In SQL Server 2000 you may need: connection.execSqlBatch(request);
connection.execSql(request);
}
Anyways, your help would be much appreciated!
Regards
Pieter
The package tedious is synchronous package, it uses the callback to return results. So when we call connection.close(), it will disable connection and stop the callback function. If will want to close the connection, I suggest you use async package to implement it.
For example
const { Connection, Request } = require("tedious");
const async = require("async");
const config = {
authentication: {
options: {
userName: "username", // update me
password: "password", // update me
},
type: "default",
},
server: "your_server.database.windows.net", // update me
options: {
database: "your_database", //update me
encrypt: true,
validateBulkLoadParameters: true,
},
};
const connection = new Connection(config);
let results=[]
function queryDatabase(callback) {
console.log("Reading rows from the Table...");
// Read all rows from table
const request = new Request("SELECT * FROM Person", (err, rowCount) => {
if (err) {
callback(err);
} else {
console.log(`${rowCount} row(s) returned`);
callback(null);
}
});
request.on("row", (columns) => {
let result={}
columns.forEach((column) => {
result[column.metadata.colName]=column.value
console.log("%s\t%s", column.metadata.colName, column.value);
});
// save result into an array
results.push(result)
});
connection.execSql(request);
}
function Complete(err, result) {
if (err) {
callback(err);
} else {
connection.close();
console.log("close connection");
}
}
connection.on("connect", function (err) {
if (err) {
console.log(err);
} else {
console.log("Connected");
// Execute all functions in the array serially
async.waterfall([queryDatabase], Complete);
}
});
connection.connect();
Besides, you also can use the package mssql. It supports asynchronous methods and depends on package tedious. We can directly call close after querying.
For example
const mssql = require("mssql");
const config = {
user: "username",
password: "password",
server: "your_server.database.windows.net",
database: "your_database",
options: {
encrypt: true,
enableArithAbort: true,
},
};
let pool = new mssql.ConnectionPool(config);
async function query() {
try {
await pool.connect();
const request = pool.request();
const result = await request.query("SELECT * FROM Person");
console.dir(result.recordset);
await pool.close();
console.log(pool.connected);
} catch (error) {
throw error;
}
}
query().catch((err) => {
throw err;
});
You can custom a class first and declare an Array to save ojects such as:
let sales = new Array();
class SalesLT{
constructor(catagryName,productName){
this.catagryName = catagryName;
this.productName = productName;
}
Here my sql statement returns 2 properties, so every time the loop takes out two elements from the ColumnValue[].
request.on("row", columns => {
for(let i=0; i<columns.length; i=i+2){
let sale = new SalesLT(columns[i].value,columns[i+1].value);
sales.push(sale);
}
sales.forEach( item => {
console.log("%s\t%s",item.catagryName, item.productName)
})
});
The code is as follows:
const { Connection, Request } = require("tedious");
let sales = new Array();
class SalesLT{
constructor(catagryName,productName){
this.catagryName = catagryName;
this.productName = productName;
}
}
// Create connection to database
const config = {
authentication: {
options: {
userName: "<***>", // update me
password: "<***>" // update me
},
type: "default"
},
server: "<****>.database.windows.net", // update me
options: {
database: "<***>", //update me
encrypt: true
}
};
const connection = new Connection(config);
// Attempt to connect and execute queries if connection goes through
connection.on ("connect", err => {
if (err) {
console.error(err.message);
} else {
queryDatabase();
}
});
function queryDatabase() {
console.log("Reading rows from the Table...");
// Read all rows from table
const request = new Request(
`SELECT TOP 2 pc.Name as CategoryName,
p.name as ProductName
FROM [SalesLT].[ProductCategory] pc
JOIN [SalesLT].[Product] p ON pc.productcategoryid = p.productcategoryid`,
(err, rowCount) => {
if (err) {
console.error(err.message);
} else {
console.log(`${rowCount} row(s) returned`);
}
connection.close();
}
);
request.on("row", columns => {
for(let i=0; i<columns.length; i=i+2){
let sale = new SalesLT(columns[i].value,columns[i+1].value);
sales.push(sale);
}
sales.forEach( item => {
console.log("%s\t%s",item.catagryName, item.productName)
})
});
connection.execSql(request);
}
this article should help you, to solve all the issues you are facing...which were the same I had when I started using Node :)
https://devblogs.microsoft.com/azure-sql/promises-node-tedious-azure-sql-oh-my/

Connecting SQL Database from NodeJs - ConnectionError: Connection not yet open

I am trying to connect SQL database from NodeJs, but getting this error:
Error while querying database :- ConnectionError: Connection not yet open.
var executeQuery = function(res, query) {
var conn = new sql.ConnectionPool(dbConfig);
conn.connect().then(function () {
// create Request object
var request = new sql.Request(conn);
// query to the database
request.query(query, function (err, queryResult) {
if (err) {
console.log("Error while querying database :- " + err);
res.send(err);
} else {
res.send(queryResult);
}
});
conn.close();
})
}
Please help.
Thank You
James
The connection pool class will return a promise, which resolves with the pool that was initiated by your connection :
var executeQuery = function(res, query) {
var conn = new sql.ConnectionPool(dbConfig);
conn.connect()
.then(function (pool) {
// ^ the pool that is created and should be used
// create Request object
var request = new sql.Request(pool);
// ^ the pool from the promise
// query to the database
request.query(query, function (err, queryResult) {
if (err) {
console.log("Error while querying database :- " + err);
res.send(err);
} else {
res.send(queryResult);
}
});
conn.close();
});
}
In your case conn variable is always the promise and not the connection itself.
A basic example of a connection pool from the documentation is as follows :
new sql.ConnectionPool(config).connect().then(pool => {
return pool.query`select * from mytable where id = ${value}`
}).then(result => {
console.dir(result)
}).catch(err => {
// ... error checks
})

Node.js mssql Global connection already exists connection pool memory leak

New to Node. I have looked at some other questions, and haven't found a direct answer. I'm creating a module for a simple Sql data call, but am getting the "Global connection already exists" error. I believe my solution lies in using a connectionPool, but I am uncertain of how to create a connection pool or if I'm doing it correctly. Any help is appreciated. I have removed any sensitive login data as well.
UPDATE: by switching my sql.close() and putting it before my onDone&&onDone() callback function, I now am receiving the correct data, but node is now giving warnings of " Warning: Possible EventEmitter memory leak detected. 11 error listeners added. Use emitter.setMaxListeners() to increase limit"
What's not working: I can successfully run my first call, but then every call after that returns nothing. I have verified using SQL profiler that only one call is being made to the server. So I believe something in my code is bypassing the second call and returning a null result set?
const self = {};
// NPM package that supports accessing Microsoft SQL databases from node.
let sql = require('mssql');
let debug = false;
let connection;
// configure SQL Connection. The credentials below should be handled more securely.
// While this backend code is not accessible via public web accesses, it will be in
// GitHub.
let config = {
domain: "hidden",
user: "hidden",
password: 'hidden',
server: 'hidden',
database: 'hidden',
options: {
encrypt: false // Use this if you're on Windows Azure
}
};
self.getSQLData = function getSQLData (sprocInfo,onDone,onRow) {
// Connect to SQL Server.
if(debug) console.log("Hit SQL Function \n");
if (typeof(sprocInfo.parameters) === 'object' && Array.isArray(sprocInfo.parameters)){
if(debug)console.log("Passed array check");
if(sprocInfo.database) config.database = sprocInfo.database;
if(connection && connection._connected){
if(debug) console.log("Inside SQL Connection");
const request = new sql.Request(connection); //create a new request
request.stream = true;
var results = [];
//put all parameters as input parameters to sql call
sprocInfo.parameters.forEach(function(element){
request.input(element.parameterName.toString(),sql.NVarChar,element.parameterValue);
//NEED to remove hard typing of Integers. Maybe case statement based on typeof??
});
request.on('row', row => {
results.push(row);
onRow && onRow(row);
});
request.on('done', result => {
// console.log("Result in request.on done"+JSON.stringify(result));
onDone && onDone(results);
sql.close();
});
request.on('error', err => {
// May be emitted multiple times
if ( debug ) console.log('error');
if ( debug ) console.log('the error is: ' + err);
});
request.execute(sprocInfo.procedure)
} else {
connection = sql.connect(config, err => {
if(debug) console.log("Inside SQL Connection");
const request = new sql.Request(); //create a new request
request.stream = true;
var results = [];
//put all parameters as input parameters to sql call
sprocInfo.parameters.forEach(function(element){
request.input(element.parameterName.toString(),sql.NVarChar,element.parameterValue);
//NEED to remove hard typing of Integers. Maybe case statement based on typeof??
});
request.on('row', row => {
results.push(row);
onRow && onRow(row);
});
request.on('done', result => {
// console.log("Result in request.on done"+JSON.stringify(result));
onDone && onDone(results);
sql.close();
});
request.on('error', err => {
// May be emitted multiple times
if ( debug ) console.log('error');
if ( debug ) console.log('the error is: ' + err);
});
request.execute(sprocInfo.procedure)
});
}
sql.on('error', err => {
console.log("Error: Failed to connect to SQL Server");
});
} else {
throw new Error ("Parameters is not an array");
}
};
module.exports = self;

Node js: mssql [ConnectionError: Connection is closed.] name: 'ConnectionError', message: 'Connection is closed.', code: 'ECONNCLOSED'

i am getting error in npm mssql 3.0.0 with sqlserver 2012
i am creating single page application where i used restful using express .
there are 4 method which executing the query and returning the data to response.
for each method i am opening the connection and closing the connection.
but when savedquery is calling then connection close error occurs.
each method code is similar to savedquery method (copy pasted code only queries are changed) but they are executing savedquery is not executing
{ [ConnectionError: Connection is closed.]
name: 'ConnectionError',
message: 'Connection is closed.',
code: 'ECONNCLOSED' }
var savedquery=function(req,res){
dbConfig= {
user: 'XXX',
password: 'XXXXXXXXXX',
server: 'localhost', // You can use 'localhost\\instance' to connect to named instance
database: 'DEMO_ODS',
options: {
encrypt: true
}
};
sql.connect(dbConfig).then(function (err) {
var sqlrequest = new sql.Request();
sqlrequest.query("SELECT * from SavedQuery").then(function (recordset) {
sql.close(function (value) {
console.log("connection6 closed");
});
return res.status(200).send(recordset);
}).catch(function (err) {
console.log(err);
});
}).catch(function (err) {
console.log(err);
});
};
}
I know it is an old questionm but this answer is for the others who are facing the same isue. I had the same problem, What I did is, used promises as below.
function getData() {
try {
sqlInstance.connect(setUp)
.then(function () {
// Function to retrieve all the data - Start
new sqlInstance.Request()
.query("select * from Course")
.then(function (dbData) {
if (dbData == null || dbData.length === 0)
return;
console.dir('All the courses');
console.dir(dbData);
})
.catch(function (error) {
console.dir(error);
});
// Function to retrieve all the data - End
// To retrieve specicfic data - Start
var value = 1;
new sqlInstance.Request()
.input("param", sqlInstance.Int, value)
.query("select * from Course where CourseID = #param")
.then(function (dbData) {
if (dbData == null || dbData.length === 0)
return;
console.dir('Course with ID = 1');
console.dir(dbData);
})
.catch(function (error) {
console.dir(error);
});
// To retrieve specicfic data - End
}).catch(function (error) {
console.dir(error);
});
} catch (error) {
console.dir(error);
}
}
This solved my issue. You can find the fix here.
You should remove
options: {
encrypt: true
}
from your dbConfig
I just use promise to handle concurrent request:
const executeQuery = function (res, query, dbName) {
dbConfig = {
user: "********",
password: "********",
server: "*******",
database: dbName
}
sql.connect(dbConfig).then(pool => {
return pool.request().query(query)
}).then(result => {
res.send(result);
}).catch(err => {
res.send(err);
});
}
Hope it's help someone.