Memory issue with mongo in node - node.js

I am facing memory issues with my node app. Took some heapdumps and saw a lot of mongo objects being held in the memory which is causing the node app to run out of memory.
I have the following setup for my app.
MongoDB 3.4.13
Mongoose 4.11.10 (tried 4.13.11 and 5.0.7 also)
Node 8.9.4
config.js
const clientUID = require('./env').clientUID;
module.exports = {
// Secret key for JWT signing and encryption
secret: 'mysecret',
// Database connection information
database: `mongodb://localhost:27017/app_${clientUID}`,
// Setting port for server
port: process.env.PORT || 3000,
}
I have several models in the app. Every model is defined in the following manner (just listing one of the models here):
models/card.js
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const CardSchema = new Schema({
name: {
type: String,
unique: true,
required: true
},
macId: {
type: String,
unique: true,
required: true
},
cardTypeId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'CardType',
required: true
},
},
{
timestamps: true
});
module.exports = mongoose.model('Card', CardSchema);
In the app I require the model and perform some actions as follows:
const Card = require('./models/card');
...require other models
const config = require('./config');
mongoose.connect(config.database);
function fetchCardByMacId(macId) {
return Card.findOne({ macId }).lean().exec();
}
function updateTrackerByMacId(macId, x, y, nodeId) {
const data = {x, y, lastNodeId: nodeId};
fetchCardByMacId(macId)
.then(card => {
Tracker.findOneAndUpdate({ cardId: card._id }, data, { upsert: true, new: true }).exec((error, tracker) => {
if (error) {
return console.log('update tracker error', error);
}
TrackerHistory.findOne({ trackerId: tracker._id }).exec((err, trackerHistory) => {
if (err) {
return console.log('fetch trackerHistory error', err);
}
if (trackerHistory) {
trackerHistory.trackers.push({ x, y, timestamp: moment().format(), nodeId });
TrackerHistory.findOneAndUpdate({_id: trackerHistory._id},trackerHistory,(er, trackerHis) => {
if (er) {
return console.log('trackerHistory change update error', er);
}
})
} else {
const trackerHistoryNew = new TrackerHistory({
trackerId: tracker._id,
trackers: [{ x, y, timestamp: moment().format(), nodeId }]
});
trackerHistoryNew.save((er, trackerHis) => {
if (er) {
return console.log('trackerHistory create error', er);
}
});
}
});
});
}).catch(error => {
console.log('updateTrackerByMacId error', error);
});
}
Like this there are many other functions that read and update data.
Every 5 seconds I get new data that needs to be inserted into the db (not more than few 100kbs) and some of the old db data also gets updated based on this new data (seems like fairly straight forward db ops...read, manipulate and update back).
From the index.js I spawn 2 child processes that take the load of processing this new data and updating the db based on the business logic. When new data is received in the index.js using event listeners, I send it to child process 1 to insert/update the db. child process 2 runs on a 10s timer to read this updated data and then do some further updates to the db.
Running this on my local macbook pro is no issue (logging heap memory being used never goes above 40-50mb). When i load it on a DO Ubuntu 16.04 server (4GB /2 CPUs) I am facing memory issues. The child processes are exiting after hitting the memory threshold for the process (~1.5gb) which seems very odd to me.
I also tried to do this using docker containers and see the same results. on the mac it runs without issues but on the server it is eating up memory.
Generating heapdumps shows a lot of mongo objects in the heap.
I would like some help in understanding what I am doing wrong here and what is the issue with mongo eating up this much memory on the server.

So there was a big issue with the way the TrackerHistory collection was modelled. TrackerHistory had an array and every time a new object had to be added to the array the whole TrackerHistory object was being loaded in the memory and at the given frequency of updating the real time data the memory was bloating up faster than it was being gc'd.
Fixed it by removing the trackers array in a new collection and adding a foreign key reference to the TrackerHistory.
reference article that helped me identify this issue.
https://www.mongodb.com/blog/post/6-rules-of-thumb-for-mongodb-schema-design-part-1

Related

Is Mongoose Watch function lenient to service Unavailability

I am using mongoose watch on a particular model as shown below:
const mongoose = require("mongoose");
const ScheduledEventSchema = new mongoose.Schema(
{
_id: mongoose.Schema.Types.ObjectId,
data: Object,
expireAt: { type: Date, expires: 0 }
},
{ timestamps: true }
);
ScheduledEventSchema.index({ "expireAt": 1 }, { expireAfterSeconds: 0 })
const ScheduledEvent = mongoose.model('ScheduledEvent', ScheduledEventSchema);
ScheduledEvent.watch().on('change', data => {
let { operationType, documentKey } = data;
if (operationType === "delete") {
//Do something
}
});
module.exports = ScheduledEvent;
The above model is part of a service which is hosted on Google Cloud Run.
Now, Google Cloud Run services don't run all the time, they only run when invoked by some trigger.
So, assuming the service using the above model is currently not running and MongoDb sends a document deleted event, will the service pick up that deleted event the next time it is started or will that event be lost permanently?

Saving documents on mongoose on console app keep hanging the execution

I have a fairly simple nodejs console app using mongoose, and I am trying to save a document and end the program execution. The problem is that it hangs in a waiting state.
I just found some other questions like this, informing things like trying to close the connection, but I had no luck with the responses. I already tried everything that I found on docs, and answers and nothing worked.
My code is pretty straightforward:
const mongoose = require("mongoose");
let personSchema = new mongoose.Schema(
{
name: {type: String},
age: {type: Number}
},
{ collection: "person" }
);
let personModel = mongoose.model("Person", personSchema);
mongoose.connect("mongodb://localhost:27017/People", {useNewUrlParser: true, useUnifiedTopology: true}).then(
() => {
personModel.create({
name: "Alex",
age: 40
});
},
err => {
console.error("Error", err);
}
);
What I already tried:
Close the connection after create. Results on the error: Topology is closed. Please connect.
Setting keepAlive to false: no effect
Setting bufferCommands to false: no effect
Is there a way to fix this behavior? Any ideas?

Need to find an unused number in a nested object mongoose

I'm trying to create a Express webserver which does the following -
Register and Sign in
Once the user signs-in the user is redirected to a "Control panel", where they choose specifications for a docker container.
Mongoose schema
email: String,
password: String,
docker: {
site: String,
adminpwd: String,
hostPort: Number,
cpus: Number,
memory: Number,
storage: Number
}
})
The docker object is appended to the email and password fields after a post in the controlpanel page.
app.post('/control-setup/:userid',
//validation
[
body('site').isURL({allow_underscores: true}),
body('admin').isLength({ min: 6 })
], (req,res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const userid = req.params.userid
//Setup or Edit
var site = req.body.site
var adminpwd = req.body.admin
var cpus = req.body.cpus
var memory = req.body.memory
var storage = req.body.storage
//Needs a function to get the next available port
var hostPort = findhostPort()
//Cutting https:// from input
var site_name = site.slice(8)
//Save or Edit in DB
Users.updateOne({_id: userid}, {docker: {site: site, adminpwd: adminpwd, hostPort: hostPort, cpus: cpus, memory: memory, storage: storage}}, {upsert: true, new: true},
function (err, result) {
if (err) throw err
console.log('Docker Parameters Setup in User' + result)
res.redirect(userid)
})
})
The problem is in the hostPort variable, I'm trying to create a function that will go through mongoDb and find an available port.
I tried using for loops but it wouldn't work.
I'm clearly missing the correct logic here.
Would really appreciate some help as I'm a newbie to NodeJs and Development as a whole
You can use distinct to find all the ports already used, and then generate a free port number.
const ALL_POSSIBLE_PORT_NUMBERS = [...]
function async findhostPort () {
const usedPorts = await User.distinct('docker.hostPort')
for (let port of ALL_POSSIBLE_PORT_NUMBERS) {
if (userPorts.indexOf(port) == -1) {
return port
}
}
}
Some caveats:
You will need to make the docker.hostPort a unique field so the DB will throw an error when trying to save a duplicate hostPort, as simultaneous requests to this method will get the same port number.
I'm assuming you have an array of all possible port numbers as some port ranges are restricted, and you will probably want to keep some for internal use
Using distinct will get slower as you get more models. If it becomes a problem, you can consider caching the list of unused ports in memory and remove from it every time a new one is created.

Node.js + mongoose find freezes node when more than 100 results

I have a simple mongoose model on which I call find with limit max 100 it calls the done callback:
this.find({}).limit(100).exec(done);
The callback is never called If I modify this line into (or any higher number)
this.find({}).limit(101).exec(done);
There is no error anywhere, the database keeps working, but this node app freezes and must be restarted.
If I ssh into the server to connect to the same database and connect to mongo shell, on the same collection find({}) returns all ~700 collections in less than a sec.
When I cloned the same database to my local PC and run the app to connect to local database it worked, but the app freezes on the server if its connect to the database on the same server.
Any idea how to debug this one?
Edit1: Added model file:
Model file:
'use strict';
let mongoose = require('mongoose');
let Schema = mongoose.Schema;
let foodSchema = new Schema(
{
name: Object,
type: String,
description: Object,
price: Number,
priceBig: Number,
active: Boolean
},
{
collection: 'foods'
}
);
let model = mongoose.model('food', foodSchema);
model.getAllFoods = function (done) {
this.find({}, done);
};
model.getActiveFoods = function (done) {
this.find({active: true}, done);
};
model.getFoodById = function (id, done) {
this.findOne({_id: id}, done);
};
module.exports = model;
Usage:
foodModel.getAllFoods(function (err, docs) {
if (err) {
res.sendStatus(500);
return;
}
res.send(docs);
});
getActiveFoods works just fine (returns 96 docs)
After the tip from JohnnyK I updated Mongoose from 4.1.11 to 4.3.7 and that fixed the issue.

Query mongodb with geddy

While trying out the node.js framework geddy (on windows) and i've run into a bit of a problem.
I'm trying to query mongodb, in my controller, using the .first() method from my Users Model like so:
geddy.model.User.first({name: 'jdoe'}, function (err, data) {
if (err) {
throw err;
} else {
console.log(data);
}
});
Strangely enough i'm not getting any output, error, nothing. The user jdoe exists in the collection so it should output something, right ? Am i doing something wrong ?
My model is defined as:
var User = function () {
this.defineProperties({
username: {type: 'string', required: true},
password: {type: 'string', required: true},
});
this.autoIncrementId = true;
};
User = geddy.model.register('User', User);
The default adapter is set to mongo in development.js, when i ran geddy for the first time it created my database and it has inserted the Users collection correctly.
Any idea on whats going wrong here ?
UPDATE:
added development.js as requested
var config = {
detailedErrors: true
, debug: true
, hostname: null
, port: 4000
, model: {
defaultAdapter: 'mongo',
}
,db: {
mongo: {
dbname: 'knowledgebase'
}
}
, sessions: {
store: 'memory'
, key: 'sid'
, expiry: 14 * 24 * 60 * 60
}
};
module.exports = config;
also my collections on mongo ( created by geddy )
> show collections
User
system.indexes
users
note that somehow geddy is creating two collections instead of one
It looks like you're being hit by this bug: https://github.com/mde/geddy/issues/240
As it is, Geddy accidentally creates two collections per model. It always uses the lowercased pluralized collection to do read/writes though. Are you sure that your data was in that collection and not in the other?
At any rate, from the comments, it sounds like you've got this one covered already.

Resources