Quadear
Quadear

Reputation: 517

Duplicate Index on mongoDB / Node.js

Strange question / behaviour on my side: I have a mongoDB collection with a Index Unique on the _id Field.

In order to add an element to the collection and to be sure i'm not erasing the document if it already exists, I'm using this request:

db.getCollection('name').updateOne({
  "_id": "5bf9c75b478b493e9c989d96"
}, {
  "$setOnInsert": {
    "_id": "5bf9c75b478b493e9c989d96",
    "offset": 53,
    "data": {
      // some data
    }
  }
}, {
  'upsert' : true
})

If I'm not mistaken, this means: try to find the :id, if it doesn't exist, insert the beautiful structure. If the :id is found, the $setOnInsert won't be triggered and the request is ignored.

This works perfectly in robomongo, but when i'm trying to integrate it to my node.js program, it throws a duplicate Id exception if the structure with an existing :id is found, because it seems it still tries to write it.

Do we agree that the whole operation (finding, setting, modifying or not the collection) should be atomic ? I know that the problem is comming from the fact that I'm doing that in a micro services env, so many threads trying to execute the same operation on Mongo, that's why I NEED it to be atomic.

EDIT Following Alex's answer:

The mongo Error:

{
  "driver": true,
  "name": "MongoError",
  "index": 0,
  "code": 11000,
  "errmsg": "E11000 duplicate key error collection: eventSourcing.name index: _id_ dup key: { : ObjectId('5bfbd5a0478b493e9c989db0') }"
}

My code:

public saveIfAbsent(aggregate: T, writer: Writer<T, Schema>): Promise<UpdateWriteOpResult> {
  const element = writer(aggregate)

  if (element['_id'] !== aggregate._id) {
    throw new Error("should not happen")
  }

  return super.collection().then((collection) => {
    return collection.updateOne({
      '_id': aggregate._id
    }, {
      '$setOnInsert': element
    }, {
      'upsert' : true
    })
  }).catch((e) => {
    console.log(e)
    return Promise.reject(e)
  })
}

Any ideas on how I could make it work ? I want something able to write only if the structure is not here, and not doing anything otherwise.

Thanks

Edit:

It seems I'm trying to do something impossible ... or I'm doing it wrong.

const db = new Database()

const data = new Array<string>()
const length = 500; // user defined length

for (var i = 0; i < length; i++) {
  data.push('toto')
}

Promise.all(data.map((e) => db.db.then((db) => db.collection('name').updateOne({
  '_id': 1,
}, {
  '$setOnInsert': {
    'id': 1,
    'data': e
  }
}, {
  upsert: true
})))).then(() =>
  console.log('true'))

This piece of code tries to push in base { 'id': 'data': 'toto' }.

Expected behavior:

Current behavior:

Native mongo DB driver for node JS v3.1.10

I'm trying to have the Expected behavior in my code, is it any way to do it ?

Upvotes: 1

Views: 894

Answers (1)

Alex Blex
Alex Blex

Reputation: 37018

this.representationId(aggregate) does not return the same data/type that element._id has.

Either check it before:

if (this.representationId(aggregate) !== element._id) {
    throw new Error("Impossible happened!")
}

or use element._id in the query

return collection.updateOne({
    '_id': element._id
  }, {
    '$setOnInsert': element
  }, {
    'upsert': true
  })
})

or do both.

Upvotes: 1

Related Questions