1
\$\begingroup\$

I'm sharing this REST API controller code in order to make it work better, also, to become a better developer. I'm having this issues with the application that API handles and I also need to improve code for make it work faster and better.

This is the code is being used more then applicationg starts to increase memory consume and hangs (I understand there can be much code from functions you're not seeing there but I guess you could identify some bad patterns):

var ObjectId = require('sails-mongo/node_modules/mongodb').ObjectID;

module.exports = {

    clearLineup: function (request, response) {

      /*Round lock missing*/

      var _id = new ObjectId(request.param('id'));

      /*Only if this is your team lineup*/

      LineUp.native(function (err, collection) {
        collection.update({_id: _id}, { $set: { players: [], budgetLeft: 60000000, coach: {} }},
          function (err, updated) {
            return err ? response.send(500) : response.json(updated);
        });
      });
    },
    addPlayer: function (request,response) {

      var lineupId        = request.params.id;

      var receivedPlayer  = request.param('player');
      var playerId        = receivedPlayer.id;
      var bench           = Boolean(receivedPlayer.bench);
      var place           =   receivedPlayer.place;
      var iduser          = request.param('iduser');
      var minutesLeft;

      async.waterfall([

        function findPlayer (cb) {
          Player.findOne().where({id: playerId}).exec(function (err,foundPlayer) {
            /*TODO
            REPORT!!!!!*/
/*            if (foundPlayer && MathchDateService.teamIsPlayingRightNow(foundPlayer.team.id))
              return*/

            return cb(err,foundPlayer);
          });
        },
        function findLineup (player,cb) {

          LineUp.findOne().where({id: lineupId}).exec(function (err, foundLineUp) {

            cb(err,foundLineUp,player);
          });},
        function getFavoriteTeam(lineup, foundPlayer, cb) {
            if(iduser){
              Home.findOne({ iduser : iduser }).exec(function(err,home){
                if (err) { return cb({status: 500, msg: JSON.stringify(err)}); }

                if (!home.user.fanOf)
                  return cb({status: 409, msg: "No tienes equipo favorito"});

                return cb(null, lineup, foundPlayer, home.user.fanOf.id);
              });
            }else{
              Team.findOne().where({id: lineup.team}).exec(function (err,foundTeam) {
                if (err) {return cb({status: 500, msg: JSON.stringify(err)});}

                console.log(foundTeam.user.fanOf);
                return cb(null, lineup, foundPlayer, foundTeam.user.fanOf.id);
              });
            }
        },

        function updateLineup(lineup, player, favoriteTeamId, cb) {
          if (!player) {
            console.log("Jugador no existe");
            return cb({status: 409, msg: "El jugador " + playerId + " no existe"});
          }

          if (!lineup.formation) {
            console.log("No hay táctica")
            return cb({status: 409, msg: "No se ha elegido una táctica para esta alineación"});
          }

          if (lineup.squadIsComplete()) {
            console.log("Ya hay 15");
            return cb({status: 409, msg: "La plantilla ya contiene el máximo de 15 jugadores"});
          }

          if (lineup.playerWasAdded(player.id)) {
            console.log("Jugador ya en alineación");
            return cb({status: 409, msg: "El jugador ya ha sido agregado a la alineación"});
          }

          if (lineup.fieldIsComplete() && !bench) {
            console.log("Ya hay 11 en el campo");
            return cb({status: 409, msg: "Ya se han agregado los 11 jugadores de campo"});
          }

          player.bench = bench;
          player.place = place;

          lineup.players.push(player);

          sails.log.debug(favoriteTeamId);
          var rulesMsg = {

            maxForeign: lineup.reachesMaxForeignPlayers(),
            budgetLimit: lineup.reachesBudgetLimit(),
            maxSameTeam: lineup.reachesMaxSameTeamLimit(favoriteTeamId),
            maxSameFavorite: lineup.reachesMaxSameFavoriteTeamLimit(favoriteTeamId)
          };

          if ( _.some(_.values(rulesMsg)) )
            return cb({status: 409, msg: {msg: "La inclusión de este jugador no satisface las reglas del juego", violatedRules: rulesMsg}});

          lineup.updateBudget();
          StatsService.updatePlayerStat('lineup-in', player, '',lineup);

          LineUp.native(function (err,collection) {

            collection.update({_id: new ObjectId(lineupId)}, lineup, function (err, updated) {

              return err ? cb({status: 500, msg: JSON.stringify(err)}) : cb(null, {status: 202, msg: JSON.stringify(updated)});
            });
          });
        }
      ],

      function (err, result) {
        return err ? response.send(err.status, err.msg) : response.send(result.status, result.msg);
      });
    },
    changeCoach: function (request, response) {

      var coachId = request.param('coach');
      var lineupId = request.params.id;

      async.waterfall([

        function (cb) {

          Player.findOne({id: coachId}).
          then(function (coach) {

            if (!coach)
              return cb({status: 409, msg: "No se encuentra ese entrenador"});

            return cb(null, coach);
          }).
          catch(function (err) {
            return cb({status: 500, msg: JSON.stringify(err)});
          });
        },
        function (coach, cb) {

          LineUp.findOne({id: lineupId}).then(function (lineup) {

            if (!lineup)
              return cb({status: 404, msg: "No se encuentra esta alineación"});

            return cb(null, lineup, coach);

          }).catch(function (err) {
            return cb({status: 500, msg: "Error en la petición"});
          });
        },
        function (lineup, coach, cb) {


          delete lineup.id;
          lineup.coach = coach;

          LineUp.native(function (err, collection) {

            if (err)
              return cb({status: 500, msg: JSON.stringify(err)});

            collection.update({_id: new ObjectId(lineupId)}, {$set: {coach: coach}}, function (err, result) {

              if (!err && result)
                StatsService.updatePlayerStat('lineup-in', coach, '', {id:lineupId, round: lineup.matchDate.round});
              if (err) {return cb({status: 500, msg: JSON.stringify(err)});}

              return cb(null, {status: 200, msg: "Entrenador asignado/modificado"});
            });
          });
        }
      ],
      function (err, result) {
        return err ? response.send(err.status, err.msg) : response.send(result.status, result.msg);
      });
    },
    deleteCoach: function (request,response) {

      var lineupId = request.params.id;

      async.waterfall([

        function getNativeDriver(cb) {

          LineUp.native(function (err, collection) {
            return err ? cb({status: 500, msg: JSON.stringify(err)}) : cb(null, collection);
          });
        },
        function findLineup(collection, cb) {

          collection.findOne({ _id : new ObjectId(lineupId) }, { coach: true },
            function (err, foundLineup) {
              if (err)
                return cb({status: 500, msg: JSON.stringify(err)});
              if (!foundLineup)
                return cb({status: 404, msg: JSON.stringify(err)});
/*              if (Object.keys(foundLineup.coach).length === 0)
                return cb({status: 202, msg: "Entrenador ya eliminado"});*/
              if (_.isEmpty(foundLineup.coach))
                return cb({status: 202, msg: "Entrenador ya eliminado"})
              cb(null,foundLineup, foundLineup.coach, collection);
            });
        },
        function $pullCoachOff (lineup, coach, collection,cb) {
          collection.update({ _id: new ObjectId(lineupId)},
                            {$set: {coach: {}}},
                            function (err, update) {
                              console.log(err);
                              console.log(update);
                              if (!err && update)
                                StatsService.updatePlayerStat('lineup-out', {id: coach.id}, '',lineup);
                              return err ? response.send(500, JSON.stringify(err)) : response.send(202);
                            });
        }
      ]);
    },
    destroy: function(request,response) {
      return response.send(403);
    },
    minutesBeforeMatch: function (request,response) {
      var matchDate = request.body;

      var roundDate = new Date(matchDate.startDate);
      var currentDate = new Date();
      minutesLeft = ((roundDate - currentDate) / 1000 / 60);

      return response.send(200,{minutesLeft: minutesLeft});
    }

};

What I worry the most is about some abuse on async#waterfall function. I depend on it a lot, and I guess I could use a parallel approach, but I'd like to use whatever library/patter being more RAM kind. I'm also using a lot of lodash for my code.

\$\endgroup\$
1
\$\begingroup\$

TL;DR: Get a profiler, and check what's hogging your machine

How to achieve or ensure efficient and RAM in Node.js code?

To optimize JS run on Node, you need to understand 2 things: How JS allocates stuff, and how V8 represents them internally.

The first one is simple, as it can be viewed from a high-level perspective. The more you keep things around, the more it will eat memory, simple.The second is a bit more complicated, V8 is an entirely different monster and you need to learn V8-specific optimizations.

Either way, you'd need a profiler to check which pieces of code causes memory to be eaten. Here's a list of ways to profile NodeJS

I'm also using a lot of lodash for my code.

Lodash could potentially cause spikes in memory usage due to abstraction. Try using native counterparts to the lodash operations you're using. Most, if not all, operations you use exist in ES5 or can be done in ES5, like _.values (use Object.keys and Array.prototype.map).

What I worry the most is about some abuse on async#waterfall function

Yes, you're right, you should worry about third-party libraries. They're usually build for a specific task, but not necessarily build for others. For instance, take this article from Netflix where a simple router module causes their system to crash.

First, we need to fully understand our dependencies before putting them into production. We made incorrect assumptions about the Express.js API without digging further into its code base.

http://techblog.netflix.com/2014/11/nodejs-in-flames.html

Abusing functionality is relatively easy. I see this specifically in addPlayer. Every time you call addPlayer, it creates the async object internally that holds all your callbacks. That's one thing to consider, as during the whole lifetime of the procedure, that object will continue to exist. That's all what we can assume. We don't even know if it is cleaned up properly afterwards.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.