patternjavascriptModerate
Making an efficient and healthy db call for a real-time browser game
Viewed 0 times
realefficienttimebrowsercallgamehealthyforandmaking
Problem
I'm doing a calculation for a real-time browser game, where users can invest some of their balances to the site. I need to update their invest's based on
When this process finish, I'm saving a few unimportant data in
When
AN_AMOUNT_FROM_SITE and their percentage of shares (see PROFIT line). This function needs to run every 20-100 ms because AN_AMOUNT_FROM_SITE is coming consistently. When this process finish, I'm saving a few unimportant data in
callback().When
FUNDS collection has a few hundred document, this process will be very slow. Are there any ways I can reduce this process with Mongo to make it more efficient?FUNDS.aggregate().group({ //get sum of invested amounts
_id: '$id1',
invest_total: { $sum: '$invest_amount'}}).exec(function ( e1, d ) {
if( !e1 && d ){
FUNDS.find().exec(function( e2, invests ){ //find all invests
if ( !e2 && invests ){
//calculate PROFIT value to each invest according to their rates (it can be +/-)
for (var i = 0; i < invests.length; i++) {
var invest_amount = invests[i].invest_amount,
invest_id = invests[i].invest_id,
PROFIT = AN_AMOUNT_FROM_SITE * invest_amount / d[0].invest_total; //
//update each invest
FUNDS.findOneAndUpdate({ 'invest_id': invest_id }, { $inc: { 'invest_profit': profit } }, function(e3) {
callback()
});
};
}
})
}
})Solution
The power of the aggregation framework is it's ability to iterate over the dataset in various useful ways without incurring extra round trips between the database and the app.
Your code uses one stage for aggregation (grouping by
The code below makes the database do all the heavy lifting in 3 aggregation framework stages, and returns an array with all the necessary data.
Then we end up with an array of objects which can be iterated over, applying the the
Your code uses one stage for aggregation (grouping by
id1), and then jumps out of aggregation to iterate over the entire FUNDS collection for every user. That is very expensive, giving you exponential time complexity or worse.The code below makes the database do all the heavy lifting in 3 aggregation framework stages, and returns an array with all the necessary data.
- pivot over the user_id, calculating
invest_amount, saving unique records in an array calledrecords
- unwind
records. Now you have your original documents, but with a useful extra field.
- calculate profit
Then we end up with an array of objects which can be iterated over, applying the the
$inc update.FUNDS.aggregate([
{
"$group": {
"_id": '$id1',
"invest_total": {"$sum": "$invest_amount"},
"records": {"$push": "$ROOT"}
}
},
{
"$unwind": "$records"
},
{
"$project": {
"invest_id": "$records.invest_id",
"user_id": "$records.id1",
"invest_total": "$invest_total",
"invest_amount": "$records.invest_amount",
"profit": {
"$divide": [
{ "$multiply": ["$records.invest_amount", AN_AMOUNT_FROM_SITE ] },
"$invest_total"
]
}
}
}
]).exec(function(err,docs){
docs.forEach(function(d){
FUNDS.findOneAndUpdate(
{ "invest_id": d.invest_id},
{ "$inc": { 'invest_profit': d.profit } },
function(e3){
callback();
}
);
});
});Code Snippets
FUNDS.aggregate([
{
"$group": {
"_id": '$id1',
"invest_total": {"$sum": "$invest_amount"},
"records": {"$push": "$$ROOT"}
}
},
{
"$unwind": "$records"
},
{
"$project": {
"invest_id": "$records.invest_id",
"user_id": "$records.id1",
"invest_total": "$invest_total",
"invest_amount": "$records.invest_amount",
"profit": {
"$divide": [
{ "$multiply": ["$records.invest_amount", AN_AMOUNT_FROM_SITE ] },
"$invest_total"
]
}
}
}
]).exec(function(err,docs){
docs.forEach(function(d){
FUNDS.findOneAndUpdate(
{ "invest_id": d.invest_id},
{ "$inc": { 'invest_profit': d.profit } },
function(e3){
callback();
}
);
});
});Context
StackExchange Code Review Q#67264, answer score: 13
Revisions (0)
No revisions yet.