Как смягчить состояние гонки при одновременном вызове функции, включающей операцию записи, которая зависит от операции чтения - PullRequest
0 голосов
/ 22 февраля 2019

У меня есть простая коллекция под названием Ledger со следующей схемой

Ledger: {
  account: String,
  amount: Number // actually an integer
}

У меня есть функция, которая дебетует счет на основании остатка на нем:

const debit = async (account_to_be_debited, amount_to_be_debited) => {
  let transactions = await Ledger.find({ account: account_to_be_debited })
  
  let balance = transactions.reduce((accumulated_balance, transaction) => {
    accumulated_balance += transaction.amount
    return accumulated_balance
  }, 0)
    
  if (balance < amount_to_be_debited) {
    throw new Error('insufficient funds')
  }
  
  let new_transaction = await Ledger.create({ account: account_to_be_debited, amount: -amount_to_be_debited })
  
  return new_transaction._id
}

Теперь я хочу иметь возможность сделать что-то подобное в асинхронной среде:

// the first debit
debit('account_1', 100).then(id => console.log(id))

// another debit
debit('account_1', 200).then(id => console.log(id))

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

let debit_1 = await debit('account_1', 100)
let debit_2 = await debit('account_1', 200)

Мне известно, что с выпуском mongodb 4.0 я могу использовать транзакции, но я не уверен, что mongodb выполняет транзакции синхронно.Если это так, то я могу быть уверен, что каждая последующая транзакция будет считывать изменения, отраженные самой последней зафиксированной транзакцией, и что чтение между транзакциями не может происходить одновременно, потому что транзакции не могут происходить одновременно.

ИтакМой вопрос: подходят ли транзакции mongodb для моего случая использования, особенно для смягчения состояния гонки, которое я описал выше?

Если нет, каковы возможные пути решения этой проблемы?Я пишу это приложение на узле js, и в настоящее время я использую mongoose для моделирования и взаимодействия с набором реплик mongodb.

Любая помощь очень ценится.Спасибо.

1 Ответ

0 голосов
/ 22 февраля 2019

Транзакции Монго обычно используются для того, чтобы убедиться, что набор обновлений / вставок в нескольких коллекциях (или в одной коллекции!) Либо завершился успешно, либо завершился неудачно, что не похоже на описанный вами сценарий использования.Вместо этого вы должны убедиться, что при наличии двух обновлений одно из них завершится сбоем, и вам следует попытаться обработать его на стороне базы данных (а не с помощью кода приложения).

Вы можетезапишите операцию обновления таким образом, чтобы она не выполнялась, если это не действительное обновление: db.ledger.updateOne({account: 'account_1', amount: {$gte: 200}}, {$inc: { amount: 200 }}).

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

...