芹菜任务模型实例数据被Web worker踩踏?

时间:2012-08-21 03:46:54

标签: django celery django-celery

我有一个在一个视图上调用的任务。基本上,任务负责获取一些pdf数据,并通过django存储将其保存到s3中。

以下是开启它的观点:

@login_required
@minimum_stage(STAGE_SIGN_PAGE)
def page_complete(request):
    if not request.GET['documentKey']:
        logger.error('Document Key was missing', exc_info=True, extra={
            'request': request,
        })
    user = request.user
    speaker = user.get_profile()
    speaker.readyForStage(STAGE_SIGN)
    speaker.save()
    retrieveSpeakerDocument.delay(user.id, documentKey=request.GET['documentKey'], documentType=DOCUMENT_PAGE)
    return render_to_response('speaker_registration/redirect.html', {
        'url': request.build_absolute_uri(reverse('registration_sign_profile'))
    }, context_instance=RequestContext(request))

这是任务:

@task()
def retrieveSpeakerDocument(userID, documentKey, documentType):
    print 'starting task'
    try:
        user = User.objects.get(pk=userID)
    except User.DoesNotExist:
        logger.error('Error selecting  user while grabbing document', exc_info=True)
        return
    echosign = EchoSign(user=user)
    fileData = echosign.getDocumentWithKey(documentKey)
    if not fileData:
        logger.error('Error retrieving document', exc_info=True)
    else:
        speaker = user.get_profile()
        print speaker
        filename = "%s.%s.%s.pdf" % (user.first_name, user.last_name, documentType)
        if documentType == DOCUMENT_PAGE:
            afile = speaker.page_file
        elif documentType == DOCUMENT_PROFILE:
            afile = speaker.profile_file

        content = ContentFile(fileData)
        afile.save(filename, content)
        print "saving user in task"
        speaker.save()

与此同时,我的下一个视图点击(实际上是一个ajax调用,但这无关紧要)。基本上它是为下一个嵌入式文档提取代码。一旦获得它,它会更新扬声器对象并保存它:

@login_required
@minimum_stage(STAGE_SIGN)
def get_profile_document(request):
    user = request.user
    e = EchoSign(request=request, user=user)
    e.createProfile()
    speaker = user.get_profile()
    speaker.profile_js = e.javascript
    speaker.profile_echosign_key = e.documentKey
    speaker.save()
    return HttpResponse(True)      

我的任务正常运行,并正确更新speaker.page_file属性。 (我可以在管理员中暂时看到这个,并且还会在postgres日志中看到它。)

然而它很快就会被盖章,我相信它更新后会在get_profile_document视图中调用并保存profile_js属性。 实际上我知道这是基于SQL语句发生的地方。在更新profile_js之前,它已经消失了。

现在我真的不明白为什么。在每次更新和保存之前,扬声器都是正确的,并且这里没有真正的缓存,除非get_profile()做了一些奇怪的事情。发生了什么,我怎么能避免这种情况? (另外,在fileField上运行speaker之后,我是否需要在save上调用save?看来postgres日志中存在重复调用。

更新

很确定这是由于Django的默认视图事务处理。视图开始一个事务,需要很长时间才能完成,然后提交,覆盖我已经在芹菜任务中更新过的对象。

我不确定如何解决它。如果我将方法切换到手动事务,然后在我获取echosign js(需要5-10秒)后立即提交,它是否会启动新事务?似乎没有用。

也许不是

我没有添加TransactionMiddleware。所以除非它发生了,否则这不是问题。

1 个答案:

答案 0 :(得分:1)

解决。

所以这就是问题所在。

Django显然保留了一些它认为在任何地方都没有改变的对象缓存。 (如果我错了,请纠正我。)因为芹菜在django之外的数据库中更新了我的对象,所以当我说user.get_profile()时,它不知道这个对象已经改变并且给我提供了缓存版本。

强制它从数据库中获取的解决方案只是用自己的id重新调整它。它有点傻,但它确实有效。

speaker = user.get_profile()
speaker = Speaker.objects.get(pk=speaker.id)

显然django作者不想在对象上添加任何类型的refresh()方法,所以这是下一个最好的事情。

使用交易也可以解决我的问题,但是另一天。

更新

进一步挖掘之后,因为用户模型上有一个_profile_cache属性,因此每次从同一对象的一个​​请求中获取配置文件时它都不会重新获取。因为我在同一个对象的echosign函数中使用了get_profile(),所以它被缓存了。