Reliably reconnect to MongoDB - mongodb

UPDATE: I am using the 2.1 version on the driver, against 3.2
I have a node application that uses MongoDB. The problem I have is that if the MongoDB server goes down for any reason, the application doesn't reconnect.
To get this right, I based my tests on the code in this official tutorial.
var MongoClient = require('mongodb').MongoClient
, f = require('util').format;
MongoClient.connect('mongodb://localhost:27017/test',
// Optional: uncomment if necessary
// { db: { bufferMaxEntries: 3 } },
function(err, db) {
var col = db.collection('t');
setInterval(function() {
col.insert({a:1}, function(err, r) {
console.log("insert")
console.log(err)
col.findOne({}, function(err, doc) {
console.log("findOne")
console.log(err)
});
})
}, 1000)
});
The idea is to run this script, and then stop mongod, and then restart it.
So, here we go:
TEST 1: stopping mongod for 10 seconds
Stopping MongoDb for 10 seconds does the desired result: it will stop running the queries for those 10 seconds, and then will run all of them once the server is back ip
TEST 2: stopping mongod for 30 seconds
After exactly 30 seconds, I start getting:
{ [MongoError: topology was destroyed] name: 'MongoError', message: 'topology was destroyed' }
insert
{ [MongoError: topology was destroyed] name: 'MongoError', message: 'topology was destroyed' }
The trouble is that from this on, when I restart mongod, the connection is not re-establised.
Solutions?
Does this problem have a solution? If so, do you know what it is?
Once my app starts puking "topology was destroyed", the only way to get everything to work again is by restarting the whole app...

There are 2 connection options that control how mongo nodejs driver reconnects after connection fails
reconnectTries: attempt to reconnect #times (default 30 times)
reconnectInterval: Server will wait # milliseconds between retries
(default 1000 ms)
reference on mongo driver docs
Which means that mongo will keep trying to connect 30 times by default and wait 1 second before every retry. Which is why you start seeing errors after 30 seconds.
You should tweak these 2 parameters based on you needs like this sample.
var MongoClient = require('mongodb').MongoClient,
f = require('util').format;
MongoClient.connect('mongodb://localhost:27017/test',
{
// retry to connect for 60 times
reconnectTries: 60,
// wait 1 second before retrying
reconnectInterval: 1000
},
function(err, db) {
var col = db.collection('t');
setInterval(function() {
col.insert({
a: 1
}, function(err, r) {
console.log("insert")
console.log(err)
col.findOne({}, function(err, doc) {
console.log("findOne")
console.log(err)
});
})
}, 1000)
});
This will try 60 times instead of the default 30, which means that you'll start seeing errors after 60 seconds when it stops trying to reconnect.
Sidenote: if you want to prevent the app/request from waiting until the expiration of the reconnection period you have to pass the option bufferMaxEntries: 0. The price for this is that requests are also aborted during short network interruptions.

package.json: "mongodb": "3.1.3"
Reconnect existing connections
To fine-tune the reconnect configuration for pre-established connections, you can modify the reconnectTries/reconnectInterval options (default values and further documentation here).
Reconnect initial connection
For the initial connection, the mongo client does not reconnect if it encounters an error (see below). I believe it should, but in the meantime, I've created the following workaround using the promise-retry library (which uses an exponential backoff strategy).
const promiseRetry = require('promise-retry')
const MongoClient = require('mongodb').MongoClient
const options = {
useNewUrlParser: true,
reconnectTries: 60,
reconnectInterval: 1000,
poolSize: 10,
bufferMaxEntries: 0
}
const promiseRetryOptions = {
retries: options.reconnectTries,
factor: 1.5,
minTimeout: options.reconnectInterval,
maxTimeout: 5000
}
const connect = (url) => {
return promiseRetry((retry, number) => {
console.log(`MongoClient connecting to ${url} - retry number: ${number}`)
return MongoClient.connect(url, options).catch(retry)
}, promiseRetryOptions)
}
module.exports = { connect }
Mongo Initial Connect Error: failed to connect to server [db:27017] on first connect

By default the Mongo driver will try to reconnect 30 times, one every second. After that it will not try to reconnect again.
You can set the number of retries to Number.MAX_VALUE to keep it reconnecting "almost forever":
var connection = "mongodb://127.0.0.1:27017/db";
MongoClient.connect(connection, {
server : {
reconnectTries : Number.MAX_VALUE,
autoReconnect : true
}
}, function (err, db) {
});

With mongodb driver 3.1.10, you can set up your connection as
MongoClient.connect(connectionUrl, {
reconnectInterval: 10000, // wait for 10 seconds before retry
reconnectTries: Number.MAX_VALUE, // retry forever
}, function(err, res) {
console.log('connected')
})
You do not have to specify autoReconnect: true as that's the default.

It's happening because it might have crossed the retry connection limit. After number of retries it destroy the TCP connection and become idle. So for it increase the number of retries and it would be better if you increase the gap between connection retry.
Use below options:
retryMiliSeconds {Number, default:5000}, number of milliseconds between retries.
numberOfRetries {Number, default:5}, number of retries off connection.
For more details refer to this link https://mongodb.github.io/node-mongodb-native/driver-articles/mongoclient.html
Solution:
MongoClient.connect("mongodb://localhost:27017/integration_test_?", {
db: {
native_parser: false,
retryMiliSeconds: 100000,
numberOfRetries: 100
},
server: {
socketOptions: {
connectTimeoutMS: 500
}
}
}, callback)

Behavior may differ with different versions of driver. You should mention your driver version.
driver version : 2.2.10 (latest)
mongo db version : 3.0.7
Below code will extend the time mongod can take to come back up.
var MongoClient = require('mongodb').MongoClient
, f = require('util').format;
function connectCallback(err, db) {
var col = db.collection('t');
setInterval(function() {
col.insert({a:1}, function(err, r) {
console.log("insert")
console.log(err)
col.findOne({}, function(err, doc) {
console.log("findOne")
console.log(err)
});
})
}, 1000)
}
var options = { server: { reconnectTries: 2000,reconnectInterval: 1000 }}
MongoClient.connect('mongodb://localhost:27017/test',options,connectCallback);
2nd argument can be used to pass server options.

If you was using Mongoose for your Schemas, it would be worth considering my option below since mongoose was never retrying to reconnect to mongoDB implicitly after first attempt failed.
Kindly note I am connecting to Azure CosmosDB for MongoDB API. On yours maybe on the local machine.
Below is my code.
const mongoose = require('mongoose');
// set the global useNewUrlParser option to turn on useNewUrlParser for every connection by default.
mongoose.set('useNewUrlParser', true);
// In order to use `findOneAndUpdate()` and `findOneAndDelete()`
mongoose.set('useFindAndModify', false);
async function mongoDbPool() {
// Closure.
return function connectWithRetry() {
// All the variables and functions in here will Persist in Scope.
const COSMODDBUSER = process.env.COSMODDBUSER;
const COSMOSDBPASSWORD = process.env.COSMOSDBPASSWORD;
const COSMOSDBCONNSTR = process.env.COSMOSDBCONNSTR;
var dbAuth = {
auth: {
user: COSMODDBUSER,
password: COSMOSDBPASSWORD
}
};
const mongoUrl = COSMOSDBCONNSTR + '?ssl=true&replicaSet=globaldb';
return mongoose.connect(mongoUrl, dbAuth, (err) => {
if (err) {
console.error('Failed to connect to mongo - retrying in 5 sec');
console.error(err);
setTimeout(connectWithRetry, 5000);
} else {
console.log(`Connected to Azure CosmosDB for MongoDB API.`);
}
});
};}
You may decide to export and reuse this module everywhere you need to connect to db via Dependency Injection. But instead I will only show how to access the database connection for now.
(async () => {
var dbPools = await Promise.all([mongoDbPool()]);
var mongoDbInstance = await dbPools[0]();
// Now use "mongoDbInstance" to do what you need.
})();

Related

Exceeded timeout of 5000 ms for a test with Jest and MongoDB

I'm trying to implement database population by using a migration function. The code works perfectly, it saves all the data into the database, but the test for the function is failing, and now I would like to know why?
I'm getting the "Exceeded timeout of 5000 ms" error for this particular test. I've written 166 tests for this app and all of them are passing.
Here is the function I want to test:
const doMigration = async ({ model, data }) => {
await model.collection.insertMany(data)
}
And here is the test:
const { Amodel } = require('../../../models/Amodel')
const { doMigration } = require('../../../database/migrations')
describe('Database Population', () => {
it ('Should populate the database using migrations', async () => {
const data = [{ name: 'A' }, { name: 'B' }]
const model = Amodel
const migration = { name: 'Amodel', model, data }
await doMigration(migration)
const countAfter = await Amodel.count()
expect(countAfter).toBe(2)
})
})
In this test I simply import the function, the model and create a migration object that then is passed to the function.
What did I try?
Tried using just the countAfter without using the doMigration function, and it still generates the same timeout error.
Tried increasing the time for this test to 30000, failed with error saying that the mongodb time exceeded the 10000 ms.
Here is the github repository: https://github.com/Elvissamir/Fullrvmovies
What is happening, how can I solve this error?
The problem was the way the mongodb connection was handled. When testing, the app created a connection to the db on startup, and then the jest tests used that connection, that caused some issues.
The solution was to connect to the database on startup only if the environment is set to testing, otherwise the connection will be handled by each set of tests.
In each set I added a beforeAll and afterAll to open and close the connection to the database.
Hope it helps anyone that finds the same problem or has similar issues.
The orientation is that the message reflect the actual reason, So i recommand to follow the following steps:
use the following code to check mongo state:
const { MongoMemoryServer } = require("mongodb-memory-server");
const mongoose = require("mongoose");
(async () => {
mongod = await MongoMemoryServer.create();
const mongoUri = mongod.getUri();
await mongoose.connect(mongoUri, {
useNewUrlParser: true,
useUnifiedTopology: true,
}).then((result) => {
console.log(result.connection.readyState)
console.log(result.connection.host)
}).catch((err) => {
});;
})();
if you are using mongodb-memory-server add "testTimeout" attribute:
"jest": {
"preset": "ts-jest",
"testEnvironment": "node",
"setupFilesAfterEnv": [
"./src/test/setup.ts"
],
"testTimeout": 15000
},
If all above still huppens check the time-out of all inter-test operation

Is there way a can put timeout on mongodb server idle connections?

For different mongo drivers(nodejs , java), it provides a way to set idle time out and some other configs. I want to apply this on mongodb server, eg. in case of mysql i can set up idle connection timeout after that mysql server disconnects the connection irrespective of client timeout.
is there any such config in mongo?
You can pass timeout in option parameter as below:
var uri = ‘mongodb://[user:pass#]host[:27017]/[database]’;
var options = { server:
{ socketOptions:
{
socketTimeoutMS: SOCKET_TIME_OUT_MS,
connectTimeoutMS: CONNECTION_TIMEOUT_MS
}
}
};
MongoClient.connect(uri, options, function(err, db) {
if(!err) {
console.log("We are connected");
}
console.log("Error connecting DB");
});
[DO VOTE TO THIS ANSWER, IF ITS HELPFUL TO YOU]

AWS Lambda timing out on MongoDB Atlas connection

I'm just trying to write a simple Lambda function to insert data into my MongoDB Atlas cluster. I've set the cluster to accept all incoming traffic (0.0.0.0/0) and confirmed that I can connect locally.
For AWS Lambda, I set up a VPC using the VPC wizard, and I gave my Lambda function a security role with full admin access. I set the timeout to 12 seconds, but I'm still getting the following error:
Response:
{
"errorMessage": "2018-11-19T15:17:23.200Z 3048e1fd-ec0e-11e8-a03d-fb79584484c5 Task timed out after 11.01 seconds"
}
Request ID:
"3048e1fd-ec0e-11e8-a03d-fb79584484c5"
Function Logs:
START RequestId: 3048e1fd-ec0e-11e8-a03d-fb79584484c5 Version: $LATEST
2018-11-19T15:17:12.191Z 3048e1fd-ec0e-11e8-a03d-fb79584484c5 Calling MongoDB Atlas from AWS Lambda with event: {"address":{"street":"2 Avenue","zipcode":"10075","building":"1480","coord":[-73.9557413,40.7720266]},"borough":"Manhattan","cuisine":"Italian","grades":[{"date":"2014-10-01T00:00:00Z","grade":"A","score":11},{"date":"2014-01-16T00:00:00Z","grade":"B","score":17}],"name":"Vella","restaurant_id":"41704620"}
2018-11-19T15:17:12.208Z 3048e1fd-ec0e-11e8-a03d-fb79584484c5 => connecting to database
2018-11-19T15:17:12.248Z 3048e1fd-ec0e-11e8-a03d-fb79584484c5 (node:1) DeprecationWarning: current URL string parser is deprecated, and will be removed in a future version. To use the new parser, pass option { useNewUrlParser: true } to MongoClient.connect.
END RequestId: 3048e1fd-ec0e-11e8-a03d-fb79584484c5
REPORT RequestId: 3048e1fd-ec0e-11e8-a03d-fb79584484c5 Duration: 11011.08 ms Billed Duration: 11000 ms Memory Size: 128 MB Max Memory Used: 29 MB
2018-11-19T15:17:23.200Z 3048e1fd-ec0e-11e8-a03d-fb79584484c5 Task timed out after 11.01 seconds
The relevant part of my code for connecting is (with user and pass being the appropriate values):
const MongoClient = require('mongodb').MongoClient;
let atlas_connection_uri = "mongodb+srv://<user>:<pass>#restaurantcluster-2ylyf.gcp.mongodb.net/testdb"
let cachedDb = null;
exports.handler = (event, context, callback) => {
var uri = atlas_connection_uri
if (atlas_connection_uri != null) {
processEvent(event, context, callback);
}
else {
atlas_connection_uri = uri;
console.log('the Atlas connection string is ' + atlas_connection_uri);
processEvent(event, context, callback);
}
};
function processEvent(event, context, callback) {
console.log('Calling MongoDB Atlas from AWS Lambda with event: ' + JSON.stringify(event));
var jsonContents = JSON.parse(JSON.stringify(event));
//date conversion for grades array
if(jsonContents.grades != null) {
for(var i = 0, len=jsonContents.grades.length; i < len; i++) {
jsonContents.grades[i].date = new Date();
}
}
context.callbackWaitsForEmptyEventLoop = false;
try {
if (cachedDb == null) {
console.log('=> connecting to database');
MongoClient.connect(atlas_connection_uri, function (err, client) {
cachedDb = client.db('testdb');
return createDoc(cachedDb, jsonContents, callback);
});
}
else {
createDoc(cachedDb, jsonContents, callback);
}
}
catch (err) {
console.error('an error occurred', err);
}
}
I suspect that something is going on with my VPC firewall/permissions/security group considering the fact that I can connect from my local machine, but I have no idea how that could be the case when I'm granting full admins privileges in my security role and I've set all outgoing VPC traffic to my public subnet.
I would appreciate any advice/help in solving this!
edit to provide more info:
The function console.logs '=> connecting to database' and then immediately times out at MongoClient.connect (confirmed by attempting to console.log directly after that).

ConnectionError: Connection lost - read ECONNRESET in protractor

I am using protractor 52.2 and cucumber 3.2.2. I am using selenium grid(selenium-server-standalone-3.14.0.jar) with the protractor and running my script in 4 browsers of 4 different nodes. I have a table of 600 rows in the DB. Initially, I am accessing data from this table and entering the data of each row through my protractor script and updating the DB column after successful entering of each row. But after entering some rows successfully, protractor script abruptly ends with error "ConnectionError: Connection lost - read ECONNRESET in protractor".And I am getting an error message in update SQL query, that "RequestError: Resource ID: 1. The request limit for the database is 60 and has been reached. See 'http://go.microsoft.com/fwlink/?LinkId=267637' for assistance." The update query which I am using is given below(i am using azure sql). I am not getting a clear idea of how to solve this. Thanks in advance.
var Connection = require('tedious').Connection;
var Request = require('tedious').Request;
var config =
{
userName: 'xxx',
password: 'xxxxx',
server: 'xxxxxx',
options:
{
database: 'xxx' ,
encrypt: true,
rowCollectionOnRequestCompletion: true
}
}
var connection = new Connection(config);
defineSupportCode(function ({ setDefaultTimeout, Given, When, Then }) {
setDefaultTimeout(30000 * 1000);
function updatedb(LPAID){
request = new Request("UPDATE COM_Location_Post with (rowlock) SET IsPublished = 1 WHERE Id ="+LPAID,function(err,rowCount, rows) {
if(err){
console.log(err)
}
});
connection.execSql(request);
}
});
You did not use connection close in your script.
By your question its clear after max instance you face this problem.
Try closing your connection every time for each transaction.
(async () => {
const config = {
user: 'User',
password: 'iPg$',
server: 'cp-sql',
database: 'DBI',
options: {
encrypt: true // Use this if you're on Windows Azure
}
}
try {
let pool = await sql.connect(config)
var envcode, testcode;
let result1 = await pool.request()
.query(`query 1 goes here`)
// console.dir(result1)
pool.close();
sql.close();
let pool1 = await sql.connect(config)
let result2 = await pool1.request()
.query(`query 2 goes here`)
// console.dir(result2)
pool1.close();
sql.close();
resolve(result2);
} catch (err) {
console.log(err)
}
})()

Moongose ready state and aws lambda - cache connection

I am trying to implement a connect to database function that uses mongoose (MongoDB), and AWS lambda functions. Best practice, as I understand it, is to cache the database so that the connection can be reused. This, I have working. However, the problem is that the mongoose.readyState variable uses some fixed amount of time to determine if the connection is valid or not.
I was wondering if there is an alternative to .readyState to check if the connection is valid. I know that I can try to run a random query against the database every time I want to reuse the connection. Thus, I could determine if an exception happens or not. However, this feels a little hackish.
Here is my connect to database function:
let cacheDb = null
let options = {
useMongoClient: true,
autoIndex: true,
autoReconnect: true,
keepAlive: true,
socketOptions: {
keepAlive: true,
autoReconnect: true,
connectTimeoutMS: 30000
},
reconnectTries: Number.MAX_VALUE,
reconnectInterval: 200,
poolSize: 1,
bufferMaxEntries: 0,
};
mongoose.Promise = global.Promise
module.exports = {
connectToDatabase: function connectToDatabase(context) {
context.callbackWaitsForEmptyEventLoop = false
if (cacheDb && mongoose.connection.readyState == 1) {
return cacheDb
}
else {
mongoose.connect(process.env['MONGODB_URI'], options, function(error){
if(error){
console.log(error.toString())
}
cacheDb = mongoose.connection
return cacheDb
})
}
}
}
Do you have any idea as to an alternative approach?
In order to check for a valid connection , you should be checking your cached db serverConfig.isConnected method.
like so:
if (cachedDb && cachedDb.serverConfig.isConnected()) { ...
This is according to mongodb, in this helpful article:
https://www.mongodb.com/blog/post/optimizing-aws-lambda-performance-with-mongodb-atlas-and-nodejs