PyMongo 在 Django+uWsgi 项目中找到 returns empty/partial cursor when 运行

PyMongo find query returns empty/partial cursor when running in a Django+uWsgi project

我们使用 Django & mongoDBPyMongo 驱动程序)开发了 REST API。问题是,在对 API 端点的某些请求中,PyMongo 游标 return 是一个部分响应,其中包含的文档比应有的少(但它是一个完全有效的 JSON 文档).

让我用我们的观点之一的例子来解释它:

def get_data(key):
    return collection.find({'key': key}, limit=24)

def my_view(request):
    key = request.POST.get('key')
    query = get_data(key)
    res = [app for app in query]
    return JsonResponse({'list': res})

我们确定有 8000 多个文档与查询匹配,但是在 有些电话我们得到的结果少于 24 个(甚至为零)。我们遇到的第一个问题 经过调查,我们的代码中有多个 MongoClient 定义。通过解决这个问题,问题出现的次数减少了,但我们仍然在很多电话中遇到它。

经过所有这些调查,我们设计了一个测试,我们同时向服务器发出了 16 个异步请求。通过这种方法,我们可以重现该问题。在这 16 个请求中的每一个中,都有 6-8 个得到了部分结果。在 运行 进行此测试后,我们将 uWsgi 的进程数减少到 6 并重新启动了服务器。所有结果都很好,但在服务器上施加另一个重负载后,问题又出现了。此时,我们重新启动 uwsgi 服务,再次一切正常。通过最后一个实验,我们现在有了一个线索,当 uwsgi 服务启动 运行ning 时,一切正常,但在一段时间和重负载之后,服务器又开始 return 部分或空结果. 我们最近的调查是运行 把API using python manage.pyDEBUG=False, 这种情况过了一段时间又出问题了

我们无法弄清楚问题是什么以及如何解决。我们可以想到的一个原因是 Django 在完成之前关闭了 pymongo 的连接。因为 returned 结果是有效的 JSON.

我们的堆栈是:

非常感谢您的帮助。

更新:

Mongo版本:

db version v3.0.7
git version: 6ce7cbe8c6b899552dadd907604559806aa2e9bd

更新 2

Subject thread in Pymongo issue tracker

我非常推测性的猜测是您在代码中的某处重复使用了游标。确保您在视图堆栈本身内而不是在视图堆栈之外初始化您的集合。

例如,如所写,如果您正在执行以下操作:

import ...
import con

collection = con.documents
# blah blah code
def my_view(request):
    key = request.POST.get('key')
    query = collection.find({'key': key}, limit=24)
    res = [app for app in query]
    return JsonResponse({'list': res})

您可以结束我们对游标的重复使用。最好做类似

的事情
import ...
import con

# blah blah code
def my_view(request):
    collection = con.documents
    key = request.POST.get('key')
    query = collection.find({'key': key}, limit=24)
    res = [app for app in query]
    return JsonResponse({'list': res})

根据提问者的澄清要求进行编辑:

您需要在视图堆栈中而不是在文件加载时定义集合的原因是集合变量有一个游标,这基本上是数据库和您的应用程序相互通信的方式。游标可以做一些事情,比如跟踪你在一长串数据中的位置,以及一堆其他的东西,但那是重要的部分。

当您在视图方法之外创建集合游标时,它将在每个请求上重新使用游标(如果存在)。因此,如果您发出一个请求,然后紧随其后非常非常快地发出另一个请求(就像您应用高负载时发生的情况),游标可能只在与数据库对话的一半时间,因此您的一些数据会转到第一个请求,有些是第二个。您在请求中没有获得数据的原因是游标完成获取数据但尚未关闭,因此下一个请求尝试从游标中获取数据,并且还有 none 需要获取查询。

通过将集合定义(以及通过关联,游标定义)移动到视图堆栈中,您在处理新请求时总是会得到一个新游标。你不会在你的光标和不同的请求之间得到任何串扰,因为每个请求周期都有它自己的。

Pymongo 游标不是线程安全元素。因此,像我在多线程环境中所做的那样使用它们会导致我所描述的问题。另一方面,Python 的 list 操作大多是线程安全的,像这样更改代码片段将解决问题:

def get_data(key):
    return list(collection.find({'key': key}, limit=24))

def my_view(request):
    key = request.POST.get('key')
    query = get_data(key)
    res = [app for app in query]
    return JsonResponse({'list': res})