Здесь гонка read–modify–write: два запроса могут проверить один баланс и перезаписать результаты друг друга. Проверку остатка и уменьшение нужно выполнять атомарно, а сумму валидировать.
Гонка read–modify–write при списании баланса
Здесь гонка read–modify–write: два запроса могут проверить один баланс и перезаписать результаты друг друга. Проверку остатка и уменьшение нужно выполнять атомарно, а сумму валидировать.
Короткий ответ
Что ответить на собеседовании
Подробный разбор
Ответ с пояснениями
Условие
def withdraw(user_id, amount): user = User.objects.get(id=user_id) if user.balance < amount: raise InsufficientFundsError() user.balance -= amount user.save()
Ответ
Здесь гонка read–modify–write: два запроса могут проверить один баланс и перезаписать результаты друг друга. Проверку остатка и уменьшение нужно выполнять атомарно, а сумму валидировать.
Для суммы в целых минимальных единицах:
from django.db.models import F
def withdraw(user_id, amount):
if type(amount) is not int or amount <= 0:
raise ValueError("Некорректная сумма")
updated = User.objects.filter(
id=user_id, balance__gte=amount
).update(balance=F("balance") - amount)
if not updated:
raise InsufficientFundsError()
Пример не различает отсутствующего пользователя и недостаточный остаток. Для реальной операции нужны проверка полномочий, идемпотентный идентификатор, журнал проводок и общая транзакция с ним. update() не вызывает модельный save().
Источник: docs.djangoproject.com.