Mongoose.js transactions Mongoose.js transactions mongodb mongodb

Mongoose.js transactions


If you really must have transactions across multiple documents types (in separate collections) the means to achieve this is with a single table that stores actions to take.

db.actions.insert({ actions: [{collection: 'players', _id: 'p1', update: {$set : {name : 'bob'} } },            {collection: 'stories', _id: 's1', update: {$set : {location: 'library'} } }], completed: false }, callback);

This insert is atomic, and all done at once. You then can perform the commands in the 'actions' collection and mark them as complete or delete them as you complete them, calling your original callback when they are all completed. This only works if your actions processing loop is the only thing updating the db. Of course you'll have to stop using mongoose, but the sooner you do that the better you'll be anyway.


This question is quite old but for anyone who stumbles upon this page, you could use fawn. It's an npm package that solves this exact problem. Disclosure: I wrote it

Say you have two bank accounts, one belongs to John Smith and the other belongs to Broke Individual. You would like to transfer $20 from John Smith to Broke Individual. Assuming all first name and last name pairs are unique, this might look like:

var Fawn = require("fawn");var task = Fawn.Task()//assuming "Accounts" is the Accounts collection task.update("Accounts", {firstName: "John", lastName: "Smith"}, {$inc: {balance: -20}})  .update("Accounts", {firstName: "Broke", lastName: "Individual"}, {$inc: {balance: 20}})  .run()  .then(function(){    //update is complete   })  .catch(function(err){    // Everything has been rolled back.     //log the error which caused the failure     console.log(err);  });

Caveat:tasks are currently not isolated(working on that) so, technically, it's possible for two tasks to retrieve and edit the same document just because that's how MongoDB works.

It's really just a generic implementation of the two phase commit example on the tutorial site: https://docs.mongodb.com/manual/tutorial/perform-two-phase-commits/


You can simulate a transaction by manually rolling-back the changes whenever an error occurs. In your above example, simply remove the saved document if the other one fails.

You can do this easily using Async:

    function rollback (doc, cb) {      doc.remove(cb);    }    async.parallel([          player.save.bind(player),          story.save.bind(story),      ],      function (err, results) {        if (err) {          async.each(results, rollback, function () {            console.log('Rollback done.');          });        } else {          console.log('Done.');        }      });

Obviously, the rollback itself could fail -- if this is a not acceptable, you probably need to restructure your data or choose a different database.

Note: I discussed this in detail on this post.