#django #concurrency
#django #параллелизм
Вопрос:
Я внедряю eauction toy-app в Django и не понимаю, как наилучшим образом обрабатывать параллелизм в приведенном ниже коде. Я не уверен, какой из моих кандидатов на решение (или любой другой) лучше всего подходит для дизайна Django. Я довольно новичок в Django / python, и мое ноу-хау в SQL устарело, поэтому прошу прощения, если это не проблема.
Требование: Пользователи могут делать ставки на продукты. Ставки принимаются, только если они выше предыдущих ставок на тот же продукт.
Вот урезанная версия моделей:
class Product(models.Model):
name = models.CharField(max_length=20)
class Bid(models.Model):
amount = models.DecimalField(max_digits=5, decimal_places=2)
product = models.ForeignKey(Product)
и просмотр ставок. Именно здесь возникают условия гонки (см. Комментарии):
def bid(request, product_id):
p = get_object_or_404(Product, pk=product_id)
form = BidForm(request.POST)
if form.is_valid():
amount = form.cleaned_data['amount']
# the following code is subject to race conditions
highest_bid_amount = Bid.objects.filter(product=product_id).aggregate(Max('amount')).get('amount__max')
# race condition: a bid might have been inserted just now by another thread so highest_bid_amount is already out of date
if (amount > highest_bid_amount):
bid = Bid(amount=amount, product_id=product_id)
# race condition: another user might have just bid on the same product with a higher amount so the save() below is incorrect
b.save()
return HttpResponseRedirect(reverse('views.successul_bid)'
Кандидаты на решение, которые я рассматривал до сих пор:
- Я прочитал документ Django о транзакциях, но я не знаю, как применить их к моей проблеме. Поскольку база данных не знает о требовании, согласно которому ставки должны возрастать, это не может заставить Django выдавать IntegrityError. Есть ли способ определить это ограничение во время определения модели? Или он неправильно понял transaction API?
- Хранимая процедура могла бы позаботиться о логике предложения. На данный момент это кажется мне «лучшим» выбором, но он переносит обработку условия гонки на базовую систему базы данных. Однако, если это хороший подход, это решение может быть объединено с решением 1?
- Я рассматривал возможность использования вызова select_for_update для блокировки ставок на этот продукт. Однако это, похоже, не является решением, поскольку, в моем понимании, это не повлияет на создание каких-либо новых заявок?
Список желаний:
- Если это каким-либо образом возможно, я хотел бы воздержаться от блокировки всей таблицы ставок, поскольку ставки на другие продукты в любом случае не могут быть затронуты.
- Если есть хорошее решение на уровне приложения, я бы хотел сохранить код независимым от базовой системы баз данных.
Большое спасибо за ваши мысли!
Ответ №1:
Возможно ли было бы для вас добавить highest_bid
столбец в Products
. Если моя логика не отключена, вы могли бы затем обновить самую высокую ставку where product_id = x and highest < current_bid
. Если этот запрос указывает, что строка была обновлена, тогда вы добавляете новую запись в таблицу ставок. Вероятно, это означало бы, что у вас должно быть значение по умолчанию для столбца highest_bid.
Комментарии:
1. Спасибо! Это поможет. Это не только ограничивает блокировку базы данных в одной строке, но также позволяет мне избавиться от уродливой агрегатной функции для вычисления текущего ‘highest_bid’ в моем примере кода выше.
Ответ №2:
Вы проверили Celery? Вы могли бы обрабатывать свои запросы асинхронно, ставя запросы в очередь, а затем возвращая результаты или ошибки обратно, когда они будут доступны. Это кажется вероятным путем, если вы хотите избежать блокировки.
В противном случае, похоже, что должна была произойти некоторая блокировка.
Комментарии:
1. Я не знал о Celery. Спасибо за это! Если бы мое приложение не было «игрушкой» (читай: небольшой объем транзакций, ..) Я бы наверняка рассмотрел Celery для достижения масштабируемости.