Reputation: 456
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
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
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