2016-03-28 4 views
1

я следующий код:Джанго post_save, как сельдерей задачи странное поведение

@receiver(post_save, sender=SomeModel, dispatch_uid="build") 
def handle_creation(sender, instance, created, **kwargs): 
    if created == True: 
     build.delay(instance) 

@task() 
def build(instance): 
    instance.status = 'Processing' 
    instance.save() 

    #some heavy instructions here 
    #. . . . 
    #. . . . 

    instance.status = 'Finished' 
    instance.save() 

I производит следующие ошибки:

IntegrityError: duplicate key value violates unique constraint DETAIL: Key (id)=(13) already exists. 

Но если удалить первый instance.save() все работает отлично. Похоже, что инструкции sql не полны, когда сельдерей обрабатывает задачу. Как это исправить?

Спасибо.

+1

Вы можете передать force_update = True в качестве параметров для метода сохранения. –

+0

О, есть 'поднять DatabaseError (" Принудительное обновление не повлияло ни на какие строки. ")' After force_update – foo

ответ

2

В коде есть две проблемы.

Первый - самый большой, вы передаете экземпляр объекта задачам - это явно помечено как неправильный подход в документации на сельдерей, в основном то, что вы делаете, это сериализация вашего объекта в определенном состоянии и передача его сельдерею для работы ; но тем временем этот объект может измениться; как решение, которое вы должны передать идентификатор объекта в качестве параметра, поэтому сельдерей задача может принести его свежим:

build.delay(instance.pk) 

... 

@task 
def build(my_key): 
    instance = SomeModel.objects.get(pk=my_key) 
    instance.status = 'Processing' 
    instance.save() 

Второй проблемой является тонкий характер и редко показывает на радаре. Первая часть кода может быть вызвана в транзакции, это означает, что может возникнуть ситуация, когда ваша задача (в сельдерее) будет быстрее транзакции, а затем ваша модель будет сохранена сначала в задаче сельдерея, а затем по транзакции - и здесь у вас есть проблема.

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

Чтобы избежать такой проблемы, это хорошо, чтобы назвать задачи сельдерей из transaction.oncommit обработчика (введен в Django в версии 1.9)

еще один комментарий, что я могу видеть, вы меняете состояние объекта:

instance.status = 'Processing' 

скорее всего как что-то информационное, но, возможно, используется как механизм блокировки ... есть очень хороший вариант select_for_update метод QuerySet, который позволит вам заблокировать объект на время транзакции. Это особенно хорошо для задач сельдерея, когда вы делаете:

instance = SomeModel.objects.select_for_update().get(pk=my_key) 

это остановит вашу задачу ждет другой, чтобы закончить (не забудьте поставить @transaction.atomic над этой задачей)

если вы пройдете nowait=True к select_for_update - он будет генерировать исключение без каких-либо задержек, что позволит вам справиться с ситуацией.