I would like to assign value to "_id" field of newly inserted document. The ID field must start from 0 and cannot skip a number. The insertion can only happen if there isn't a document containing duplicated content.
Currently this is what I have.
db.system.js.save({
_id: "nextId",
value: function (x) {
return db.counters.findAndModify({
query:{_id:x},
update:{$inc:{value:1}},
new:true
}).value;
}
})
db.loadServerScripts() // make the nextId available directly or via $where clause
db.counters.insert({_id: "_id", value: 0})
db.test.update(
{"value": "abc"},
{$setOnInsert: {"_id": nextId("_id"), "value": "abc"}},
{upsert: true}
)
Everything works well if there are no document containing duplicated "value" field. When there are duplicate "value" field, nextId("_id") javascript function is still called even though it is inside $setOnInsert tag. Therefore, the observed incorrect behavior here would be some "_id" numbers will be consumed/wasted but cannot be assigned to valid new documents. The correct/intended behavior behavior would be the case where nextId() is called only when a new document is actually inserted.
Question: Is there a workaround to achieve the correct/indended behavior in MongoDB?
MongoDB version: v3.4.10 (local installation) and v3.4.15 (mlab MongoDB hosting)
Note: I have given up on this issue and decided to relax my requirement of "cannot skip a number" requirement. The modified requirement is to use as few Ids as possible globally while ensuring that the used Ids has as low values as possible. After this relax requirement change, I have devised a better and scale-able solution.
1) for each client, reserve a modest amount of ids. The result would be:
db.counters.findAndModify({
query: {_id:"_id"},
update: {$inc: {"_id_beg": 10, "_id_end": 10}},
new: true
})
2) for each client's insertion, use the following
db.test.findAndModify({
query: {"value": <input value>},
update: {$setOnInsert: {"_id": <use lowest reserved id>, "value": <input value>"}
upsert: true,
new: true
})
3) for each client's insertion response, the returned value from findAndModify contains the nextId. If nextId is the same as the lowest reserved id, it means that it is used. If it's different, it means another client inserted the same value, we can use the reserved id for later insertion
There are some case specific optimizations such as unique index on _id, unused values from crashed clients, etc, to allow crash recovery