Рубрики
Без рубрики

Транзакции MongoDB против двухфазных фиксаций

Узнайте, как работают транзакции MongoDB и как они сравниваются с использованием двухфазного шаблона фиксации.

Автор оригинала: Christian Amor Kvalheim.

В этой статье мы собираемся расследовать разницу в производительности между использованием Mongodb транзакции или Двухфазная совершить простую передачу банковского счета. Тесты были запущены на местном рабочем столе и на ограниченном наборе счетов, чтобы убедиться, что мы можем получить прерывистые конфликты пишета.

Ссылки

Узнайте больше о Монгодб в следующих местах.

http://learnmongodbthehardway.com/ Официальный сайт, чтобы узнать о MongoDB
https://github.com/learn-mongodb-the-hardway Официальный сайт GitHub Организация
https://leanpub.com/mongodbschemadesign Маленькая схемы дизайна книги
https://gitter.im/mongodb/learnmongodbthehardway Gitter Chatroom.
http://christiankvalheim.com/ Авторы веб-страницы
https://twitter.com/christkv Авторы Twitter.

Код

Мы собираемся попытаться передать сумму денег между двумя аккаунтами и откатными, если это не удается. Есть две реализации. Первый основан на использовании Двухфазная совершать без использования Multi-Document транзакции. Вторая реализация использует новый Multi-Document Поддержка транзакции в MongoDB 4.0.x или выше. Для простого ради которого мы представляем код, используя Mongo Shell синтаксис.

Для фактического бенчмаркинга мы использовали рамки Kotlin/Java, которая находится в разработке, которые можно найти в HTTPS://github.com/learn-mongodb-the-hardway/mongodb-schema-simulator.

Многолетняя транзакция

Для Multi-Document Подход транзакций мы повторяем транзакции о неудаче, чтобы она проходила. Ниже приведен некоторый пример код того, как это можно сделать в Mongo Shell Отказ

var db = db.getSisterDB("bank");
var session = db.getMongo().startSession();
var accounts = session.getDatabase("bank").accounts;
var transactions = session.getDatabase("bank").transactions;

// Retries a transaction commit
function retryUnknownTransactionCommit(session) {
  while(true) {
    try {
      // Attempt to commit the transaction
      session.commitTransaction();
      break;
    } catch (err) {
      if (err.errorLabels != null 
        && err.errorLabels.includes("UnknownTransactionCommitResult")) {
          // Keep retrying the transaction
          continue;
        }

      // The transaction cannot be retried, 
      // return the exception
      return err;
    }
  }
}

function executeTransaction(session, from, to, amount) {
  while (true) {
    try {
      // Start a transaction on the current session
      session.startTransaction({ 
        readConcern: { level: "snapshot" }, writeConcern: { w: "local" } 
      });

      // Debit the `from` account
      var result = accounts.updateOne(
        { name: from, amount: { $gte: amount } }, 
        { $inc: { amount: -amount } });

      // If we could not debit the account, abort the
      // transaction and throw an exception
      if (result.modifiedCount == 0) {
        session.abortTransaction();
        throw Error("failed to debit the account [" + from + "]");
      }

      // Credit the `from` account
      result = accounts.updateOne(
        { name: to }, 
        { $inc: { amount: amount } });

      // If we could not credit the account, abort the
      // transaction and throw an exception
      if (result.modifiedCount == 0) {
        session.abortTransaction();
        throw Error("failed to credit the account [" + to + "]");
      }

      // Insert a record of the transaction
      transactions.insertOne(
        { from: from, to: to, amount: amount, on: new Date() });

      // Attempt to commit the transaction
      session.commitTransaction();
      // Transaction was committed successfully break the while loop
      break;
    } catch (err) {
      // If we have no error labels rethrow the error
      if (err.errorLabels == null) {
        throw err;
      }

      // Our error contains UnknownTransactionCommitResult label
      if (err.errorLabels.includes("UnknownTransactionCommitResult")) {
        // Retry the transaction commit
        var exception = retryUnknownTransactionCommit(session, err);
        // No error, commit as successful, break the while loop
        if (exception == null) break;
        // Error has no errorLabels, rethrow the error
        if (exception.errorLabels == null) throw exception;

        // Error labels include TransientTransactionError label
        // Start while loop again, creating a new transaction
        if (err.errorLabels.includes("TransientTransactionError")) {
          continue;
        }
        
        // Rethrow the error
        throw exception;
      }

      // Error labels include TransientTransactionError label
      // Start while loop again, creating a new transaction
      if (err.errorLabels.includes("TransientTransactionError")) {
        continue;
      }

      // Rethrow the error
      throw err;
    }
  }
}  

executeTransaction(session, "Peter", "Joe", 100);

Давайте сломаем код в грубых шагах. Сначала давайте посмотрим на Идеальное управление метод.

function executeTransaction(session, from, to, amount) {
  while (true) {
    try {
      // Start a transaction on the current session
      session.startTransaction({ 
        readConcern: { level: "snapshot" }, writeConcern: { w: "local" } 
      });

      // Debit the `from` account
      var result = accounts.updateOne(
        { name: from, amount: { $gte: amount } }, 
        { $inc: { amount: -amount } });

      // If we could not debit the account, abort the
      // transaction and throw an exception
      if (result.modifiedCount == 0) {
        session.abortTransaction();
        throw Error("failed to debit the account [" + from + "]");
      }

      // Credit the `from` account
      result = accounts.updateOne(
        { name: to }, 
        { $inc: { amount: amount } });

      // If we could not credit the account, abort the
      // transaction and throw an exception
      if (result.modifiedCount == 0) {
        session.abortTransaction();
        throw Error("failed to credit the account [" + to + "]");
      }

      // Insert a record of the transaction
      transactions.insertOne(
        { from: from, to: to, amount: amount, on: new Date() });

      // Attempt to commit the transaction
      session.commitTransaction();
      // Transaction was committed successfully break the while loop
      break;
    } catch (err) {
      // If we have no error labels rethrow the error
      if (err.errorLabels == null) {
        throw err;
      }

      // Error labels include TransientTransactionError label
      // Start while loop again, creating a new transaction
      if (err.errorLabels.includes("TransientTransactionError")) {
        continue;
      }

      // Our error contains the UnknownTransactionCommitResult label
      if (err.errorLabels.includes("UnknownTransactionCommitResult")) {
        // Retry the transaction commit
        var exception = retryUnknownTransactionCommit(session);
        // No error, commit as successful, break the while loop
        if (exception == null) break;
        // Error has no errorLabels, rethrow the error
        if (exception.errorLabels == null) throw exception;

        // Error labels include TransientTransactionError label
        // Start while loop again, creating a new transaction
        if (err.errorLabels.includes("TransientTransactionError")) {
          continue;
        }
        
        // Rethrow the error
        throw exception;
      }

      // Rethrow the error
      throw err;
    }
  }
}  

Сначала мы создаем новый транзакция И тогда мы применяем операции, чтобы передать деньги с одного аккаунта на другой. Первое утверждение Дебеты от учетная запись.

var result = accounts.updateOne(
  { name: from, amount: { $gte: amount } }, 
  { $inc: { amount: -amount } });
if (result.modifiedCount == 0) {
  session.abortTransaction();
  throw Error("failed to debit the account [" + from + "]");
}

Если дебет не удается прервать транзакцию и бросить ошибку, чтобы сигнализировать дебет операция не удалась. Далее мы кредит к учетная запись.

result = accounts.updateOne(
  { name: to }, 
  { $inc: { amount: amount } });
if (result.modifiedCount == 0) {
  session.abortTransaction();
  throw Error("failed to credit the account [" + to + "]");
}

Если кредит Не удается прервать транзакцию и бросить ошибку, чтобы сигнализировать кредит операция не удалась. Наконец мы запись перевод.

transactions.insertOne(
    { from: from, to: to, amount: amount, on: new Date() });

Как только мы создали все операции, мы пытаемся совершить транзакция Отказ

session.commitTransaction();

Если транзакция терпит неудачу, вот когда начинается веселье. Давайте посмотрим на Исключение умение обращаться.

} catch (err) {
  // If we have no error labels rethrow the error
  if (err.errorLabels == null) {
    throw err;
  }

  // Error labels include TransientTransactionError label
  // Start while loop again, creating a new transaction
  if (err.errorLabels.includes("TransientTransactionError")) {
    continue;
  }

  // Our error contains the UnknownTransactionCommitResult label
  if (err.errorLabels.includes("UnknownTransactionCommitResult")) {
    // Retry the transaction commit
    var exception = retryUnknownTransactionCommit(session);
    // No error, commit as successful, break the while loop
    if (exception == null) break;
    // Error has no errorLabels, rethrow the error
    if (exception.errorLabels == null) throw exception;

    // Error labels include TransientTransactionError label
    // Start while loop again, creating a new transaction
    if (err.errorLabels.includes("TransientTransactionError")) {
      continue;
    }
    
    // Rethrow the error
    throw exception;
  }

  // Rethrow the error
  throw err;
}

Если у нас есть объект ошибки без ErrorLabels Мы образуемся, так как транзакция не может быть получена. Однако, если у нас есть ErrorLabels Нам нужно осмотреть их.

Если этикетка Трансэртеррансдействорор присутствует мы не можем повторить текущую транзацию, чтобы мы Продолжить Будучи петлями, заставляя создание новой транзакции.

Однако, если ErrorLabels Содержит метку UnvernuretransactionCommitresult Мы можем повторить текущую транзакцию. Мы делаем это, позвонив в RetryunknowntransactionCommit Функция с текущим сеансом. Давайте посмотрим на функцию в деталях.

// Retries a transaction commit
function retryUnknownTransactionCommit(session) {
  while(true) {
    try {
      // Attempt to commit the transaction
      session.commitTransaction();
      break;
    } catch (err) {
      if (err.errorLabels != null 
        && err.errorLabels.includes("UnknownTransactionCommitResult")) {
          // Keep retrying the transaction
          continue;
        }

      // The transaction cannot be retried, 
      // return the exception
      return err;
    }
  }
}

Пока session.committransaction () Вызов возвращает UnvernuretransactionCommitresult Мы продолжаем повторять транзакцию. Если совершение транзакции успешно, мы вырвались из цикла возвращающихся NULL. Если ошибка возвращается, отличается от UnvernuretransactionCommitresult Мы возвращаем ошибку.

Вернувшись в точку, где мы называем RetryunknowntransactionCommit Функция мы видим следующую логику.

// Retry the transaction commit
var exception = retryUnknownTransactionCommit(session);
// No error, commit as successful, break the while loop
if (exception == null) break;
// Error has no errorLabels, rethrow the error
if (exception.errorLabels == null) throw exception;

// Error labels include TransientTransactionError label
// Start while loop again, creating a new transaction
if (err.errorLabels.includes("TransientTransactionError")) {
  continue;
}

// Rethrow the error
throw exception;

Если возвращено Исключение это null Мы разбиваем в то время как петля, поскольку наша транзакция была успешно совершена. Если Исключение не включает ErrorLabels Мы Retrow исключение. С другой стороны, если исключение содержит ErrorLabels И этикетки включают метку Трансэртеррансдействорор Мы не можем повторить текущую транзацию, чтобы мы Продолжить Будучи петлями, заставляя создание новой транзакции.

Два фазных фиксации

Две фазы Подход Commit использует другой подход с использованием двойного бухгалтерского учета для обеспечения последовательных передач учетной записи. Давайте посмотрим на автономный пример ниже, что витрины, как можно реализовать шаблон с использованием Mongo Shell Отказ

var db = db.getSisterDB("bank");
db.dropDatabase();
var accounts = db.accounts;
var transactions = db.transactions;

accounts.insertOne({ _id: 1, name: "Joe Moneylender", balance: 1000, pendingTransactions:[] });
accounts.insertOne({ _id: 2, name: "Peter Bum", balance: 1000, pendingTransactions:[] });

function cancel(id) {
  transactions.updateOne(
    { _id: id }, 
    { $set: { state: "canceled" } }
  );
}

function rollback(from, to, amount, id) {
  // Reverse debit
  accounts.updateOne({
      name: from, 
      pendingTransactions: { $in: [id] }
    }, {
      $inc: { balance: amount }, 
      $pull: { pendingTransactions: id }
    });  

  // Reverse credit
  accounts.updateOne({
    name: to, 
    pendingTransactions: { $in: [id] }
  }, {
    $inc: { balance: -amount }, 
    $pull: { pendingTransactions: id }
  });  

  cancel(id);
}

function cleanup(from, to, id) {
  // Remove the transaction ids
  accounts.updateOne(
    { name: from }, 
    { $pull: { pendingTransactions: id } });
  
  // Remove the transaction ids
  accounts.updateOne(
    { name: to }, 
    { $pull: { pendingTransactions: id } });

  // Update transaction to committed
  transactions.updateOne(
    { _id: id }, 
    { $set: { state: "done" } });
}

function executeTransaction(from, to, amount) {
  var transactionId = ObjectId();

  transactions.insert({
    _id: transactionId, 
    source: from, 
    destination: to, 
    amount: amount, 
    state: "initial"
  });

  var result = transactions.updateOne(
    { _id: transactionId }, 
    { $set: { state: "pending" } }
  );

  if (result.modifiedCount == 0) {
    cancel(transactionId);
    throw Error("Failed to move transaction " + transactionId + " to pending");
  }

  // Set up pending debit
  result = accounts.updateOne({
    name: from, 
    pendingTransactions: { $ne: transactionId }, 
    balance: { $gte: amount }
  }, {
    $inc: { balance: -amount }, 
    $push: { pendingTransactions: transactionId }
  });

  if (result.modifiedCount == 0) {
    rollback(from, to, amount, transactionId);
    throw Error("Failed to debit " + from + " account");
  }

  // Setup pending credit
  result = accounts.updateOne({
    name: to, 
    pendingTransactions: { $ne: transactionId }
  }, {
    $inc: { balance: amount }, 
    $push: { pendingTransactions: transactionId }
  });

  if (result.modifiedCount == 0) {
    rollback(from, to, amount, transactionId);
    throw Error("Failed to credit " + to + " account");
  }

  // Update transaction to committed
  result = transactions.updateOne(
    { _id: transactionId }, 
    { $set: { state: "committed" } }
  );

  if (result.modifiedCount == 0) {
    rollback(from, to, amount, transactionId);
    throw Error("Failed to move transaction " + transactionId + " to committed");
  }

  // Attempt cleanup
  cleanup(from, to, transactionId);
}

executeTransaction("Joe Moneylender", "Peter Bum", 100);

Давайте сломаемся функцией Идеальное управление Шаг за шагом и обсудите, что происходит на каждом шаге, и как восстановить от ошибки.

transactions.insert({
  _id: transactionId, 
  source: from, 
  destination: to, 
  amount: amount, 
  state: "initial"
});

Первый шаг вставляет новый документ в транзакции Коллекция, которая содержит информацию о передаче, которую мы собираемся выполнять. Состояние транзакция установлен на Первоначальный Сигнализация мы только начали процесс.

var result = transactions.updateOne(
  { _id: transactionId }, 
  { $set: { state: "pending" } }
);

if (result.modifiedCount == 0) {
  cancel();
  throw Error("Failed to move transaction " + transactionId + " to pending");
}

Далее мы пытаемся перевернуть транзакцию в состояние В ожидании Отказ Если это не удается ( Результат. ModiDedCount ) Мы пытаемся отменить транзакцию, вызывающую функцию Отмена Отказ Давайте посмотрим на то, что Отмена Функция делает.

function cancel(id) {
  transactions.updateOne(
    { _id: id }, 
    { $set: { state: "canceled" } }
  );
}

Функция в основном пытается установить Государство транзакции до отменен Отказ После возвращения из Отмена Функция, мы бросаем исключение сигнализацию вызывающего абонента Идеальное управление Функция, которая не удалась.

Однако, если мы успешным при установке транзакция Государство в В ожидании Мы можем начать процесс применения транзакция Отказ

result = accounts.updateOne({
  name: from, 
  pendingTransactions: { $ne: transactionId }, 
  balance: { $gte: amount }
}, {
  $inc: { balance: -amount }, 
  $push: { pendingTransactions: transactionId }
});

if (result.modifiedCount == 0) {
  rollback(from, to, amount, transactionId);
  throw Error("Failed to debit " + from + " account");
}

Мы смотрим на от Учетная запись, обеспечение того, чтобы PendendsTransactions Массив не содержит трансзариемый И что аккаунт Баланс это Большой или равный на сумму, которую мы собираемся в дебету. Если мы сочтены документами, мы собираемся дебет аккаунт Баланс По сумма и нажмите трансзариемый к PendendsTransactions множество.

Если документ не был изменен, мы знаем Обновить от учетной записи не удалось, и нам нужно позвонить в Откат Функция, чтобы отменить транзакцию, прежде чем мы выбрасываем исключение, сигнализирующее приложение, что перенос не удался. Давайте посмотрим на Откат функция.

function rollback(from, to, amount, id) {
  // Reverse debit
  accounts.updateOne({
      name: from, 
      pendingTransactions: { $in: [id] }
    }, {
      $inc: { balance: amount }, 
      $pull: { pendingTransactions: id }
    });  

  // Reverse credit
  accounts.updateOne({
    name: to, 
    pendingTransactions: { $in: [id] }
  }, {
    $inc: { balance: -amount }, 
    $pull: { pendingTransactions: id }
  });  

  cancel(id);
}

Чтобы откатиться транзакцией, нам нужно обратить вспять на от и к учетные записи. Сначала мы должны удалить транзакцию из от Учетная запись, возвращая зарезервированные сумма к Баланс Отказ

accounts.updateOne({
    name: from, 
    pendingTransactions: { $in: [id] }
  }, {
    $inc: { balance: amount }, 
    $pull: { pendingTransactions: id }
  });  

Мы обновим учетную запись, если она содержит транзакция Подходя на Имя И если PendendsTransactions Массив содержит Идентификатор транзакции Отказ Если документы совпадают, мы добавим сумма к Баланс и удалить Идентификатор транзакции от PendendendTransaction Отказ Далее нам нужно обратить вспять транзакция на к аккаунт также.

accounts.updateOne({
  name: to, 
  pendingTransactions: { $in: [id] }
}, {
  $inc: { balance: -amount }, 
  $pull: { pendingTransactions: id }
});  

Единственное отличие от от Учет в том, что мы вычесть сумма от Баланс отчета. Наконец мы называем Отмена Метод установить транзакцию Государство к отменен Отказ Возвращаясь к Идеальное управление Функция позволяет посмотреть на следующее утверждение.

result = accounts.updateOne({
  name: to, 
  pendingTransactions: { $ne: transactionId }
}, {
  $inc: { balance: amount }, 
  $push: { pendingTransactions: transactionId }
});

if (result.modifiedCount == 0) {
  rollback(from, to, amount, transactionId);
  throw Error("Failed to credit " + to + " account");
}

Так же, как в случае применения трансзариемый к от аккаунт мы гарантируем учетная запись еще не содержит трансзариемый В PendendendTransaction Отказ Если это не существует в PendendendTransaction Мы добавляем сумма к Баланс и нажмите трансзариемый к PendendsTransactions множество.

Если документ не удается обновить, мы называем Откат Функция, поскольку мы делали ранее, а затем выбросьте исключение, чтобы сигнализировать о приложении, транзакция не удалась.

Наконец мы собираемся перевернуть состояние транзакция к совершил Отказ

result = transactions.updateOne(
  { _id: transactionId }, 
  { $set: { state: "committed" } }
);

if (result.modifiedCount == 0) {
  rollback(from, to, amount, transactionId);
  throw Error("Failed to move transaction " + transactionId + " to committed");
}

Если это не удается, мы называем Откат функция, чтобы изменить транзакцию. Наконец мы называем Очистка функция. Давайте посмотрим на то, что делает функция.

function cleanup(from, to, id) {
  // Remove the transaction ids
  accounts.updateOne(
    { name: from }, 
    { $pull: { pendingTransactions: id } });
  
  // Remove the transaction ids
  accounts.updateOne(
    { name: to }, 
    { $pull: { pendingTransactions: id } });

  // Update transaction to committed
  transactions.updateOne(
    { _id: id }, 
    { $set: { state: "done" } });
}

Первое обновление удалит трансзариемый от от учетная запись. Второе обновление сделает то же самое для к учетная запись. Наконец последнее обновление установит транзакцию Государство к сделано завершение передачи между двумя счетами.

Выполнение производительности и анализ

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

Первый сценарий – это тот, где у нас есть Одиночная нить Выполнение передачи учетной записи каждый Миллисекан для 35 секунд Отказ

Второй сценарий мы запускаем одинаковую передачу каждого Миллисекан Но используя Пять ниток для 35 секунд Отказ

Мы используем инструмент симулятора схемы в HTTPS://github.com/learn-mongodb-the-hardway/mongodb-schema-simulator для генерации нагрузки и записи результатов.

Мы берем измерение от От 5 до 35 секунд Чтобы избежать начального периода кеш разматывает на Монгодб а также Java Jit Warmup Отказ

Одиночная нить

Для сценария единого потока мы получаем следующие результаты.

Транзакция, ReadConcern: Snapshot, WriteConcern: Local

График выше показывает результаты транзакция подход.

Двухфазная, WriteConcern: W1

График выше показывает результаты Двухфазная подход. Давайте возьмем ключ и поместите их в таблицу для ослабления сравнения.

Среднее мс. 2.02 мс. 4,35 мс.
мин мс. 1.6335 мс 3,2685 мс.
Макс мс 47.2947 мс 70,4311 мс
95 процентилей MS. 2,38 мс. 5,45 мс.
99 процентилей MS. 2,64 мс. 6,02 мс

Я> Среднее это Среднее геометрическое Отказ Среднее геометрическое означает среднее или среднее, что указывает на центральную тенденцию или типичное значение набора чисел Я> мин это минимальное значение, найденное в наборе. Я> Макс Максимальное значение найдено в наборе. Я> PTH процентиль это Процент данных, которые меньше значения. А 95 процентилей 100 будет означать 95% значений в наборе VAS ниже 100 Отказ

Определение Википедии

Давайте сломаем цифры.

  1. Среднее Для подхода к транзакциям есть ~ 2x ниже.
  2. мин Для подхода к транзакциям есть ~ 2x ниже.
  3. Макс Для подхода к транзакциям есть ~ 2x ниже.
  4. 95 процентилей Для подхода к транзакциям есть ~ 2x ниже.
  5. 99 процентилей Для подхода к транзакциям есть ~ 2x ниже.

Глядя на это, мы видим, что поддержка транзакции примерно в два раза быстрее, чем Двухфазная Сделайте подход. Это имеет смысл, так как объем операций, которые нам нужно выполнить против Монгодб закончить Двухфазная Commit, больше, чем то, что нужно для транзакция подход.

Учитывая это, мы могли бы сделать вывод, что транзакция Подход превосходит Двухфазная подход. Но держите своих лошадей. Давайте посмотрим, что произойдет, когда мы заставляем транзакция столкновения путем увеличения нагрузки.

Несколько потоков

Для сценария нескольких потоков мы получаем следующие результаты.

Транзакция, ReadConcern: Snapshot, WriteConcern: Local

Первый график показывает передачу учетной записи, используя Mongodb 4.0.x Подход поддержки транзакции.

Двухфазная, WriteConcern: W1

Второй график показывает передачу учетной записи, используя Двухфазная Сделайте подход. Давайте схватить главные числа и сравнивать их.

Среднее мс. 7,42 мс. 7,79 мс.
мин мс. 1,6434 мс. 4.7189 мс.
Макс мс 87.0411 MS. 116,416 мс
95 процентилей MS. 19,98 мс 11,23 мс.
99 процентилей MS. 30,62 мс 34,54 мс.

Давайте сломаем цифры.

  1. Среднее очень похоже между двумя подходами.
  2. мин Для подхода транзакции ниже.
  3. Макс Для подхода транзакции ниже.
  4. 95 процентилей выше для подхода транзакции.
  5. 99 процентилей очень похоже между двумя подходами.

Мы можем видеть, что Двухфазная совершать в целом лучшую общую характеристику производительности из-за 95 процентилей говоря …| 95% операций занял 19,98 мс или меньше по сравнению с 30,62 мс для подхода к транзакциям.

Причина Двухфазная Подход сейчас конкурентоспособен с транзакция Подход, связан с транзакция Подход, начинающий опыт писать конфликты Из-за более высокой одновременной нагрузки, заставив ориентир, чтобы повторить транзакции, пока они не будут успешными.

Заключение

С начальной проверки казалось бы, что использование транзакции будет все еще жизнеспособным способом вперед, так как разница не является огромной между двумя подходами. Однако прежде чем мы решим, мы должны принимать во внимание пару дополнительных факторов.

  1. Транзакции работают только на реплизетки как Mongodb 4.0.x Отказ
  2. Максимум Время работы для транзакции составляет минуту (что-либо на минуту становится прерванным).
  3. Блокировка документов для транзакций может привести к узким местам производительности на документах, которые получают много пишет, поскольку все транзакции получают сериализацию и должны дождаться их очередь.

Однажды Монгодб Поддерживает Шардированный транзакции ваши Воспроизведение транзакции могут в конечном итоге распределить транзакции, которые включают несколько осколки Отказ Это представляет большой штраф производительности, а также новые и очень сложные режимы отказа, любые, которые могут привести к сбою транзакции. В этом случае его очень вероятно, Двухфазная Подход Commit, значительно превзойдет подход транзакций.