Reputation: 15099
I'm trying to implement an email notifications feature. I have a notifications collection where each document has the following:
{
userID: {type: ObjectId, index: true, required: true},
read: {type: Boolean, default: false, index: true},
emailed: {type: Boolean, default: false, index: true},
}
I have a node CronJob that once a day calls a function which is supposed to do the following (pseudocode):
foreach (user)
db.notifications.find({
userID: user._id,
read: false,
emailed: false
}, function(e, notifications) {
set emailed to true
sendNotificationsEmail(user, notifications)
});
However I can't figure out a way to fetch the relevant notifications and mark them as "emailed" in an atomic way such that if this code is executing on multiple servers at the same time there won't be a race condition where the user receives multiple emails.
Any ideas?
Upvotes: 0
Views: 247
Reputation: 15099
The following question and answer was extremely helpful: Solution to Bulk FindAndModify in MongoDB
Here's my solution:
Boolean emailed
field with a String emailID
field. Give each machine/reader a uniquely generated ID, emailID.Update them with:
db.notifications.update(
{_id: {$in: notificationIDs}, emailID: null, $isolated: true},
{$set: {emailID: emailID}},
{multi: true}
Find notifications with emailID set.
The trick is that with $isolated: true, either the whole write will happen or none of it. So if some other reader has already claimed the notifications with its emailID then this update will not go through and you can guarantee that one reader's update will finished before the other starts.
findEmailNotifications: function(user, emailID, callback) {
Notification.find({
read: false,
deleted: false,
userID: user._id,
emailID: null,
}, function(findError, notifications) {
// handle findError
var notificationIDs = getKeys(notifications, '_id');
if (notificationIDs.length === 0) {
callback(null, []);
return;
}
Notification.update(
{_id: {$in: notificationIDs}, emailID: null, $isolated: true},
{$set: {emailID: emailID}},
{multi: true},
function(updateError) {
// handle updateError
Notification.find({
read: false,
deleted: false,
userID: user._id,
emailID: emailID
}, function(findError, notifications) {
// handle findError
callback(null, notifications);
});
}
);
});
},
No thanks to downvoters who downvoted a well-formed question without specifying any valid reason. Hope this helps someone else.
Upvotes: 2