8

Consider a schema:

var userSchema = mongoose.Schema({
...
followers: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }],
following: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }]
...
}

When userA follows userB, userB is to be pushed in userA.following and userA is to be pushed in userB.followers. Both operations require a .save().

What is a good way - perhaps conceptual - of ensuring that if either one of the .save() fails, both documents are left untouched?

k88074
  • 2,042
  • 5
  • 29
  • 43
  • Could you do without maintaining the `followers` list? Is it absolutely necessary? You could get the `followers` by querying the `following` fields of other users. If you could remove that it would be a single Atomic operation. – BatScream Dec 02 '14 at 20:28
  • Thanks for the comment. I just made an example of where atomicity may be required. To make a more general example, consider the Story-Author refs example on the [mongoose docs](http://mongoosejs.com/docs/populate.html). What to do if I created the Story, but something went wrong when adding the ref to the the author's document? – k88074 Dec 02 '14 at 20:41
  • 2
    See if this helps - http://cookbook.mongodb.org/patterns/perform-two-phase-commits/ – BatScream Dec 02 '14 at 21:25
  • 1
    @BatScream interesting reading. I think it gives the conceptual framework I was looking for. Thanks. How to implement this in Node.js-Mongoose is not intuitive, but I have something to reflect on. Just as a comment, I am a bit surprised by the fact that there is very little talking about Atomicity-Isolation-Consistency in the Nodejs community. Is it my perception or I am missing something? – k88074 Dec 03 '14 at 12:32
  • 1
    If we were to follow @BatScream's suggestion by querying the `following` fields, I was worried that it would add too much lookup time with a certain `userB` buried in arrays of different users, and I am okay with sacrificing some space for it. – writofmandamus Feb 14 '17 at 20:37

2 Answers2

7

I turned out doing as @BatScream suggested in the first comment. I reorganized the Schemas of my data and removed the list followers.

After reading quite a lot on the topic my humble conclusion is that doing transactions across multiple documents in mongoDB/Mongoose.js is, though doable, probably not the smartest-safest thing to do. When implementing two-phase commits rollback actions can themselves fail.

If atomic operations across multiple documents are necessary probably MongoDB is not the right tool. However, a bit of data restructuring/reorganization can perhaps some times make multidocument transactions unnecessary.

k88074
  • 2,042
  • 5
  • 29
  • 43
2

Use Mongoose's transaction API https://mongoosejs.com/docs/transactions.html . This lets you execute multiple queries in isolation and undo all of them if anyone of them fails