Reputation: 45531
I need to assign sequentially increasing ids to a set of names. This boils to:
A field that contains the next id to be generated: curId
A set myset
containing the name/value pair, where the value is the id and the name is the name
The problem:
I need to atomically:
1. Check if myset
contains 'name'. If not,
2. Generate a new id using $inc.
3. Insert the name/id pair in myset.
I can't find a way to do this in mongodb, at least without introducing race conditions. Suggestions welcome.
Update
Sample document (what it should look like).
Before adding 'c':
{
"mySet": [
{
"name": 'a',
"value" : 1
},
{
"name": 'b',
"value" : 2
}
]
}
After adding 'c'. 3 is returned since that is the id assigned to 'c'.
{
"mySet": [
{
"name": 'a',
"value" : 1
},
{
"name": 'b',
"value" : 2
},
{
"name": 'c',
"value" : 3
}
]
}
Trying to add 'c' again. Nothing happens since 'c' is already present. But '3' is returned since that is the id for 'c'.
Upvotes: 4
Views: 561
Reputation: 42352
Here is a simple way to do it, it involves maintaining the counter corresponding to the nextId to be used inside the document with this set, and it involves a simple find query and an update. Starting with an empty document:
{
"curId": 1,
"mySet" : [ ]
}
The process that wants to add the next "name" (variable Name) does the following two operations:
var Name = "a";
var curId = db.coll.findOne({"mySet.name":{"$ne":Name}},{"_id":0,"curId":1}).curId;
/* curId variable is now 1 */
var result = db.coll.update(
{ "mySet.name" : {"$ne":Name}, "curId":curId },
{ "$push": {"mySet" : {"name":Name,"value":curId} }, "$inc":{"curId":1} }
);
if (result.nModified == 0) {
print("We lost - someone else must have gotten there first");
}
The critical parts here are:
the find query and the update query are conditional on Name
not already being in mySet
array "names".
the update query also contains the previously read value of curId
to ensure that no other thread has updated the record since we read it.
the curId
field is only incremented on successful $push
to array not on previous read of curId
- until we have successfully used the curId
value available, we don't want to increment it.
Upvotes: 1
Reputation: 1683
db.counter.insert({"_id": "myCollectionName", "count": 1})
This creates a counter collection. Run a find and modify to increment it. http://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/ This operation is atomic and won't cause a race condition. Use the result as your new id.
EDIT: My apologies, didn't realize all 3 needed to be atomic. Use $push (http://docs.mongodb.org/manual/reference/operator/update/push/). This operation does not guarantee that the array will be in order (id of 2 may make it into the array before id 1) however the potential to losing an element of the array like you wold with a straight update is non existent. Use $push in conjuction with findandmodify (http://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/) which is atomic as well.
Upvotes: 0