Trax Trax - 6 months ago 20
Node.js Question

Node.js mongodb Multiple Scheduled Tasks

I have the following .js file, part of a node.js APP, and 2 questions.

var User = require('../app/models/user');
var Agenda = require('agenda');

var mongoConnectionString = "mongodb://localhost/agenda";
var agenda = new Agenda({db: {address: mongoConnectionString}});


agenda.define('handle energy', function(job, done) {
let lastUpdateRun = new Date(Date.now() - 50*1000); // 50 seconds ago for some buffer room


/****** Energy Loss Per Minute ******/
User.update( // Over 85 -> ~ 40 loss / hour
{'local.energy' : { "$gt" : 85}, "updatedAt": {"$lt": lastUpdateRun}}, // query
{"$inc": { "local.energy": -0.66 } } , // update
{"multi": true}, // Options
function(err, result) { // Callback
if(err) {
console.log(err)
}
}
);
User.update( // Over Under 80 -> ~ 25 loss / hour
{'local.energy' : { "$gt": 50, "$lte" : 85 }, "updatedAt": {"$lt": lastUpdateRun}}, // query
{"$inc": { "local.energy": -0.42 } } , // update
{"multi": true}, // Options
function(err, result) { // Callback
if(err) {
console.log(err)
}
}
);
User.update( // Under 50 -> ~ 15 loss / hour
{'local.energy' : { "$gt": 25, "$lte" : 50}, "updatedAt": {"$lt": lastUpdateRun}}, // query
{"$inc": { "local.energy": -0.25 } }, // update
{"multi": true}, // Options
function(err, result) { // Callback
if(err) {
console.log(err)
}
}
);
User.update( // Under 25 -> ~ 10 loss / hour
{'local.energy' : { "$gt": 10, "$lte" : 25}, "$or" :[{'local.estate.owned.movedIn' : false}, {'local.estate.rented.movedIn' : false}], "updatedAt": {"$lt": lastUpdateRun} }, // query
{"$inc": { "local.energy": -0.167 }} , // update
{"multi": true}, // Options
function(err, result) { // Callback
if(err) {
console.log(err)
}
}
);


/******* Energy Gain Per Minute, No Office *******/

User.update( // Set Energy to Ten if over 9.5 and under 10
{'local.energy' : { "$lt": 10, "$gte" : 9.9}, "$or" : [{'local.estate.movedIn' : false}, {'local.estate.rented.movedIn' : false}], "updatedAt": {"$lt": lastUpdateRun} }, // query
{"$set": { "local.energy": 10 } } , // update
{"multi": true}, // Options
function(err, result) { // Callback
if(err) {
console.log(err)
}
}
);
User.update( // Under 10 -> ~ 5 gain / hour
{'local.energy' : { "$lt": 9.9}, "$or" : [{'local.estate.movedIn' : false}, {'local.estate.rented.movedIn' : false}], "updatedAt": {"$lt": lastUpdateRun} }, // query
{"$inc": { "local.energy": 0.085 } } , // update
{"multi": true}, // Options
function(err, result) { // Callback
if(err) {
console.log(err)
}
}
);


/******* Energy Gain Per Minute, With Office *******/

User.update( // Set Energy to 25 if over 24.5 and under 10
{'local.energy' : { "$lt": 10, "$gte" : 24.5}, "$or" : [{'local.estate.movedIn' : true}, {'local.estate.rented.movedIn' : true}], "updatedAt": {"$lt": lastUpdateRun} }, // query
{"$set": { "local.energy": 10 } } , // update
{"multi": true}, // Options
function(err, result) { // Callback
if(err) {
console.log(err)
}
}
);
User.update( // Under 25 -> ~ 15 gain / hour
{'local.energy' : { "$lt": 24.5 } , "$or" : [{'local.estate.movedIn' : true}, {'local.estate.rented.movedIn' : true}], "updatedAt": {"$lt": lastUpdateRun}}, // query
{"$inc": { "local.energy": 0.250 } } , // update
{"multi": true}, // Options
function(err, result) { // Callback
if(err) {
console.log(err)
}
}
);

done()
});


agenda.on('ready', function() {
agenda.every('one minute', 'handle energy');
agenda.start();
});

// Handle Office Rent Expiration Every Hour
//Handle A Few other things at different times


Question #1:
Is there a better way to handle all those updates, I have quite a few updates in a short interval and I'm repeating 90% of the code over and over again. Also performance wise is this the way to go or I'm doing a big mistake ?

Question #2:
If I need more scheduled tasks 1 every hour and one every 25 minutes should I just create a setInterval for each of them, because iirc javascript doesn't handle multiple setIntervals. So how should I handle it ?

P.S. I'm still learning node/js so don't hate me if these are dumb questions. Thanks for you patience.

Answer

The updates are fine. If they need to be done, they need to be done. Given that they are running repetitively, just make sure you add a few compound indexes between local.energy and each of other fields you are querying by:

UserSchema.index({"local.energy": 1});
UserSchema.index({"local.energy": 1, "local.estate.owned.movedIn": 1});
UserSchema.index({"local.energy": 1, "local.estate.rented.movedIn": 1});
//etc.

The only thing I see that concerns me about the logic is that your updates are running in parallel with no restriction on duplicate updates. Imagine you have a document:

DocA = {
    "local.energy": 9.85
}

Depending on order of execution within the two "Energy Gain/No Office" queries, you can have different results.

The update querying by {"local.energy": {$lt: 9.9}} could increment DocA to 9.935. Now it falls into the update querying by {"local.energy": {$gte: 9.9, $lt: 10}}, which will set it to 10. I'd recommend adding a "lastUpdatedAt": Date field to the schema (and indexes), and restricting your queries with it:

let lastUpdateRun = new Date(Date.now() - 50*1000); // 50 seconds ago for some buffer room

User.update( // Over 85 -> ~ 40 loss / hour
    {'local.energy' : { "$gt" : 85}, "lastUpdatedAt": {"$lt": lastUpdateRun}}, // query
    {"$inc": { "local.energy": -0.66 }, "$set": { "lastUpdatedAt": new Date()}}, // update
    {"multi": true}, // Options
    function(err, result) { // Callback
        if(err) {
            console.log(err)
        }
    }
);

This way you'll only update ones that haven't been updated in this most recent interval.