Django post_save作为芹菜任务的奇怪行为

时间:2016-03-28 15:01:18

标签: django celery

我有以下代码:

@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()

我产生以下错误:

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

但如果我先移除instance.save(),一切正常。当celery处理任务时,看起来sql指令不完整。如何解决?

感谢。

1 个答案:

答案 0 :(得分:2)

您的代码存在两个问题。

第一个是最大的,你将对象实例传递给任务 - 这在celery文档中被明确标记为错误的方法,基本上你正在做的是在某个状态中序列化你的对象并将它传递给芹菜来处理;但与此同时,这个对象可能会改变;作为一个解决方案,你应该将对象id作为参数传递,因此celery任务可以获取新的:

build.delay(instance.pk)

...

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

第二个问题本质上是微妙的,很少在雷达上显示出来。您的代码的第一部分可以在事务中调用,这意味着可能存在这样的情况,您的任务(在芹菜中)将比事务提交更快,然后您的模型将首先保存在芹菜任务中,然后通过事务 - 这里有一个问题。

如果您按照上面的建议更改了代码,则可能不会出现第二个问题,或者会显示不同的错误。

为了避免这样的问题,最好从transaction.oncommit处理程序调用celery任务(在版本1.9中引入Django)

还有一条评论,我可以看到你正在改变对象的状态:

instance.status = 'Processing'

最有可能是信息性的东西但可能用作锁定机制...... select_for_update有一个非常好的选项QuerySet方法,它允许你在事务期间锁定一个对象。当你这样做时,这对于芹菜任务特别好:

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

它将停止你的任务等待其他人完成(不要忘记把@transaction.atomic放在这个任务上)

如果您将nowait=True传递给select_for_update - 它会毫不拖延地生成异常,让您处理情况。