Есть ли транзакции в MongoDB
Что проверяют
Знаете ли вы актуальное состояние MongoDB, а не миф «в NoSQL транзакций нет», и понимаете ли их цену. Следом обычно спрашивают, как это выглядит в коде на Node.js и почему локально транзакция падает с ошибкой.
Как отвечать
- один документ: операция над одним документом атомарна всегда, включая вложенные массивы и поддокументы. Поэтому многие задачи решаются моделью данных;
- многодокументные транзакции: с версии 4.0 на replica set, с 4.2 и на шардированных кластерах. Внутри транзакции данные читаются из согласованного снимка, изменения видны другим только после коммита;
- развёртывание: на standalone-сервере транзакций нет. Для локальной разработки поднимают replica set из одного узла;
- лимит времени: по умолчанию транзакция живёт не дольше 60 секунд (
transactionLifetimeLimitSeconds), потом её прерывают.
В драйвере mongodb удобнее всего session.withTransaction(): он коммитит, откатывает при исключении и повторяет попытку при ошибках с метками TransientTransactionError и UnknownTransactionCommitResult.
const session = client.startSession();
try {
await session.withTransaction(async () => {
await accounts.updateOne({ _id: from }, { $inc: { balance: -sum } }, { session });
await accounts.updateOne({ _id: to }, { $inc: { balance: sum } }, { session });
});
} finally {
await session.endSession();
}В Mongoose то же через connection.transaction() или session.withTransaction(). При одновременной записи в один документ одна из транзакций получает WriteConflict и повторяется, поэтому длинные транзакции бьют по производительности.
Где спотыкаются
Забывают передать { session } в одну из операций. Она выполнится вне транзакции и не откатится вместе с остальными, причём без всякой ошибки.
Пишут ручные startTransaction() и commitTransaction() без обработки повторов. Под нагрузкой временные ошибки неизбежны, и без ретраев запросы просто падают.
Берут транзакцию там, где хватило бы одного документа с вложенными данными или атомарного $inc. Сильнее звучит ответ, где вы сначала думаете о схеме, а транзакцию оставляете для случаев, когда без неё не обойтись.