3
votes

I have a DocumentDB stored procedure that does insert or update (not replace but rather reads and update existing document). The stored procedure does at most two operations:

  1. query by Id and
  2. either insert or update

The document is also not particularly large. However, every now and then I would get either time out (caused by bounded execution) or 449 (conflict updating resources, which is a transient error).

IMO this isn't a particularly taxing stored procedure but seems that I'm running to limitations already. I could do more work client side but I love the ACID guarantee in the stored procedure.

Is there any quantitative measure on bounded execution? I'm wondering if I'm simply doing things wrong or I have indeed hit limit of DocumentDB.

My stored procedure is a modified https://github.com/Azure/azure-documentdb-js-server/blob/master/samples/stored-procedures/update.js that takes in document instead of id. I'm using "$addToSet" in particular and the code looks like

function unique(arr) {
    var uniqueArr = [], map = {};

    for (var i = 0; i < arr.length; i++) {
        var exists = map[arr[i]];

        if (!exists) {
            uniqueArr.push(arr[i]);
            map[arr[i]] = true;
        }
    }

    return uniqueArr;
}

// The $addToSet operator adds elements to an array only if they do not already exist in the set.
function addToSet(document, update) {
    var fields, i;

    if (update.$addToSet) {
        console.log(">addToSet");
        fields = Object.keys(update.$addToSet);

        for (i = 0; i < fields.length; i++) {
            if (!Array.isArray(document[fields[i]])) {
                // Validate the document field; throw an exception if it is not an array.
                throw new Error("Bad $addToSet parameter - field in document must be an array.");
            }

            // convert to array if input is not an array
            var newIds = Array.isArray(update.$addToSet[fields[i]])
                ? update.$addToSet[fields[i]]
                : [update.$addToSet[fields[i]]];

            var finalIds = unique(document[fields[i]].concat(newIds));
            document[fields[i]] = finalIds;
        }
    }
}
1

1 Answers

2
votes

DocumentDB stored procedures must complete within 5 seconds. They are also limited by the provisioned throughput of the collection. If you have 5000 RU/s provisioned, then the stored procedure cannot consume more than 5000 * 5 RUs in total.

When a stored procedure reaches its execution time or its throughput limit, any request to perform a database operation (read, write, query) will receive a pre-emption signal, i.e. the request will not be accepted as a signal for the stored procedure to wrap up execution, and return to the caller. If you check for return code from each call, your stored procedure will never timeout. Here's a snippet showing how to do this (full samples are available at https://github.com/Azure/azure-documentdb-js-server/blob/master/samples/stored-procedures/):

var isAccepted = collection.replaceDocument(...) {
     // additional logic in callback
});

if (!isAccepted) {
   // wrap up execution and return        
}

Regarding 449, this is a concurrency error that can be returned if your stored procedure attempts to perform a conflicting write. This is side-effect free and safe to retry on from the client. You can implement a retry until succeeded pattern whenever you run into this error.