PyMongo find query возвращает пустой/частичный курсор при работе в проекте Django + uWsgi

Мы разработали API REST с помощью Django и mongoDB (PyMongo driver). Проблема заключается в том, что при некоторых запросах конечным точкам API курсор PyMongo возвращает частичный ответ, который содержит меньше документов, чем должен (но его полностью действительный документ 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 s до 6 и перезапустили сервер. Все результаты были хорошими, но после применения еще одной большой нагрузки на сервер проблема снова началась. На этом этапе мы перезапустили сервис uwsgi, и снова все было в порядке. В этом последнем эксперименте мы теперь поняли, что при запуске службы uwsgi все работает правильно, но после определенного периода времени и большой нагрузки сервер снова начинает возвращать частичные или пустые результаты. Последнее исследование, которое мы провели, заключалось в том, чтобы запустить API с помощью python manage.py с DEBUG=False, и у нас была проблема снова после определенного периода времени в этой ситуации.

Мы не можем понять, в чем проблема и как ее решить. Одна из причин, по которой мы можем думать, заключается в том, что Django закрывает соединения pymongos до завершения. Поскольку возвращаемый результат является допустимым JSON.

Наш стек:

  • nginx (без кэширования)
  • uWsgi
  • MemCached (отключено во время процедуры отладки)
  • Django (v1.8 на python 3)
  • PyMongo (v3.0.3)

Ваша помощь действительно оценена.

Обновление:

Монго версия:

db version v3.0.7
git version: 6ce7cbe8c6b899552dadd907604559806aa2e9bd
  • Мы запускаем одиночный экземпляр mongod. Нет осколков/репликации.
  • Мы создаем соединение, используя этот фрагмент:

    con = MongoClient ('localhost', 27017)

Обновление 2

Тематическая тема в Pymongo tracker.

Ответ 1

Курсоры Пимонго не являются потокобезопасными элементами. Поэтому использование их, как то, что я делал в многопоточной среде, приведет к тому, что я описал. С другой стороны, операции 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})

Ответ 2

Мое очень умозрительное предположение заключается в том, что вы повторно используете курсор где-то в своем коде. Убедитесь, что вы инициализируете свою коллекцию в самом стеке представления, а не за ее пределами.

Например, как написано, если вы делаете что-то вроде:

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})

РЕДАКТИРОВАТЬ по запросу акитатора для уточнения:

Причина, по которой вам нужно определить коллекцию в стеке представления, а не когда загружается файл, состоит в том, что переменная коллекции имеет курсор, в основном, как база данных и ваше приложение разговаривают друг с другом. Курсоры делают такие вещи, как отслеживать, где вы находитесь в длинном списке данных, в дополнение к кучу других вещей, но это важная часть.

Когда вы создаете курсор коллекции вне метода просмотра, он будет повторно использовать курсор для каждого запроса, если он существует. Таким образом, если вы сделаете один запрос, а затем еще один действительно, действительно быстрый сразу после этого (например, то, что произошло при применении высокой нагрузки), курсор может быть только на полпути через разговор с базой данных, и поэтому некоторые ваши данные поступают первый запрос, а второй - второй. Причина, по которой вы получите НИКАКИЕ данные в запросе, будет заключаться в том, что курсор завершил выборку данных, но еще не был закрыт, поэтому следующий запрос попытался извлечь данные из курсора, и в запросе не осталось ничего.

Переместив определение коллекции (и ассоциацию, определение курсора) в стек представления, вы ВСЕГДА получите новый курсор при обработке нового запроса. Вы не будете перекрестно разговаривать между вашими курсорами и разными запросами, так как каждый цикл запроса будет иметь свои собственные.