Медленный доступ к запросу Django.

Иногда эта строка приложения Django (размещенная с использованием Apache/mod_wsgi) требует много времени для выполнения (например, 99%, например, 6 секунд обработки запроса, как измерено New Relic), когда они передаются некоторыми мобильными клиентами

raw_body = request.body

(где request - входящий запрос)

Вопросы, которые у меня есть:

  • Что могло бы замедлить доступ к request.body так много?
  • Какова была бы правильная конфигурация Apache для ожидания перед вызовом Django, пока клиент не отправит всю полезную нагрузку? Возможно, проблема в конфигурации Apache.

Django body атрибут в HttpRequest является свойством, поэтому он действительно решает, что на самом деле делается там, и как сделать это за пределами Приложение Django, если это возможно. Я хочу, чтобы Apache дождался полного запроса перед отправкой его в приложение Django.

Ответ 1

Есть два способа исправить это в Apache.

Вы можете использовать mod_buffer, доступный в >=2.3, и изменить BufferSize на максимальный ожидаемый размер полезной нагрузки. Это должно заставить Apache хранить запрос в памяти до тех пор, пока он не завершит отправку, или не будет достигнут буфер.

Для старых версий Apache < 2.3 вы можете использовать mod_proxy в сочетании с ProxyIOBufferSize, ProxyReceiveBufferSize и loopback vhost, Это включает в себя установку вашего реального vhost на loopback-интерфейс и демонстрацию прокси-хоста, который соединяется с реальным vhost. Недостатком этого является то, что он использует вдвое больше сокетов и может сделать расчет ресурсов трудным.

Однако самым идеальным выбором было бы включить буферизацию запросов/ответов в балансировщике нагрузки L4/L7. Например, haproxy позволяет вам добавлять правила на основе req_len, а также для nginx. У большинства хороших балансировщиков коммерческой нагрузки также есть возможность буферизовать запросы перед отправкой.

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

Вам также необходимо рассмотреть загрузку файлов, поскольку они не подходят для буферизации полезной нагрузки на основе памяти. В большинстве случаев вы будете обрабатывать запросы на загрузку на своем веб-сервере, например HttpUploadModule, затем запросить nginx для загрузить прогресс, а не обрабатывать его непосредственно в WSGI. Если вы буферизируетесь на балансировщике нагрузки, вы можете исключить загрузку файлов из правил буферизации.

Вам нужно понять почему это происходит, и эта проблема существует как при отправке ответа, так и при получении запроса. Также неплохо иметь эти защитные меры не только для масштабируемости, но и для соображений безопасности.

Ответ 2

Что касается (1), Apache передает управление обработчику mod_wsgi, как только заголовки запроса доступны, а mod_wsgi затем передает управление Python. Внутренняя реализация request.body затем вызывает метод read(), который в конечном итоге вызывает реализацию в mod_wsgi, которая запрашивает тело запроса из Apache и, если он еще не был полностью получен Apache, блокируется до тех пор, пока он не будет доступен.

Что касается (2), это невозможно с mod_wsgi. По крайней мере, обработка входящих запросов на обработку кликов не обеспечивает механизм блокировки до тех пор, пока не будет доступен полный запрос. Другой плакат предложил использовать nginx в качестве прокси-сервера в ответе на этот повторяющийся вопрос.

Ответ 3

Я боюсь, что проблема может заключаться в количестве данных, которые вы переносите, и, возможно, в медленном соединении. Также обратите внимание, что пропускная способность загрузки обычно намного меньше, чем ширина загрузки.

Как уже указывалось, при использовании request.body Django будет ждать полного переноса всего тела из клиента и доступной в памяти (или на диске, в соответствии с конфигурациями и размером) на сервере.

Я бы посоветовал вам попробовать, что происходит с тем же запросом, если клиент подключен к точке доступа WiFi, которая подключена к самому серверу, и посмотрите, не улучшилось ли оно. Если это невозможно, возможно, просто запустите такой инструмент, как speedtest.net на клиенте, получите размер запроса и сделайте математику, чтобы узнать, сколько времени потребуется для теоретического (я ожидаю, что время, затраченное на более или менее 20 % Больше). Будьте осторожны, что скорость сети часто измеряется в битах в секунду, а размер файла измеряется в байтах.

В некоторых случаях, если для данных требуется большая обработка, может быть удобно read() запрос и выполнять вычисления на ходу, или, возможно, напрямую передать объект request любой функции, которая может читать из так называемого "файлового объекта" вместо строки.

В вашем конкретном случае, однако, я боюсь, что это затронет только 1% времени, которое не будет потрачено на получение тела из сети.

Edit:

Извините, теперь я заметил дополнительное описание в щедрости. Боюсь, я не могу вам помочь, но могу спросить, в чем смысл? Я бы предположил, что это сохранило бы только крошечный бит серверных ресурсов, чтобы поддерживать простоя python на время, без какого-либо заметного увеличения производительности по запросу...

Ответ 4

Глядя на источник Django, похоже, что на самом деле, когда вы вызываете request.body, тело запроса загружается в память, будучи прочитанным из потока.

https://github.com/django/django/blob/stable/1.4.x/django/http/ init.py # L390-L392

Вероятно, если запрос большой, время, затрачиваемое на загрузку, просто загружает его в память. У Django есть методы по запросу обрабатывать действие на теле как поток, который в зависимости от того, что именно потребляет контент, может позволить вам обработать запрос более эффективно.

https://docs.djangoproject.com/en/dev/ref/request-response/#django.http.HttpRequest.read

Вы можете, например, читать по одной строке за раз.