Friday, January 4, 2019

MongoDB - E11000 duplicate key error


Summary

In this post, I discuss the symptoms and workaround for an atomicity bug in MongoDB.  This error manifests itself when performing collection updates from multiple threads, with upsert:true, on a unique key.  The bug has been open since 2014 and still unresolved.  https://jira.mongodb.org/browse/SERVER-14322

Bug Set Up

/*jshint esversion: 6 */

'use strict';
'use esversion 6';

const MongoClient = require('mongodb').MongoClient;
const CONNECTION_URL = 'mongodb://admin:mongo@localhost:27017';
const DB_NAME = 'testDB';
const COLLECTION = 'testCollection';

let connection;
let promises = [];

function update(i, db,  query, value, upsrt) {
 return db.collection(COLLECTION).updateOne(query, value, {'upsert' : upsrt})
 .then((result) => {
  return new Promise((resolve, reject) => { resolve('i: ' + i + ' time: ' + new Date().getTime());});
 })
 .catch(err => {
  throw err;
 });
}

MongoClient.connect(CONNECTION_URL, {useNewUrlParser : true})
.then((result) => {
 connection = result;
 const db = connection.db(DB_NAME);
 
 for (let i=0; i<5 .then="" :="" addtoset="" db="" funds="" i="" id="" indices="" johndoe="" let="" p="" promise.all="" promises.push="" promises="" query="" results="" return="" set="" true="" value=""> {
 results.forEach((result) => {
  console.log('result - ' + result);
 });
})
.catch(err => {
 console.log(err); 
})
.finally(() => {
 console.log('closing db connection');
 connection.close();
});

Explanation

The Node.js code above creates an array of promises, each of which attempts to perform a db update with upsert equal to 'true'.  The promises sent thru Promise.all to execute them all as a batch.

Results

The duplicate key error is generated, as expected.
{ MongoError: E11000 duplicate key error collection: testDB.testCollection index: _id_ dup key: { : "johnDoe" }
    at Function.create (/nas/archive/dev/workspace/blackjack/node_modules/mongodb-core/lib/error.js:43:12)
    at toError (/nas/archive/dev/workspace/blackjack/node_modules/mongodb/lib/utils.js:149:22)
    at coll.s.topology.update (/nas/archive/dev/workspace/blackjack/node_modules/mongodb/lib/operations/collection_ops.js:1399:39)
    at /nas/archive/dev/workspace/blackjack/node_modules/mongodb-core/lib/connection/pool.js:532:18
    at process.internalTickCallback (internal/process/next_tick.js:70:11)
  driver: true,
  name: 'MongoError',
  index: 0,
  code: 11000,
  errmsg:
   'E11000 duplicate key error collection: testDB.testCollection index: _id_ dup key: { : "johnDoe" }',
  [Symbol(mongoErrorContextSymbol)]: {} }
closing db connection
Below is the output of a read of this collection.  One update (#4 index) completed before the error was thrown and halted the execution.
> db.testCollection.find({});
{ "_id" : "johnDoe", "funds" : 400, "indices" : [ 4 ] }

Workaround

function updateWithRetry(i, db,  query, value, upsrt) {
 return db.collection(COLLECTION).updateOne(query, value, {'upsert' : upsrt})
 .then((result) => {
  return new Promise((resolve, reject) => { resolve('i: ' + i + ' time: ' + new Date().getTime());});
 })
 .catch(err => {
  if (err.code === 11000){
   console.log('i: ' + i + ' 11000 error');
   return updateWithRetry(i, db, query, value, false);
  }
  else {
   throw err;
  }
 });
}

Explanation

In this revised update block, I added a check for the 11000 error code.  If that's, in fact, the source of the error, I retry the update with a recursive call.

Results

i: 0 11000 error
i: 2 11000 error
i: 3 11000 error
i: 4 11000 error
result - i: 0 time: 1546649403149
result - i: 1 time: 1546649403146
result - i: 2 time: 1546649403149
result - i: 3 time: 1546649403149
result - i: 4 time: 1546649403149
closing db connection
> db.testCollection.find({});
{ "_id" : "johnDoe", "funds" : 400, "indices" : [ 1, 0, 2, 3, 4 ] }

Source


Copyright ©1993-2024 Joey E Whelan, All rights reserved.