JamesLCQ
JamesLCQ

Reputation: 341

Cloud Code Parse Limit 1000 overcome with Chaining?

I have the following function below which I am using to determine a users rank in a scoreboard.

Parse.Cloud.define("getUserGlobalRank", function(request, response) 
{
    var usernameString = request.params.username;
    var scoreAmount = request.params.score;

    var globalRankQuery = new Parse.Query("scoreDB");
        globalRankQuery.greaterThanOrEqualTo("score",scoreAmount);  
        globalRankQuery.descending("score");
        globalRankQuery.limit("1000");
        globalRankQuery.count(
        {
        success: function(count);
        {
            response.success(count);                
        },

.........

However this will not give an accurate response if there are more than 1000 entries that meet that criteria. I would like to chain together a set of .find methods that I can subsequently perform a .count on. Does anyone have any idea how I can achieve this? If you could provide a code example that would be great!

Many thanks, James

Upvotes: 5

Views: 7443

Answers (5)

Cam Tullos
Cam Tullos

Reputation: 2577

Beware when using count. For classes with over 1000 objects, count operations are limited by timeouts. They may routinely yield timeout errors or return results that are only approximately correct. Thus, it is preferable to architect your application to avoid this sort of count operation. I typically use the skip parameter like so:

Parse.Cloud.define('getUserGlobalRank', function(request, response) {
var output = [];
var usernameString = request.params.username;
var scoreAmount = request.params.score;
var skip = (request.params['skip']) ? request.params.skip : 0;

if (skip == 0 || !window['results']) { window['results'] = []; }

var qry = new Parse.Query('scoreDB');
qry.greaterThanOrEqualTo('score', scoreAmount);
qry.descending('score');
qry.limit(1000);
qry.skip(skip);
qry.find({
    success: function (results) {
        if (results.length > 0) { // got results..
            output = output.concat(results);

            // Call the function again
            var params = request.params;
            params['skip'] = results.length;
            request.params = params;
            Parse.Cloud.run('getUserGlobalRank', request, response);

        } else { // we're done here
            response.success(output);
        }
    },
    error: function (err) {

    }
});
});

Upvotes: 3

Andrew Wilhelm
Andrew Wilhelm

Reputation: 141

Reckon this should work as far as implementing promises. Also, pretty sure skip is only supported up to 10,000 unfortunately, but I've been wrong before. Full disclosure, I've not tested this code.

    Parse.Cloud.job("checkUsers", function(request, response) {
        // {} - empty params for first iteration
        getAllUsers({}).then(function(users) {
            // Do stuff with users
          response.success("Loaded " + users.length + " users");
        },
        function(error) {
            response.error(error);
        });

    });

    function getAllUsers(request) {
        //
      var users = (request["users"]) ? request["users"] : [];
      var skip = users.length;
      var userQuery = new Parse.Query(Parse.User);
      userQuery.limit(1000);
      userQuery.skip(skip);

      Parse.Promise.as().then(function() {
        //
        return userQuery.find().then(null, function(error) {
            //
            return Parse.Promise.error(error);

        });     

      }).then(function(userObjects) {
        //
        if (userObjects.length > 0) {
          var allUsers = users.concat(userObjects);
          var params = {"users": allUsers};
          getAllUsers(params);
        } else {
            //
          return Parse.Promise.resolve(allUsers);
        }       

      },
      function(error) {
        //
        return Parse.Promise.reject(error);
      });

    }

Upvotes: 0

derpoliuk
derpoliuk

Reputation: 1816

My goal was to iterate through all users on Parse. Here's my answer based on keither04's answer that is based on Cam Tullos's answer =]

Parse.Cloud.job("checkUsers", function (request, response) 
{
    // {} - empty params for first iteration
    getAllUsers({}, {
        success: function (users) {
            // Do stuff with users
            response.success("Loaded " + users.length + " users");
        },
        error: function (error) {
            response.error(error);
        }
    });
});

function getAllUsers (request, response) 
{
    var users = (request["users"]) ? request["users"] : [];
    var skip = users.length;
    var userQuery = new Parse.Query(Parse.User);
    userQuery.limit(1000);
    userQuery.skip(skip);
    userQuery.find({
        success: function (results) {
            if (results.length > 0) {
                var allUsers = users.concat(results);
                var params = {"users": allUsers};
                getAllUsers(params, response);
            } else {
                response.success(users);
            }
        },
        error: function (error) {
            response.error(error);
        }
    });
}

P.S.: I'm curious if it's possible to do using promises.

Upvotes: 4

keither04
keither04

Reputation: 321

The following answer will work in Cloud Code as it keeps track of the count by using a function parameter in successive calls to getUserGlobalRank(). I have used this architecture in similar cases successfully.

As Gene Z. Ragan mentioned in his comment, the currently accepted answer will not work in Cloud Code because there is no 'window' object.

The reason that recursion has been offloaded to a regular Javascript function is because Parse has a very low recursion limit for Cloud Code functions (I have verified that >4 recursive calls to a Cloud Code function will result in an error). By implementing the recursion in a Javascript function, you can bypass Parse's recursive limit, and continue making recursive calls as long as the code executes in the permitted time (about 15 seconds).

Parse.Cloud.define('getUserGlobalRank', function(request, response) {
     getUserGlobalRank({'username':request.params.username, 'score':request.params.score}, {
        success: function(count) {
            response.success(count);
        },
        error: function(error) {
            response.error(error);
        }
    }); 
});

function getUserGlobalRank(request, response) {

    var usernameString = request['username'];
    var scoreAmount = request['score'];
    var count = (request['count'])? request['count'] : 0;

    var globalRankQuery = new Parse.Query("scoreDB");
    globalRankQuery.greaterThanOrEqualTo("score", scoreAmount);  
    globalRankQuery.descending("score");
    globalRankQuery.limit(1000);
    globalRankQuery.skip(count);
    globalRankQuery.find({
        success: function(results) {
            if (results.length > 0) {
                count = count + results.length;
                getUserGlobalRank({'count':count, 'username':usernameString, 'score':scoreAmount}, response);
            }
            else { // found count of users with higher ranks
                response.success(count);
            }
        },
        error: function(error) { // query error
            response.error(error);
        }
    });
}

Upvotes: 5

Meirion
Meirion

Reputation: 788

Parse.Query.count() is not affected by the Parse.com 1000 query limit.

So you will get a correct count returned no matter how many entries matched by the query.

eg:

var userQ = new Parse.Query('_User');
userQ.count({success:function(count){console.log(count)}});
> 1512

var userQ = new Parse.Query('_User');
userQ.limit(100);
userQ.count({success:function(count){console.log(count)}});
> 1512

Bonus points - .limit() takes a number as its parameter, not a string as in your code. https://www.parse.com/docs/js/symbols/Parse.Query.html#limit

hth

Upvotes: 3

Related Questions