我正在开发的assets
django 应用程序在 SQLite 上运行良好,但我面临着删除/更新大量记录的性能问题,所以我正在过渡到 PostgreSQL 数据库。
为此,我通过更新theapp/settings.py
来配置PostgreSQL,从新的数据库开始并删除assets/migrations/
目录。然后我正在运行:
./manage.py makemigrations assets
./manage.py migrate --run-syncdb
./manage.py createsuperuser
我有一个在注册post_create
信号中调用的函数。它在创建Scan
对象时运行扫描。在类assets.models.Scan
中:
@classmethod
def post_create(cls, sender, instance, created, *args, **kwargs):
if not created:
return
from celery.result import AsyncResult
# get the domains for the project, from scan
print("debug: task = tasks.populate_endpoints.delay({})".format(instance.pk))
task = tasks.populate_endpoints.delay(instance.pk)
违规代码:
from celery import shared_task
....
import datetime
@shared_task
def populate_endpoints(scan_pk):
from .models import Scan, Project,
from anotherapp.plugins.sensual import subdomains
scan = Scan.objects.get(pk=scan_pk) #<<<<<<<< django no like
new_entries_count = 0
project = Project.objects.get(id=scan.project.id)
....
由此产生的异常DoesNotExist
引发:
debug: task = tasks.populate_endpoints.delay(2)
[2017-09-14 23:18:34,950: ERROR/ForkPoolWorker-8] Task assets.tasks.populate_endpoints[4555d329-2873-4184-be60-55e44c46a858] raised unexpected: DoesNotExist('Scan matching query does not exist.',)
Traceback (most recent call last):
File "/usr/local/lib/python3.6/site-packages/celery/app/trace.py", line 374, in trace_task
R = retval = fun(*args, **kwargs)
File "/usr/local/lib/python3.6/site-packages/celery/app/trace.py", line 629, in __protected_call__
return self.run(*args, **kwargs)
File "/usr/src/app/theapp/assets/tasks.py", line 12, in populate_endpoints
scan = Scan.objects.get(pk=scan_pk)
但是,通过./manage.py shell
进行交互表示存在Scan
pk == 2 的对象:
>>> from assets.models import Scan
>>> Scan.objects.all()
<QuerySet [<Scan: ACME Web Test Scan>]>
>>> s = Scan.objects.all().first()
>>> s.pk
2
我唯一的猜测是,在调用post_create
函数时,尽管save()
被调用,但 PostgreSQL 数据库中仍然不存在Scan
对象。
SQLite 不会出现此问题。
另外,我还没有在stackoverflow上发现相关的相关问题,因为DoesNotExist
异常看起来相当通用,并且是由许多事情引起的。 对此的任何想法将不胜感激。
这是由事务和隔离级别引起的众所周知的问题 - 有时在执行任务时事务尚未提交,如果您的隔离级别是 READ 提交,那么您确实无法从另一个进程读取此记录。Django 1.9引入了on_commit
钩子作为解决方案。
注意:从技术上讲,这个问题是芹菜任务中缺少的 Django 相关对象的副本(竞争条件?),但接受的答案使用了django-transaction-hooks
,此后它已被合并到 django 中。