Atomic Counters using Mongodb's findAndModify with PHP

A common problem when developing web applications is the ability to generate unique sequential numbers, my recent use case was an API which generated an order number on receiving an order but before the order had been stored in a database, normally I would use MySQL auto incrementing keys but I needed to send the order number back to the client long before the order was stored, as Mongodb was already in the application stack it seemed the natural place to generate a persistent source of sequential numbers.

MongoDB provides a useful function findAndModify which, while MongoDB doesn’t support transactions, allows a value to be retrieved and updated atomically, making it ideal for this situation where I wanted the order number to increment by one every time it was used.

For those who’re in the dark about why you’d need to retrieve and update a value in one operation, the advantage of using this over separate find and updates is that it avoids the race condition where the same value could be retrieved twice if another client attempted to retrieve the counter before the update has taken place.

Example

The findAndModify command is no use without something to find so I started with this document (in a collection named ‘Counters’:

db.Counters.save({name : 'order_no', value : 1});

Now we have a starting point we can go onto retrieving and modifying the value, from the MongoDB command line client this be done quite simply:

db.Counters.findAndModify({query : { name : 'order_no'}, update : { $inc : {value : 1}}})

The first query being the find section and the second the update, here I’ve used the $inc operator to increment the value by the specified number. By default this query returns the current value then updates it, if new : true.

As expected this query then returns the document and updates the value each time:

{
	'_id' : ObjectId('4ff9f8d43ddba5fc637aade3'),
	'name' : 'order_no',
	'value' : 1
}

then next time….:

{
	'_id' : ObjectId('4ff9f8d43ddba5fc637aade3'),
	'name' : 'order_no',
	'value' : 2
}

etc….

In the PHP driver found in the PECL repositories this command is currently not supported so must be executed using the execute() command, in my case I’m using this inside a Silex based app with the Doctrine Mongo abstraction layer (not ODM) so the syntax may be slightly different to the raw PHP Mongo library:

$record = $database->execute(
	'db.Counters.findAndModify({
		query : { name : '.$counter_name.'},
		 update : { $inc : {value : 1}}
		})'
);

There is currently an open bug report on the MongoDB PHP client for findAndUpdate support: https://jira.mongodb.org/browse/PHP-117

Limitations

One thing to be careful of, which is applicable to auto incrementing in most databases, is to ensure any replication is correctly configured to ensure there is no chance of using an old value after the value has been updated elsewhere.