Django post_save 信号和 celery 任务之间可能的竞争条件

Posted

技术标签:

【中文标题】Django post_save 信号和 celery 任务之间可能的竞争条件【英文标题】:Possible race condition between Django post_save signal and celery task 【发布时间】:2019-04-29 10:00:54 【问题描述】:

在 django 2.0 应用程序中,我有一个名为 Document 的模型,用于上传图像并将其保存到文件系统。那部分有效。我在 celery (v 4.2.1) 任务中使用https://github.com/ageitgey/face_recognition 对图像执行一些面部识别。我将图像的 document_id 传递给 celery 任务,因此面部识别任务可以找到要处理的图像。如果我在保存图像后从 DocumentAdmin 操作手动调用 face_recognition 任务,这一切都很好。

我尝试从我的 models.py 中的 (models.signals.post_save, sender=Document) 方法调用 face_recognition 任务,但我在 face_recognition 的 celery 任务中的这一行收到错误:

document = Document.objects.get(document_id=document_id)

错误是:

[2018-11-26 16:54:28,594: ERROR/ForkPoolWorker-1] Task biometric_identification.tasks.find_faces_task[428ca39b-aefb-4174-9906-ff2146fd6f14] raised unexpected: DoesNotExist('Document matching query does not exist.',)
Traceback (most recent call last):
  File "/home/mark/.virtualenvs/memorabilia-JSON/lib/python3.6/site-packages/celery/app/trace.py", line 382, in trace_task
    R = retval = fun(*args, **kwargs)
  File "/home/mark/.virtualenvs/memorabilia-JSON/lib/python3.6/site-packages/celery/app/trace.py", line 641, in __protected_call__
    return self.run(*args, **kwargs)
  File "/home/mark/python-projects/memorabilia-JSON/biometric_identification/tasks.py", line 42, in find_faces_task
    document = Document.objects.get(document_id=document_id)
  File "/home/mark/.virtualenvs/memorabilia-JSON/lib/python3.6/site-packages/django/db/models/manager.py", line 82, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/home/mark/.virtualenvs/memorabilia-JSON/lib/python3.6/site-packages/django/db/models/query.py", line 403, in get
    self.model._meta.object_name
memorabilia.models.DoesNotExist: Document matching query does not exist.

此外,此错误并非一直发生,只是偶尔发生。其余时间,该过程有效;即保存图像并识别人脸。

我在 DocumentAdmin 类中覆盖了 save_model,但这只是将图像的一些元数据保存在另一个模型中。最后一行是对super().save_model(request, obj, form, change) 的调用,所以我假设在此之后调用 post_save 信号。

在我看来,将模型保存到数据库和 celery 任务向数据库查询新创建的 document_id 之间存在竞争条件。我以为直到模型保存后才激活 post_save 信号?

我是否必须在 celery 任务 face_recognition 中添加一些人为延迟才能解决这种可能的竞争条件,还是我错过了其他东西?

谢谢!

标记

【问题讨论】:

【参考方案1】:

检查保存Document 模型的函数。它被包裹在某处的atomic 块中,或者您将ATOMIC_REQUESTS 设置为True。所以当post_save 被调用时,事务还没有提交。所以你的模型在那一刻并没有真正保存到数据库中。

【讨论】:

我还找到了这篇关于堆栈溢出的文章,我会尝试一下。 ***.com/questions/45276828/… 这个链接不错,transaction.on_commit + lambda + apply async 是你需要的我觉得【参考方案2】:

似乎有时信号会超过您的 db-write 速度!作为有害的解决方法,您可以做的就是稍后运行 celery 任务,只需几秒钟。

这是如何完成的:

your_task.apply_async(
            [document_id],
            countdown=5 # this is the delay in seconds - you can adapt it accordingly
        )

让我知道这是否适用于您的情况!

【讨论】:

我很难相信新的三星 860 EVO sata SSD 太慢了!但是,我将它添加到代码中,现在它可以工作了:for x in range(0, 4): # try 4 times try: document = Document.objects.get(document_id=document_id) str_error = None except Exception as str_error: pass if str_error: sleep(2) # wait for 2 seconds before trying to fetch the data again else: break 抱歉,我无法让代码标签工作。我的解决方案建立在 Kostas 所说的基础上,但我在 celery 任务中添加了一个循环,以 (1) 尝试获取 document_id,如果出现异常则 sleep(2),然后重试最多 4 次。

以上是关于Django post_save 信号和 celery 任务之间可能的竞争条件的主要内容,如果未能解决你的问题,请参考以下文章

Django post_save 信号和 celery 任务之间可能的竞争条件

识别 django post_save 信号中更改的字段

Django post_save() 信号实现

Django:如何在 post_save 信号中访问原始(未修改)实例

忽略 django 的 post_save 信号中对 m2m 关系的更改

将pre_save信号更改为post_save?Django