2

I'm looking at this article about auto-incrementing fields in MongoDb. http://docs.mongodb.org/manual/tutorial/create-an-auto-incrementing-field/

This seems good in theory, but doesn't work in my application where all DB calls are async. Ps, Im using Mongoskin. Consider this:

var getNextSequence function (name) {
  db.counters.findAndModify({_id: 'invoiceNr'}, {}, {$inc: {seq: 1}}, {new: true}, function(err, doc) {
    if (err) {
      return next(err);
    }
    return doc.seq;
  });
}; 

var seq = getNextSequence('invoiceNr');
console.log(seq);

This (Console.log) will of course not have any value when executed...

This makes me thinking about nested callbacks instead (classic Node style):

db.counters.findAndModify({_id: 'invoiceNr'}, {}, {$inc: {seq: 1}}, {new: true}, function(err, doc) {
  if (err) {
    return next(err);
  }
  var seq = getNextSequence('invoiceNr');
  // Do something with the seq, like using it when inserting another document.
});

This will work fine for one document, but I cant see how this would work if I make a bulk Insert (passing an array of several documents to insert). And I NEED bulk insertions.

Going back to loops and single insertions doesnt seems like a good solutions.

Do you have any good solution for this?

Anders Östman
  • 3,702
  • 4
  • 26
  • 48
  • Getting a custom `_id` value is not a great idea in general unless you have a really good case for it. But what does "bulk" mean to you and do you always know how many documents you want to "bulk" insert? Or could you at least? – Neil Lunn Sep 05 '14 at 10:22
  • This is not for the _id field, but sure I understand it will still create the same problems. This is for incrementing invoice number. I have have been told this very desirable from people more into accounting than I am. Bulk insertion: http://docs.mongodb.org/manual/core/bulk-inserts/ I will probably insert from 5 to 500 docs. – Anders Östman Sep 05 '14 at 10:27
  • But you do **know** the amount you are processing beforehand right? So you are not processing from a stream and if you were then it would still be possible for you to know the count of items before you executed? That is essentially what I am clarifying. Also you want the "Bulk API" form right? Which may not seem straightforward in mongoskin but is possible. – Neil Lunn Sep 05 '14 at 10:39
  • I always know the amount of item to insert yes. What i want is to take an array (with known length) of invoice Objects and give it to db.invoices.insert. And I want all invoice Object in there to have an incrementing invoiceNr field. – Anders Östman Sep 05 '14 at 11:02
  • I might be onto a solution now... Lunch and time to think is a good thing :) – Anders Östman Sep 05 '14 at 11:03

1 Answers1

4

So the documentation recommends .findAndModify() for this type of operation because the actions of "updating/incrementing" and retrieving the result are an "atomic" operation. Of course if you are inserting 500 documents at a time you don't want to go back an "get the next increment" 500 times, because that would be insane. But there is something in the concept you are missing.

Who said you have to increment by 1? Simply put, when you know you are inserting 500 items, then you just want to increment the current counter by 501 so that the next operation that asks for the next counter value gets the value that has already been increased by 501, so that part is okay.

Now that you asked the counter to be incremented by a block of 500 you basically just use them. So you know that the first value to use basically starts at your return value minus 500, and you want to end your insert on the value that was actually returned.

So you want some code like this:

var async = require('async'),
    mongo = require('mongoskin'),
    db = mongo.db('mongodb://localhost/test');


// The general incrementer

function getNextSequence( name, increment, callback ) {

  // Handling passing in "increment" as optional
  var args = Array.prototype.slice.call(arguments, 1);

  callback = args.pop();
  increment = args.length ? args.shift() || 1 : 1; // default 1

  db.collection('counters').findAndModify(
    { "_id": name },
    {},
    { "$inc": { "seq": increment } },
    { "new": true, "upsert": true },
    function(err,doc) {
      callback(err,doc.seq);
    }
  );

}

// Meat of your insertion logic

db.collection('target',function(err, collection) {

  var arrayToInsert = []; // simulating, but 500 items long assumed
  for (var x=0; x < 500; x++) {
    arrayToInsert.push({
      "x": x
    });
  }

  var bulk = collection.initializeOrderedBulkOp();

  async.waterfall(
    [
      function(callback) {
        getNextSequence('invoiceNo',arrayToInsert.length + 1,callback);
      },
      function(seq,callback) {
        var current = seq - arrayToInsert.length;
        var index = 0;

        async.whilst(
          function() { return index < arrayToInsert.length },
          function(callback) {
            doc = arrayToInsert[index];
            doc["invoice"] = current;
            bulk.insert( doc );
            index++;
            current++
            callback();
          },
          function(err) {
            bulk.execute(callback);
          }
        );
      }
    ],
    function(err,result) {
      if (err) throw err;
      // Log BulkWrite result
      console.log( JSON.stringify( result, undefined, 2 ) );
    }
  );

});

Well, that's the basic simulator but you should get the idea. Essentially just grab a block of "values" for how many entries you intend to insert and use each of the "values" as you insert the actual documents.

Worst case scenario? The inserts failed somewhere and not all of the values ended up in documents. So what? It does not matter, as the point is you already incremented the counter and no other process can possibly grab those same "values" from the counter.

As you can gather from the comments, this is where I was trying to lead you and you may have worked out the basics already. Nothing says you can't increment that counter by more than 1, so just increment by the amount you want to.

Neil Lunn
  • 148,042
  • 36
  • 346
  • 317