Auto Increment with MongoDB

29 Jul 2010

We are currently working on an app that uses a number of technologies, including PHP, Python, and MongoDB. Recently, a need arose to use sequential identifiers for users, similar to an auto_increment column in MySQL.

If you've used MongoDB, you might be familiar with the default behavior of using a UUID as the primary key. This is convenient, especially if you partition your database across servers, because you don't have to coordinate the primary key in any way. If you use sequential identifiers (as I demonstrate in this post), you can use multiple servers and interleave identifiers by advancing each server's sequence by the total number of servers. (For example, with two servers, advance each sequence by two, so one server generates even identifiers, and the other generates odd.)

I'd rather not discuss the advantages and disadvantages of either approach, because it's exactly this debate that makes it very difficult to find any useful information on using sequential identifiers with MongoDB. Instead, I'm just going to explain how I did it, and hope this is helpful to someone. :-)

First, create a sequence collection that you can use to determine the next identifier in the sequence. The following creates a collection called seq that has a single sequence in it (for users), but you can add as many as you need:

  1. db.seq.insert({"_id":"users", "seq":new NumberLong(1)});

If you assign seq to 1 instead of new NumberLong(1), it will be interpreted as a float due to a JavaScript quirk.

Before adding a new user, you need to increment the sequence by one and fetch the next identifier. Fortunately, the findandmodify() command provides an atomic way to do this. Using the MongoDB shell, the command would look something like this:

  1. db.seq.findAndModify({
  2.     query: {"_id":"users"},
  3.     update: {$inc: {"seq":1}},
  4.     new: true
  5. });

Because I'm using Lithium, I added a method for fetching the next identifier to my User model:

  1. <?php
  2.  
  3. namespace app\models;
  4.  
  5. class User extends \lithium\data\Model {
  6.  
  7.     static public function seq() {
  8.         $seq = static::_connection()->connection->command(
  9.             array('findandmodify' => 'seq',
  10.                   'query' => array('_id' => 'users'),
  11.                   'update' => array('$inc' => array('seq' => 1)),
  12.                   'new' => TRUE
  13.             )
  14.         );
  15.  
  16.         return $seq['value']['seq'];
  17.     }
  18.  
  19. }
  20.  
  21. ?>

If you're not using Lithium, you can use the MongoDB class to execute a command().

With this in place, adding a new user is a simple process. I create an array called $data with everything I want to store for a user, and then do the following:

  1. <?php
  2.  
  3. $user = User::create($data);
  4. $user->_id = User::seq();
  5. $success = $user->save();
  6.  
  7. ?>

This example should be easy to adapt to any environment. Once you have the next identifier in the sequence, you simply store it as you would any other data.

I hope to blog more about both MongoDB and Lithium. As these technologies are still pretty new to me, please feel free to point out any improvements. I'll update the post accordingly.