mongodb/mongoose, как обеспечить согласованность данных при изменении двух документов

Допустим, у меня есть база данных mongoDB, в которой я создаю документ, принадлежащий пользователю, где и документы, и пользователи хранятся в БД.

Далее скажем, что пользовательский объект содержит ссылку на документ.

Создание нового документа может выглядеть примерно так.

exports.create = function (req, res, next) {

  //make a new document
  var newDoc = new Document({
    title: req.body.title,
    content: req.body.content,
  });
  User.findById(req.user._id, function (err, user) {
    if (err) 
      return res.send(400);
    user.documents.push(newDocument._id);
    user.save(function(err){
      if (err) 
        return res.json(400, err);
      newDoc.save(function(err) {
        if (err) 
          return res.json(400, err); //<--- What if you have an error here??
        return res.json(200, {document:'successfully created'});
      });
    });
  });
};

Если у вас есть ошибка в последнем if(err), вы добавите ссылку на новый документ пользователю, но документ не будет создан.

И, конечно, это очень простой случай. Это может стать намного сложнее с многочисленными документами, ссылающимися друг на друга. Каков самый чистый способ справиться с этим в Mongo/Mongoose?


person honkskillet    schedule 24.03.2014    source источник
comment
Вы как будто ищете транзакции и откат, чего не существует. Если вам действительно необходимо поддерживать отдельные коллекции, по крайней мере, попытайтесь сохранить документ, на который есть ссылка, сначала. Затем обновите основной документ ссылкой. Любой откат придется делать вручную.   -  person Neil Lunn    schedule 25.03.2014


Ответы (2)


Похоже, что отношение вашего пользовательского документа — «один ко многим». Ваша текущая операция по добавлению нового документа требует записи в две коллекции.

MongoDB не предоставляет никаких транзакций (возможно, это будет в будущем), поэтому ваш единственный вариант — удалить идентификатор документа из массива документов пользователей, если сохранение документа не удалось. Но это потребует еще одной записи, которая также может завершиться ошибкой.

Чтобы быть более атомарным и сделать все за одну запись, вы можете добавить поле userId в коллекцию документов и удалить массив decuments из коллекции пользователей.

Также создайте индекс для userId в коллекции документов. Теперь ваш документ ссылается на пользователя, и для создания нового документа требуется только одна запись. Если эта запись завершается ошибкой, в вашей базе данных ничего не меняется, и согласованность сохраняется.

person chatoooo    schedule 24.03.2014
comment
Таким образом, компромисс для атомарности заключается в том, чтобы выполнять запрос при поиске всех документов пользователя. - person honkskillet; 25.03.2014
comment
Вам все равно придется выполнять запрос при поиске пользовательских документов, потому что у вас есть только их _id. Индексированное поле userId делает запрос молниеносным. Вы также можете вставлять документы непосредственно в пользователя, но, скорее всего, размер документа превысит 16 МБ. - person chatoooo; 25.03.2014
comment
Ах. Приведенный мной пример кода немного упрощен по сравнению с моим реальным. В моем приложении прямо сейчас пользователь и документы ссылаются друг на друга. - person honkskillet; 25.03.2014
comment
Также теперь вы можете использовать виртуальные машины с мангустом. См. thecodebarbarian.com/mongoose-virtual-populate. - person Damien; 07.05.2019

Обдумывая свой вопрос, я наткнулся на это... обсуждение двухфазных коммитов с MongoDB. http://docs.mongodb.org/manual/tutorial/perform-two-phase-commits/

Это не тот ответ, который я приму, но я думаю, что он уместен.

person honkskillet    schedule 25.03.2014
comment
интересно, что ваша ссылка теперь перенаправляет на docs.mongodb.com/manual/core/transactions - person Damien; 07.05.2019