Тупик с одновременными работниками сельдерея в Джанго

#django #postgresql #django-models #django-rest-framework #deadlock

Вопрос:

У меня есть десятки одновременных работников Сельдерея, которые создают, обновляют и удаляют данные из различных моделей в моем приложении Django. Я сталкиваюсь со следующей ошибкой:

 deadlock detected
DETAIL:  Process 3285070 waits for ShareLock on transaction 559341801; blocked by process 3285058.
Process 3285058 waits for ShareLock on transaction 559341803; blocked by process 3285070.
HINT:  See server log for query details.
 

И это те блоки кода, в которых он выдает ошибку:

 with transaction.atomic():
    for score in list_of_scores_to_update:
        score.save()
 
 time_delta = timezone.now() - timezone.timedelta(minutes=2)
with transaction.atomic():
    Score.objects.select_for_update(of=('self'), skip_locked=True).filter(checked_date__lte=time_delta).delete()
 

Как я могу предотвратить эти взаимоблокировки, получив блокировку записи для всех строк до их обновления?

Ответ №1:

Лучший способ предотвратить взаимоблокировки — это

  • держите ваши транзакции короткими
  • сохраняйте небольшие транзакции (измените как можно меньше строк).

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

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

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

Комментарии:

1. есть ли способ перезапустить задачу Сельдерея без вмешательства человека, если произошел тупик.

2. Это превосходит мои знания; я не знаю Сельдерея.

Ответ №2:

Итак, в одном месте вы обновляете Score , а в другом месте они удаляются?

Я думаю, что виновник в том, где вы повторяете list_of_scores_to_update . Вместо этого вам нужно предварительно выбрать пакеты/разбить набор запросов на страницы, а затем открыть транзакцию для обновления этого пакета, а не всего набора запросов. Сначала выберите соответствующие pks без блокировки, а затем обновите их подмножество в транзакции. Видеть iterator .

Что касается операции удаления, если ваш вариант использования позволяет, вы можете добавить pk в другую очередь. Затем другая задача может предварительно выбрать несколько идентификаторов из этой очереди и попытаться удалить. В случае неудачи повторите попытку. Это было бы очень простым решением для повторной попытки без вмешательства человека.

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