pdeva
pdeva

Reputation: 45531

How to atomically add a sequentially increasing id to a set of name/id pairs

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

Answers (2)

Asya Kamsky
Asya Kamsky

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:

  1. the find query and the update query are conditional on Name not already being in mySet array "names".

  2. 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.

  3. 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

kwolfe
kwolfe

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

Related Questions