Maximum call stack size exceeded on insert 10000 documents - node.js

This is the code am running which returns the Range Maximum call stack size exceeded error.
// to insert 10000 values on to mongodb using node.js
var MongoClient = require('mongodb').MongoClient;
var mongoServer = require('mongodb').Server;
var serverOptions = {
'auto_reconnect': true,
'poolSize': 100
};
var i=0;
var async =require('async');
var mongoClient = new MongoClient(new mongoServer('localhost', 27017, serverOptions));
var db = mongoClient.db('test');
var collection = db.collection('new_file_test');
mongoClient.open(function (err, mongoClient)
{
if(err){console.log(err)};
function start(i,call)
{
if(i<10000) {
call(start);
}
}
function pass(callback)
{
Insert(save);
i++;
callback(i,pass);
}
start(i,pass);
});
function Insert(callback) {
console.log("Inserting" );
var doc={
'trip_paramid':i,
'tripid':'116',
'lattitude':'12.8929183',
'longitude':'77.63627',
'speed':'2',
'heading':'0',
'altitude':'80469',
'address':'qwertyasdfgxcvbn',
'engine_status':'Normal',
'oil_pressure': '83.12',
'water_temp': '28',
'fuel_content':'0',
'brake':'Normal',
'creation_time':'2013-08-31 23:22:17',
'brakelight_status':'Normal',
'battery_status':'12.68',
'event_code':'8',
'dbinsert_time':'2013-08-31 23:24:59',
'gsm_status':'-51',
'cell_id':'45',
'vehicle_id':'123456',
'distance':'0'}
callback(doc);
}
function save(doc)
{
collection.insert(doc, function(err)
{
if (err)
{
console.log('Error occured');
}
else
console.log("Saved");
});
}
If the condition is to insert 1000 rows it works fine and the error throws only when the condition goes beyond 10000.

Looping over 10000 times and performing insert is really a bad idea. But still you can do with async library which might help you fix the issue. I have came across this situation before and i used async.queue to overcome the issue.
Async.js module.

The problem comes from the recursive loop you made:
function start(i, call) {
if (i < 10000) {
call(start);
}
}
function pass(callback) {
Insert(save);
i++;
callback(i, pass);
}
start(i, pass);
You should change it to something like this:
for (var i = 0; i < 10000; i++) {
Insert(save);
}
Simplifying your code you have this:
var i = 0;
function pass() {
if (i < 10000) {
Insert(save);
pass(i);
}
i++;
}
pass();
The problem comes from the part that you are calling this function recursively, and since javascript doesn't have tail recursion elimination, the callstack keeps growing. V8(nodejs javascript engine) has it's limits, the callstack once reached to the maximum defined size the error will be thrown.
You can also have look at the following questions for more information:
Maximum call stack size exceeded error
JavaScript recursion: Maximum call stack size exceeded
This is all about fixing Maximum call stack size exceeded error. But 10000 looks like a huge number. I just ran that and it took about 3 seconds on my machine, to finish the loop using monk. Using mongo shell it took about 1 second. If you are running a server, when the loop is running your application is unresponsive.
I suggest instead, insert in batches, and use node's setImmediate function to schedule the next batch to be run after pending I/O events(like handling new web requests):
function insert10000(i) {
insert100();
i++;
if (i < 100) {
setImmidiate(insert10000, i);
}
}
function insert100() {
for (var i = 0; i < 100; i++) {
Insert(save);
}
}
And since we came on the topic of batching insert calls, collection.insert method, supports an array of documents instead of just one to be inserted.
So when we currently have something like following:
collection.insert(doc1);
collection.insert(doc2);
It can be changed to this:
collection.insert([doc1, doc2]);
And that actually is faster. So you can change the code to this:
function insert10000(i) {
insert100(i);
i++;
if (i < 100) {
setImmediate(insert10000, i);
}
}
function insert100(i) {
var docs = [];
for (var l = i + 1000; i < l; i++) {
docs.push({
'trip_paramid':i,
'tripid':'116',
'lattitude':'12.8929183',
'longitude':'77.63627',
'speed':'2',
'heading':'0',
'altitude':'80469',
'address':'qwertyasdfgxcvbn',
'engine_status':'Normal',
'oil_pressure': '83.12',
'water_temp': '28',
'fuel_content':'0',
'brake':'Normal',
'creation_time':'2013-08-31 23:22:17',
'brakelight_status':'Normal',
'battery_status':'12.68',
'event_code':'8',
'dbinsert_time':'2013-08-31 23:24:59',
'gsm_status':'-51',
'cell_id':'45',
'vehicle_id':'123456',
'distance':'0'
});
}
collection.insert(docs, function(err) {
if (err) {
console.log('Error occurred', err);
}
});
}
I measured this, it was faster twice faster than the original case.

Related

Sorting data into appropriate key in an object

I'm trying to loop & sort a large number of data ( the whole ethereum blockchain lol )
I'm trying to create a record of all transactions for every address.
Obviously this is a very intensive process and I'm not sure how to make it more efficient beyond what I have (which isn't that efficient)
It starts out quick but I'm thinking now it has slowed because of the lookup for the address in the txs object.
Any help opinions / help is greatly appreciated.
https://giphy.com/gifs/3o6fJ7KWqxESY9okk8
var txs = {};
var i = 0;
// Loop over blocks
(function loop () {
setTimeout(function () {
// Get current block
var block = web3.eth.getBlock(i, true, (error, block) => {
// debugger;
// Loop over transactions in block
for(var j = 0; j < block.transactions.length; j++) {
// debugger;
if(txs[block.transactions[j].to]) {
txs[block.transactions[j].to].transactions.push(block.transactions[j]);
} else if (txs[block.transactions[j].to]) {
txs[block.transactions[j].from].transactions.push(block.transactions[j]);
} else {
txs[block.transactions[j].to] = {
transactions: [block.transactions[j]]
}
txs[block.transactions[j].from] = {
transactions: [block.transactions[j]]
}
}
}
});
i++
if (i < highestBlock) {
loop();
}
}, 50);
})();
I think that your code has an error in it located at "else-if", it seems that you should use the txs[block.transactions[j].from] property instead of the txs[block.transactions[j].to]. If you simply want to accomplish a recursive pattern you could use the setImmediate function or the process.nextTick method. If you use node.js v6+ you could use a Map instead of the object.

undefined value returning while executing sql query in nodejs [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Javascript infamous Loop issue? [duplicate]
(5 answers)
Closed 6 years ago.
I am fetching values from my database having tables:
My code:
function listshops(callback)
{
client.connection.query('select * from shop',function(err,rows){
if(rows.length>0)
{
for(var i=0;i<rows.length;i++)
{
var shopIdFetched = rows[i].shopId;
client.connection.query('select * from image where shopId=?',shopIdFetched,function(err,data){
if(data.length > 0){
console.log(rows[i],data);
}
});
}
}
});
}
But when displaying result, the first query shows a undefined value.
When i gives rows [0] and rows1 the values are fetching. But i need to implement rows[i].
You misunderstand how asynchronous calls are make.
What happens when you run this part of code?
for(var i=0;i<rows.length;i++)
{
var shopIdFetched = rows[i].shopId;
client.connection.query(...) //these are asynchronous methods
}
For rows.length=10, it will call 10 times the client.connection.query, which is unfortunately asynchronous method, therefore it is not executed yet, but it put 10 asynchronous methods to Event Stack.
After this method synchronously finishes and one of method indicates, the call to database is finished, the method is executed, which is this
if(data.length > 0){
console.log(rows[i],data);
}
However at this point, the for-cycle already finished, the i=10, therefore rows[10] is undefined (because for rows.length=10 you have data in rows[0] to rows[9]
One workaround can be to put another method to the inner scope, something like this
for(var i=0;i<10;i++)
{
x(i);
}
function x(i){
console.log(i);
//this i will be same even after asynchronous paradighm
}
The same thing can be written as this
for (var i = 0; i < 10; i++) {
(function(i){
console.log(i);
})(i)
}
In your case
for(var i=0;i<rows.length;i++)
{
(function(i){
var shopIdFetched = rows[i].shopId;
client.connection.query('select * from image where shopId=?',shopIdFetched,function(err,data){
if(data.length > 0){
console.log(rows[i],data);
}
});
})(i);
}
For better understanding, this would do the same
for(var index=0;index<rows.length;index++)
{
(function(i){
var shopIdFetched = rows[i].shopId;
client.connection.query('select * from image where shopId=?',shopIdFetched,function(err,data){
if(data.length > 0){
console.log(rows[i],data);
}
});
})(index);
}
In previous example, we just shadowed variable i with different variable i (if more variables with same name are created, the one which is in the most inner scope will be selected)
You can not rely on i in async callbacks, because it was changed at time handler called.
You should to create some scope to save iteration data (i or row).
With Array.prototype.forEach:
rows.forEach(row => {
var shopIdFetched = row.shopId;
client.connection.query('select * from image where shopId=?',shopIdFetched,function(err,data){
if(data.length > 0){
console.log(row,data);
}
});
});
With IIFE:
for (var i=0; i<rows.length; i++) {
!function(i) {
// here you can use `i`/`rows[i]` without initial issue
}(i);
}

Mongoose promise built in but not working?

Or quite possibly I am doing it wrong, in fact, more than likely I am doing it wrong.
Have a table which contains a "tree" of skill, starting at the root level and may be as deep as ten levels (only two so far), but I want to return it as one big fat JSON structure, so I want to ask the database for each set of data, build my structure then ask for the next level.
Of course if I just send of my requests using mongoose, they will come back at any time, as they are all nice asyncronous calls. Normally a good things.
Looking at the documentation for Mongoose(using 4.1.1) it seems like it has a promise built in, but whenever I try to use it the api call throws a hissy fit and I get a 500 back.
Here is my simple function:
exports.getSkills = function(req,res) {
console.log("Will return tree of all skills");
for (var i = 0; i<10; i++){
var returnData = [];
console.log("Lets get level " + i );
var query = Skill.find({level: i });//The query function
var promise = query.exec; //The promise?
promise.then(function(doc) { //Totally blows up at this point
console.log("Something came back")
return "OK";
});
}
}
The Mongoose documentation on the subject can be found here
http://mongoosejs.com/docs/api.html#promise_Promise
var promise = query.exec;
// =>
var promise = query.exec()
exports.getSkills = function(req,res) {
console.log("Will return tree of all skills");
var p;
for (var i = 0; i < 10; i ++) {
if (i == 0 ) {
p = Skill.find({level:i}).exec();
} else {
p.then(function (){
return Skill.find({level:i}).exec()
})
}
p.then(function (data) {
//deal with your data
})
}
p.then(function () {
// deal with response
})
}

Confusion with async functions

Please note that I am a total noob when it comes to Node.js.
I have created a WebSockets app that will send server status and usage every second when a client connects (through basic authentications so that only the authorized clients can contact).
I want to add disk usage info to the data and is using the nodejs-disks module for that. And this is the function I've created to get the info I need:
function getDiskStats()
{
var output = {
count: 0,
space: {
total: 0,
used: 0,
free: 0
}
};
df.drives(function(err, drives)
{
df.drivesDetail(drives, function(err, data)
{
output.count = data.length;
for(var i = 0; i < data.length; i++)
{
output.space.total += parseInt(data[i].total);
output.space.used += parseInt(data[i].used);
output.space.free += parseInt(data[i].available)
}
});
});
return output;
}
But when the output is returned, everything is 0 and if I log variable in the console right after the for loop I get the values. My guess is that its because of the async method nodejs-disks uses to get the data. I am not sure what I should do next and I HAVE google a lot but couldn't find a good solution.
Thanks!
Like #mevernom said, you are returning your object before any of the asynchronous objects have a chance to work.
You can modify your function to work asynchronously by making your getDiskStats function take a callback that it calls when everything else is done.
function getDiskStats(callback) {
var output = {
count: 0,
space: {
total: 0,
used: 0,
free: 0
}
};
df.drives(function(err, drives) {
if (err) return callback(err); // stop on error
df.drivesDetail(drives, function(err, data) {
if (err) return callback(err); // stop on error
output.count = data.length;
for(var i = 0; i < data.length; i++) {
output.space.total += parseInt(data[i].total);
output.space.used += parseInt(data[i].used);
output.space.free += parseInt(data[i].available)
}
// done processing, call callback
callback(null, output);
});
});
}
You would then have to use your function like
getDiskStats(function(err, value) {
// do stuff!
});
Because your getDistStats returned immediately, before callbacks finished their tasks.
If you are not comfortable with asynchronous nature, perhaps you could try async module.

handling asynchronous call backs in nodejs

I am newbie to nodejs.It's very hard to handle callbacks at nodejs level. I have code like this,
getItems(request,function(jsonObject){
var itemData={};
var itemDetails=new Array();
for(var i=0;i < jsonObject.length;i++){
getItemDetails(jsonObject[i].value.item_id,function(jsonObject){
itemDetails.push(jsonObject);
});
}
itemData["itemDetails"]=itemDetails;
response.contentType("application/json");
response.send({"data":itemData});
});
while executing the above code, the for loop is continuing with out getting callback from getItemDetails method and response sent to client. My requirement is the loop will wait until getting the call back from the getItemDetails then response should send.
I have tried with process.nextTick(), but i am unable to find where i have to use that process.nextTick().. Please anybody provide suggestions.
Thanks in advance.
You need to send the response only after you get all the items, so modify your code like so:
getItems(request,function(jsonObject) {
var itemData = {},
itemDetails = [],
itemsLeft = len = jsonObject.length,
i;
function sendResponse(itemDetails) {
itemData["itemDetails"] = itemDetails;
response.contentType("application/json");
response.send({ "data": itemData });
}
for (i = 0; i < len; i++) {
getItemDetails(jsonObject[i].value.item_id, function(jsonObject) {
itemDetails.push(jsonObject);
// send response after all callbacks have been executed
if (!--itemsLeft) {
sendResponse(itemDetails);
}
});
}
});
Note: I've used itemLeft here since it's a more generic way to solve these kind of problems, but Ianzz approach is also ok since you can compare the length of the two arrays.
You can't get the loop to wait, but you can modify your code to get the behavior you are expecting:
getItems(request,function(outerJsonObject){
var itemData={};
var itemDetails=new Array();
for(var i=0;i < outerJsonObject.length;i++){
getItemDetails(jsonObject[i].value.item_id,function(innerJsonObject){
itemDetails.push(innerJsonObject);
if (itemDetails.length == outerJsonObject.length) {
// got all item details
itemData["itemDetails"]=itemDetails;
response.contentType("application/json");
response.send({"data":itemData});
}
});
}
});

Resources