jiananshi
jiananshi

Reputation: 456

mongodb concurrency on inserting document

Says we currently have two collections: Teachers and Students, we limit each teacher can only owns up to 30 students, surely I have already set a reference-key like studentsId, teachersId on each document.

When I'm about to create a new Student, how can I make sure I only create it when the specify teacher has less than 30 students?

// get students count under specify teacher
const StudentNums = await Teachers.findById(1234).studentsId.length;
if (StudentNums < 30) /* create student */

(above code is written JS)

undoubtedly above code would fail when concurrency, cause mongodb share read locks, any ideas? tks in advance.

Upvotes: 0

Views: 1369

Answers (2)

Sikorski
Sikorski

Reputation: 2691

Instead of handling anything related to concurrency, why not just check if your teacher can accomodate any more students ? You can check and update in a single operation to ensure atomicity.

It essentially boils down to adding student length check in find query like below :

teachers.update({id: 1, '$where': 'this.students.length < 8'}, {'$push': {students: 8}}

Added a test case below:

 const mongodb = require('mongodb')
 const mongoClient = mongodb.MongoClient

 mongoClient.connect('mongodb://localhost:27017/stackoverflow', function (err, db) {
   if (err) {
     console.log(`DB connect error ${JSON.stringify(err)}`)
   } else {
     const teachers = db.collection('teachers')
     let doc = {id: 1, name: 'Ms Anderson', students: [1, 2, 3, 4, 5, 6, 7], subject: 'Matrix'}
     teachers.insert(doc, {w: 1}, function (err, result) {
       console.log(`insert error ${JSON.stringify(err)}`)
       console.log(`insert result : ${JSON.stringify(result)}`)
//Check the $where parameter, it will return only if student array has size less than 8
       teachers.update({id: 1, '$where': 'this.students.length < 8'}, {'$push': {students: 8}}, {w: 1}, function (err, result) {
         console.log(`update error ${JSON.stringify(err)}`)
         console.log(`update result : ${JSON.stringify(result)}`)
       })
     })
   }
 })

result is 1 if document is updated else 0.

Upvotes: 1

Alex Blex
Alex Blex

Reputation: 37048

You can apply standard Optimistic lock on application level.

Assuming a document in teacher collection has following fields:

db.teachers.insert({
    _id: 1234,
    students: [12,34],
    version: 8
});

Where _id and students are object's payload, and version is an internal field for concurrency control. The update in the question may look like following:

var teacher = db.teachers.findById(1234);

if(teacher.students.length < 30) {
    var res = db.teachers.update({_id: 1234, version: teacher.version}, {$push: {students: 56}, $inc: {version: 1}});
}

if(res.nModified < 1) {
    // apply retry logic
}

If the concurrent update happened, version of the document in the database won't match the query part of the update, which will result with 0 documents updated.

Upvotes: 1

Related Questions