uWSGI для ML-приложений на Python в production среде
1. O uWSGI
uWSGI — это сервер приложений, который поддерживает множество протоколов и может использоваться для запуска Python-приложений. Он широко применяется для развертывания веб-приложений, обеспечивая надежное и масштабируемое выполнение кода.
Особенности uWSGI
- Поддержка WSGI: uWSGI полностью поддерживает WSGI, стандартный интерфейс для взаимодействия между веб-серверами и Python-приложениями.
- Многопоточность и многопроцессорность: uWSGI позволяет распределять нагрузку между несколькими процессами или потоками, что улучшает производительность.
- Масштабируемость: uWSGI легко масштабируется, позволяя добавлять больше процессов или потоков в зависимости от нагрузки.
- Поддержка кэширования: uWSGI может поддерживать единый кэш для всех воркеров.
- Гибкость: Поддержка множества плагинов и возможность настройки под различные задачи. Например, есть плагин Python.
- Надежность и отказоустойчивость: uWSGI обеспечивает надежное выполнение приложений, автоматически перезапуская процессы в случае их сбоя.
- Управление ресурсами: uWSGI предоставляет инструменты для управления ресурсами, такими как лимиты на использование памяти и CPU, что помогает избежать перегрузки системы.
Из минусов отмечу сложность изучения, местами неполную документацию и бОльший порог входа, по сравнению с тем же Gunicorn.
uWSGI 2.1
Цитата из этого комментария от 2017 года:
... we decided to fix all of the bad defaults (expecially for the python plugin) in the 2.1 branch.
По состоянию на конец 2024 года версия 2.1 так и не выпущена.
2. Ресурсы и документация
- uWSGI 2.0 documentation.
- uWSGI
- Things to know (best practices and “issues”) READ IT !!!.
- The Art of Graceful Reloading.
- uWSGI Options.
- uwsgitop is a top-like command that uses the uWSGI Stats Server to monitor your uwsgi application.
- Configuring uWSGI for Production: The defaults are all wrong.
3. Конфиг, оптимизированный для ML-приложения на Python в production среде
Tip
Перед воркерами uWSGI лучше установить reverse-proxy, например, nginx, который будет балансировать нагрузку между ними.
См. Балансировка нагрузки между инстансами uWSGI с помощью nginx.
uwsgi.ini | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 |
|
4. Опции
[x-core]
strict
# Enable strict mode (placeholder cannot be used).
# Only valid uWSGI options are tolerated.
strict = true
You can easily add non-existent options to your config files (as placeholders, custom options, or app-related configuration items). This is a really handy feature, but can lead to headaches on typos. The strict mode (
--strict
) will disable this feature, and only valid uWSGI options are tolerated.
master
В контексте uWSGI параметр master
управляет использованием мастер-процесса. Когда master = true
, uWSGI запускает
мастер-процесс, который управляет воркерами (рабочими процессами). Это рекомендуется для большинства приложений, так
как позволяет более эффективно управлять воркерами, перезапускать их при необходимости, и обрабатывать сигналы.
Использование мастер-процесса особенно важно в случае, если вы планируете использовать функции, такие как
thunder-lock
или lazy-apps
, которые требуют наличия мастер-процесса для корректной работы.
die-on-term
Параметр die-on-term
в конфигурации uWSGI указывает, что процесс uWSGI должен завершиться, если получает сигнал
завершения (SIGTERM). Это полезно в сценариях, где вы хотите, чтобы uWSGI корректно завершал все свои процессы и
освобождал ресурсы при остановке сервера или при перезапуске контейнера в Kubernetes.
Когда die-on-term
установлен в true
, uWSGI будет завершать все свои рабочие процессы и завершать свою работу при
получении сигнала SIGTERM. Это обеспечивает более предсказуемое и управляемое завершение работы приложения, что
особенно важно в средах, где требуется корректное освобождение ресурсов и завершение всех операций перед остановкой.
TIll uWSGI 2.1, by default, sending the
SIGTERM
signal to uWSGI means "brutally reload the stack" while the convention is to shut an application down onSIGTERM
.
[x-socket]
socket
В чем будет разница, если в конфиге uWSGI указать socket = :<port>
или http = :<port>
?
Когда вы настраиваете uWSGI, вы можете указать, как именно сервер будет принимать входящие соединения. Разница между
использованием socket = :<port>
и http = :<port>
заключается в протоколе, который будет использоваться для обработки
запросов.
-
socket = :5000:
- В этом случае uWSGI будет слушать на сокете, используя протокол uWSGI или другой низкоуровневый протокол (например, FastCGI).
- Это более эффективно для взаимодействия с веб-серверами, такими как Nginx или Apache, которые могут выступать в роли прокси-сервера и передавать запросы приложению через этот сокет.
- Обычно используется в производственных средах, где uWSGI работает за обратным прокси-сервером.
-
http = :5000:
- Здесь uWSGI будет слушать HTTP-запросы напрямую.
- Это удобно для разработки и тестирования, так как позволяет напрямую обращаться к uWSGI через браузер или HTTP-клиент без необходимости в дополнительном веб-сервере.
- Однако это может быть менее эффективно в производственной среде по сравнению с использованием сокетов и прокси-сервера.
Выбор между этими двумя опциями зависит от вашего окружения и требований. Для разработки и тестирования
http = :<port>
может быть более удобным, тогда как для производственной среды обычно предпочтительнее использовать
socket = :<port>
в сочетании с веб-сервером, таким как Nginx (ну или Angie:-).
chmod-socket
Параметр chmod-socket
в конфигурации uWSGI используется для установки прав доступа к сокету, который создается для
взаимодействия с веб-сервером или для прослушивания входящих соединений. Значение этого параметра указывает на права
доступа в формате, аналогичном команде chmod
в Unix-системах.
chmod-socket = 666
означает, что сокет будет иметь права rw-rw-rw-
, то есть чтение и запись
разрешены для всех пользователей. Это может быть полезно, если вы хотите, чтобы несколько процессов или
пользователей могли взаимодействовать с сокетом, но также может представлять риск с точки зрения безопасности, если
сокет доступен для записи всем пользователям системы.
Рекомендуется использовать более строгие права доступа, если это возможно, например, 660
, чтобы ограничить доступ
только владельцу и группе, к которой принадлежит процесс uWSGI.
В этом случае uid
и gid
должны соответствовать пользователю и группе, под которыми работает nginx. Это
необходимо для того, чтобы nginx мог взаимодействовать с uWSGI через сокет.
vacuum
Параметр vacuum
в конфигурации uWSGI отвечает за очистку временных файлов и сокетов после завершения работы
процесса uWSGI. Когда этот параметр установлен в true
, uWSGI автоматически удаляет все временные файлы и сокеты,
которые были созданы во время работы, после завершения процесса. Это полезно для предотвращения накопления ненужных
файлов и освобождения системных ресурсов. В контексте Kubernetes, где контейнеры могут часто перезапускаться,
использование vacuum = true
помогает поддерживать чистоту в файловой системе.
[x-application]
module
Опция module
в конфигурации uWSGI указывает на модуль Python, который должен быть загружен и использован в
качестве WSGI-приложения. Это фактически точка входа для вашего веб-приложения.
Приведенная выше настройка означает, что uWSGI будет искать модуль app
и вызывать функцию get_app()
, которая должна
возвращать объект WSGI-приложения. Этот объект будет использоваться для обработки входящих HTTP-запросов.
Эта опция необходима для того, чтобы uWSGI знал, какой код запускать для обработки запросов. Важно убедиться, что путь к модулю корректен и что функция возвращает правильный объект WSGI-приложения.
need-app
Этот параметр не позволяет uWSGI стартовать, если невозможно найти модуль приложения. По умолчанию, uWSGI стартует в любом случае, так как считает, что вы можете динамически загрузить приложение позднее. Это был общеиспользуемый паттерн в 2008.
Now things have changed but there are still dozens of paying customers that use this kind of setup, and by default we do not change default unless after a loooong deprecation phase.
Если не включить, то может случиться ситуация, когда uWSGI стартовал без приложения и начнёт отдавать 4xx и 5xx.
lazy-app
При включении опции lazy-apps = true
в uWSGI каждый рабочий процесс (worker) загружает приложение заново после
форка. Это отличается от стандартного поведения, когда приложение загружается один раз в основном процессе, а затем
форкается для создания рабочих процессов.
Основные причины увеличения потребления RAM при использовании lazy-apps = true
:
- Отсутствие общей памяти: При стандартном подходе (без
lazy-apps = true
) рабочие процессы разделяют память, занятую приложением, благодаря механизму copy-on-write. Это означает, что пока данные не изменяются, они остаются общими для всех процессов. При использованииlazy-apps = true
каждый процесс загружает своё собственное независимое приложение, что увеличивает общий объём используемой памяти. - Инициализация ресурсов: Каждый процесс может заново инициализировать ресурсы, такие как соединения с базой данных, кэш и другие внешние сервисы, что также может увеличить потребление памяти.
- Библиотеки и зависимости: Если приложение использует большие библиотеки или зависимости, их копии будут загружены в память для каждого рабочего процесса, что также увеличивает общий объём используемой памяти.
Использование lazy-apps = true
может быть полезно в некоторых сценариях, например, когда необходимо, чтобы каждый
процесс имел свою собственную изолированную среду, но это приходит с компромиссом в виде увеличенного потребления
памяти.
Tip
Хорошее пояснение логики работы lazy-app
: Preforking VS lazy-apps VS lazy.
[x-python]
single-interpreter
Multiple interpreters are cool but there are reports on some C extensions that do not cooperate well with them.
Опция single-interpreter = true
в конфигурации uWSGI указывает на использование единственного интерпретатора
Python для всех приложений, работающих в одном процессе uWSGI.
По умолчанию uWSGI позволяет размещать несколько services в каждом worker процессе. uWSGI может создавать несколько интерпретаторов Python, если это необходимо, например, для изоляции различных приложений. Однако это может привести к увеличению потребления памяти и усложнению управления памятью.
Использование single-interpreter = true
может быть полезно в следующих случаях:
- Экономия памяти: Поскольку используется только один интерпретатор, это может снизить потребление памяти, особенно если у вас несколько приложений, работающих в одном процессе.
- Упрощение отладки: Наличие единственного интерпретатора может упростить процесс отладки, так как все приложения будут работать в одном и том же интерпретаторе.
- Совместимость: Некоторые расширения или библиотеки могут не поддерживать работу с несколькими интерпретаторами, и эта опция может помочь избежать проблем совместимости.
Однако стоит учитывать, что использование одного интерпретатора может привести к проблемам с изоляцией приложений, особенно если они имеют разные зависимости или конфигурации. Поэтому перед включением этой опции следует тщательно оценить потребности и архитектуру вашего проекта.
optimize
См. документацию Python: PYTHONOPTIMIZE.
[x-workers]
processes
Сколько указывать processes
?
There is no magic rule for setting the number of processes or threads to use. It is very much application and system dependent. Simple math like
processes = 2 * cpucores
will not be enough. You need to experiment with various setups and be prepared to constantly monitor your apps.uwsgitop
could be a great tool to find the best values.
Отсюда вывод: необходимо выполнять нагрузочное тестирование приложений с последующей оптимизацией конфига uWSGI.
enable-threads
By default, the Python plugin does not initialize the GIL. This means your app-generated threads will not run.
This "strange" default behaviour is for performance reasons, no shame in that.
Включение параметра enable-threads
в конфигурации uWSGI позволяет использовать многопоточность в рамках каждого
процесса. Это может быть полезно для приложений, которые выполняют операции I/O или требуют параллельной
обработки, но для CPU-bound задач, таких как ML-приложения, многопоточность может не всегда приводить к улучшению
производительности из-за GIL (Global Interpreter Lock) в Python.
Влияние данной опции нужно тестировать на конкретной реализации ML-приложения.
threads
Параметр threads
определяет количество потоков, которые будет использовать каждый процесс uWSGI. Потоки могут быть
полезны для I/O-bound задач, но для CPU-bound задач, таких как ML-приложения, они могут не давать значительного
прироста производительности и даже могут снижать её из-за накладных расходов на переключение контекста.
Влияние данной опции нужно тестировать на конкретной реализации ML-приложения.
thunder-lock
В конфигурации uWSGI параметр thunder-lock
используется для улучшения производительности при работе с несколькими
процессами и потоками. Вот как он влияет на конфиг:
- Синхронизация доступа к ресурсам:
thunder-lock
помогает синхронизировать доступ к ресурсам между процессами и потоками. Это особенно важно, когда используется параметрenable-threads = true
, так как в этом случае в каждом процессе могут быть активны несколько потоков. - Улучшение производительности: Включение
thunder-lock
может улучшить производительность приложения за счет уменьшения накладных расходов на блокировки. Это достигается путем оптимизации механизма блокировки, что особенно полезно при высокой нагрузке. - Предотвращение дедлоков: Использование
thunder-lock
может помочь избежать дедлоков, которые могут возникнуть при одновременном доступе нескольких процессов и потоков к общим ресурсам.
В приведенном ниже конфиге processes = 4
и threads = 1
, что означает, что у uWSGI будет 4 процесса, каждый из
которых может использовать один поток. Включение thunder-lock
в этом случае может помочь более эффективно управлять
ресурсами и улучшить общую производительность приложения.
[x-worker-recycling]
Периодический рестарт workers может минимизировать влияние ошибок в состоянии и утечек памяти.
max-requests
# Restart workers after this many requests.
# 60 sec * 60 min * 60 RPS
max-requests = 216000
Этот параметр задает фиксированное количество запросов, после обработки которых рабочий процесс будет перезапущен.
Например, если max-requests
установлен в 1000, то каждый рабочий процесс будет перезапущен после обработки 1000
запросов.
Данный параметр полезен для предотвращения утечек памяти, которые могут возникнуть при длительном выполнении процессов.
max-requests-delta
Этот параметр добавляет случайный элемент к значению max-requests
, чтобы избежать одновременного перезапуска всех
рабочих процессов.
Например, если max-requests установлен в 1000, а max-requests-delta в 100, то рабочие процессы будут перезапускаться после обработки от 1000 до 1100 запросов (случайное значение в этом диапазоне).
Это помогает распределить нагрузку и избежать пиковых нагрузок, которые могут возникнуть, если все рабочие процессы перезапускаются одновременно.
Warning
Опция присутствует в документации, но в uWSGI v2.0.x она не реализована.
См. этот комментарий.
max-worker-lifetime
# Restart worker after this many seconds.
# 60 sec * 60 min * 4 hrs
max-worker-lifetime = 14400
Параметр max-worker-lifetime
в uWSGI определяет максимальное время жизни воркера в секундах. Это означает, что
после указанного времени воркер будет перезапущен. Данный параметр полезен для предотвращения утечек памяти, которые
могут возникнуть при длительном выполнении процессов.
Рекомендации:
- Оптимальное значение: Установите
max-worker-lifetime
в зависимости от стабильности вашего приложения и наличия утечек памяти. Обычно это значение может быть от нескольких часов до суток. - Совместимость: Убедитесь, что перезапуск воркеров не приводит к потере данных или сбоев в обработке запросов.
Используйте параметры, такие как
lazy-apps
илиreload-on-rss
, чтобы минимизировать влияние на производительность. - Мониторинг: Следите за использованием памяти и временем отклика, чтобы определить оптимальное значение для вашего приложения.
reload-on-rss
Параметр reload-on-rss
в uWSGI используется для автоматической перезагрузки рабочих процессов, когда они достигают
определенного порога использования памяти (RSS - Resident Set Size).
Данный параметр полезен для предотвращения утечек памяти, которые могут возникнуть при длительном выполнении процессов.
Note
Если процессы перезагружаются слишком часто, это может негативно сказаться на производительности из-за времени, необходимого для инициализации новых процессов.
Убедитесь, что перезагрузка процессов не приводит к значительным задержкам или снижению доступности сервиса. Это особенно важно для приложений с высокими требованиями к доступности и низкой latency.
worker-reload-mercy
Параметр worker-reload-mercy
в uWSGI определяет время в секундах, которое дается воркеру на завершение всех
текущих запросов перед его перезапуском. Это полезно в ситуациях, когда вы хотите перезапустить воркеры без
прерывания обработки текущих запросов, например, при развертывании новой версии приложения или изменении конфигурации.
Если воркер не завершит обработку запросов в течение указанного времени, он будет принудительно завершен. Это позволяет избежать ситуации, когда воркер "зависает" и не отвечает на запросы.
Рекомендации:
- Значение должно превышать максимальное время обработки запроса в приложении. Например, если ваш p95 времени
обработки запроса составляет <100 мс, вы можете установить
worker-reload-mercy
в 1-2 секунды, чтобы учесть возможные задержки. - Значение должно быть немного больше, чем значение
harakiri
, чтобы дать воркерам достаточно времени для завершения текущих запросов перед перезапуском. Это обеспечит плавный перезапуск воркеров без прерывания обработки запросов, особенно если у вас есть длительные операции, которые могут занять больше времени, чем обычно. - Значение должно быть достаточно большим, чтобы учесть возможные задержки в обработке, но не слишком большим, чтобы не задерживать перезапуск воркеров при необходимости.
- Тестируйте изменения в конфигурации на нагрузочном тестировании, чтобы убедиться, что перезапуск воркеров не влияет на производительность и доступность вашего приложения.
[x-buffers-limits]
buffer-size
# Maximum size of request body.
# By default uWSGI allocates a very small buffer (4096 bytes) for the headers of each
# request. If you start receiving “invalid request block size” in your logs, it could
# mean you need a bigger buffer.
# Increase it (up to 65535) with the buffer-size option.
buffer-size = 8192
Параметр buffer-size
в uWSGI определяет размер буфера для чтения данных из сокета. Это важно для обработки
HTTP-запросов, так как буфер используется для хранения данных запроса до их обработки приложением. Если размер
буфера слишком мал, то запросы, превышающие этот размер, могут быть обрезаны или не обработаны корректно.
Для CPU-bound ML-приложения, получающего JSON в POST-запросах, важно учитывать размер данных, которые могут быть отправлены в одном запросе. Если есть вероятность, что JSON payload может значительно увеличиться в будущем, то стоит предусмотреть запас.
По умолчанию, размер буфера в uWSGI составляет 4096 байт (4 КБ). Это значение может быть достаточным для небольших запросов, но если вы ожидаете более крупные запросы, стоит увеличить этот параметр.
Рекомендации по установке buffer-size
:
- Оцените максимальный размер запроса, который ваше приложение может получить.
- Установите
buffer-size
с запасом, чтобы учесть возможные увеличения данных. Например, если максимальный размер запроса составляет 8 КБ, установитеbuffer-size
на 16 КБ (16384 байта). - Проведите нагрузочное тестирование, чтобы убедиться, что выбранный размер буфера оптимален и не приводит к потере данных или увеличению времени обработки.
http-buffer-size
http-buffer-size
— это параметр, который определяет размер буфера для HTTP-запросов. Этот параметр важен для
обработки входящих HTTP-запросов, особенно когда они содержат большие объемы данных, такие как JSON-пейлоады.
Рекомендации:
- Увеличение размера буфера: Если ваши POST-запросы содержат большие JSON-объекты, может быть полезно увеличить
http-buffer-size
. Например, можно установить его на 8192 или 16384 байт, чтобы избежать фрагментации данных и улучшить производительность при обработке больших запросов. - Тестирование: После изменения этого параметра рекомендуется провести нагрузочное тестирование, чтобы убедиться, что приложение обрабатывает запросы эффективно и без ошибок.
- Совместимость с
buffer-size
иpost-buffering
: Убедитесь, что значенияbuffer-size
иpost-buffering
также соответствуют увеличенномуhttp-buffer-size
, чтобы избежать узких мест в обработке данных.
Warning
При использовании параметра socket = :<port>
вместо http = :<port>
, uWSGI работает с сокетами
напрямую, а не через HTTP-протокол. В этом случае параметр http-buffer-size
не имеет смысла, так как он
используется для настройки размера буфера HTTP-запросов, когда uWSGI работает в режиме HTTP.
Если вы используете сокеты, вам следует сосредоточиться на параметрах, связанных с сокетами, таких как
buffer-size
, который определяет размер буфера для сокетов. Это будет более релевантно для такой конфигурации.
post-buffering
# The default value is 0.
# Before setting up, make sure that you really know how this option works.
post-buffering = 0
Параметр post-buffering
в uWSGI определяет, сколько байт данных из POST-запроса будет буферизовано в памяти перед
тем, как передать их в приложение. Это может быть полезно, если приложение не может обрабатывать данные по частям и
требует, чтобы весь запрос был доступен сразу.
Когда стоит использовать post-buffering
:
- Защита от медленных клиентов: Если вы ожидаете, что клиенты могут отправлять данные медленно, буферизация может помочь избежать блокировки рабочих процессов.
- Ограничение памяти: Если ваше приложение обрабатывает большие тела запросов и вы хотите ограничить
использование памяти,
post-buffering
может помочь, так как uWSGI будет использовать временные файлы на диске.
Когда post-buffering
не нужен:
- Маленькие тела запросов: Если тела запросов небольшие,
post-buffering
может быть избыточным. - Высокая производительность: Если ваше приложение уже оптимизировано и нет проблем с производительностью или
памятью,
post-buffering
может не дать значительных преимуществ.
В uWSGI параметр post-buffering
по умолчанию имеет значение 0
. Это означает, что без явного указания этого
параметра, uWSGI не будет буферизовать тело POST-запросов, и данные будут передаваться приложению по мере их
поступления.
Для CPU-bound ML-приложения на Python, которое получает JSON-пейлоады, значение post-buffering
следует выбирать с
учетом следующих факторов:
- Размер данных: Если у вас есть ограничения по памяти или если вы ожидаете, что входящий JSON может значительно увеличиться, это стоит учитывать.
- Память сервера: Убедитесь, что сервер имеет достаточно памяти для буферизации POST-запросов. Если у вас много одновременных запросов, это может привести к значительному потреблению памяти.
- Обработка данных: Если ваше приложение обрабатывает данные постепенно или если есть задержки при обработке, может быть полезно буферизовать больше данных, чтобы избежать блокировок.
Для начала, вы можете установить post-buffering
на значение, которое превышает средний размер ваших POST-запросов.
Например, если средний размер вашего JSON-пейлоада составляет около 1-2 КБ, вы можете установить post-buffering
на
4096 байт (4 КБ) или 8192 байт (8 КБ). Это должно быть достаточно для буферизации большинства запросов целиком.
Важно протестировать приложение с разными значениями post-buffering
, чтобы найти оптимальное значение, которое
обеспечит баланс между использованием памяти и производительностью. Также следите за поведением приложения под
нагрузкой, чтобы убедиться, что выбранное значение не приводит к проблемам с памятью или производительностью.
Если отключить post-buffering
, то uWSGI на старте будет выдавать такое предупреждение:
*** WARNING: you have enabled harakiri without post buffering. Slow upload could be rejected on post-unbuffered webservers ***
listen
# uWSGI will maintain a queue of this number of incoming connections that can wait
# to be processed. If the queue overflows, new connections will be rejected.
listen = 256
В контексте uWSGI параметр listen
определяет размер очереди для входящих соединений на сокете. Это количество
соединений, которые могут быть в очереди, ожидая обработки, прежде чем новые соединения начнут отклоняться сервером.
Конфигурация listen = 256
означает, что до 256 соединений могут находиться в очереди. Это значение должно быть
достаточно большим, чтобы избежать отказов в обслуживании при пиковых нагрузках, но не слишком большим, чтобы не
тратить ресурсы впустую.
Note
Прежде чем увеличивать это значение, убедитесь, что ваша система и сеть могут справиться с увеличенной нагрузкой.
[x-timeouts]
harakiri
harakiri
— это параметр в uWSGI, устанавливающий максимальное время (в секундах), в течение которого запрос
может выполняться. Если запрос не завершится в течение указанного времени, uWSGI автоматически завершит процесс,
обрабатывающий этот запрос.
Это полезно для предотвращения зависания процессов, которые могут возникнуть из-за длительных или зависших операций.
This option will
SIGKILL
workers after a specified number of seconds. Without this feature, a stuck process could stay stuck forever.
socket-timeout
socket-timeout
— это параметр, который определяет максимальное время ожидания в секундах для операций ввода-вывода
на сокете. В контексте uWSGI, это время, в течение которого сервер будет ожидать завершения операции чтения или
записи на сокете. Если операция не завершится в течение указанного времени, она будет прервана.
Рекомендации по настройке socket-timeout
:
-
Текущая настройка: Если ваше приложение обрабатывает запросы менее чем за 100 мс, то значение в 30 секунд может быть избыточным.
-
Оптимизация: Учитывая, что ваше приложение должно обрабатывать запросы с p95 < 100 мс и у вас есть SLA на p95@300 мс, вы можете уменьшить
socket-timeout
до более разумного значения, например, 1-2 секунды. Это позволит быстрее освобождать ресурсы в случае зависших соединений и улучшит общую отзывчивость системы. -
Совместимость с другими параметрами: Убедитесь, что
socket-timeout
согласован сharakiri
, чтобы избежать ситуаций, когда разные таймауты конфликтуют друг с другом.
Таким образом, вы уменьшите время ожидания для операций с сокетом, что может помочь в достижении вашей цели по снижению задержек и улучшению производительности приложения.
Рекомендации по настройке socket-timeout
:
- Анализ нагрузки: Если ваше приложение обрабатывает большие объемы данных или сложные вычисления, вам может
потребоваться увеличить значение
socket-timeout
, чтобы избежать преждевременного разрыва соединений. - Сетевые условия: Если ваше приложение работает в условиях нестабильной сети или с высокой задержкой, также
рекомендуется увеличить
socket-timeout
. - Балансировка с
harakiri
: Убедитесь, что значениеsocket-timeout
согласуется с параметромharakiri
, который завершает запросы, выполняющиеся дольше указанного времени. Обычноharakiri
должно быть больше, чемsocket-timeout
, чтобы избежать преждевременного завершения запросов. - Тестирование: Проведите нагрузочное тестирование с различными значениями
socket-timeout
, чтобы определить оптимальное значение для вашего приложения.
Увеличение socket-timeout
может помочь в ситуациях, когда приложение обрабатывает длительные запросы или работает
в условиях высокой сетевой задержки. Однако не забывайте, что слишком большое значение может привести к увеличению
времени ожидания для клиентов в случае реальных проблем с сетью или сервером.
http-timeout
Параметр http-timeout
в конфигурации uWSGI определяет максимальное время ожидания для HTTP-запросов. Это время, в
течение которого uWSGI будет ждать завершения обработки HTTP-запроса, прежде чем считать его неудачным и прервать
соединение.
Warning
В моём сетапе использование параметра http-timeout
в конфигурации uWSGI не имеет смысла, так как я
использую сокет для взаимодействия между Nginx и uWSGI, а не HTTP.
Параметр http-timeout
применяется только в случае, если uWSGI работает в режиме HTTP-сервера.
Поскольку я использую Nginx для балансировки нагрузки и проксирования запросов, именно его параметры
uwsgi_read_timeout
и uwsgi_send_timeout
будут определять таймауты для HTTP-запросов.
[x-http]
http-keepalive
Параметр http-keepalive
в конфигурации uWSGI отвечает за поддержку постоянных соединений (keep-alive) для
HTTP-запросов. Когда этот параметр включен (true
), uWSGI будет пытаться поддерживать открытое соединение с
клиентом после отправки ответа, чтобы использовать его для последующих запросов.
Это может значительно снизить накладные расходы на установку новых соединений, улучшая производительность и снижая задержки, особенно в условиях высокой нагрузки.
Поддержка keep-alive полезна, когда клиент отправляет несколько запросов подряд, так как позволяет избежать необходимости повторного установления TCP-соединения для каждого запроса. Однако стоит учитывать, что поддержка keep-alive также требует управления количеством открытых соединений и их временем жизни, чтобы избежать исчерпания ресурсов.
Note
В моём сетапе взаимодействие между nginx и uWSGI происходит через Unix-сокет, а не через HTTP. Поэтому этот параметр не будет иметь эффекта на взаимодействие между nginx и uWSGI.
Однако, если у вас есть другие компоненты, которые взаимодействуют с uWSGI через HTTP, этот параметр может быть полезен для оптимизации работы с ними.
http-auto-chunked
Параметр http-auto-chunked
в uWSGI отвечает за автоматическое использование chunked transfer encoding для
HTTP-ответов. Это полезно, когда размер ответа заранее неизвестен или когда вы хотите начать отправку данных клиенту
до того, как весь ответ будет сформирован.
Включение этого параметра позволяет uWSGI автоматически разбивать ответ на чанки, что может улучшить производительность в определённых сценариях, особенно когда ответы большие или генерируются динамически.
Включение параметра http-auto-chunked
в uWSGI имеет смысл, когда размер ответа превышает определенный порог,
который может варьироваться в зависимости от конкретной конфигурации и требований приложения. Обычно это актуально
для ответов, размер которых превышает несколько килобайт.
Если ответы могут быть достаточно большими (например, более 8-16 КБ), включение http-auto-chunked
может помочь
оптимизировать передачу данных, особенно если клиент ожидает потоковую передачу данных. Это позволяет серверу
отправлять данные по частям, не дожидаясь формирования всего ответа, что может улучшить время отклика и снизить
нагрузку на память.
Однако, если ответы обычно меньше этого порога, то включение http-auto-chunked
может не дать значительных
преимуществ. Важно протестировать и измерить производительность с включенным и выключенным параметром, чтобы
определить его влияние на конкретную рабочую нагрузку.
Note
В моём сетапе взаимодействие между nginx и uWSGI происходит через Unix-сокет, а не через HTTP. Поэтому этот параметр не будет иметь эффекта на взаимодействие между nginx и uWSGI.
Однако, если у вас есть другие компоненты, которые взаимодействуют с uWSGI через HTTP, этот параметр может быть полезен для оптимизации работы с ними.
tcp-nodelay
# Disables Nagle's algorithm
# Effect:
# 1. Reduces latency when sending small packets
# 2. Packets are sent immediately without waiting for buffering
# 3. Useful for applications that require a fast response
# 4. May increase network load due to more packets
# It is recommended to use:
# 1. For real-time applications
# 2. When minimum delay is important
# 3. When sending small packages frequently
tcp-nodelay = true
Параметр tcp-nodelay
в конфигурации uWSGI (и других сетевых приложений) управляет использованием алгоритма Нейгла
для соединений TCP. Алгоритм Нейгла предназначен для уменьшения количества мелких пакетов, отправляемых по сети,
путем буферизации данных до тех пор, пока не будет накоплено достаточно информации для отправки одного большого
пакета. Это может уменьшить нагрузку на сеть, но также может привести к задержкам в передаче данных.
Когда tcp-nodelay
установлен в true
, это отключает алгоритм Нейгла, что позволяет отправлять данные сразу же,
без задержек. Это может быть полезно для приложений, где важна минимальная задержка передачи данных, например, в
случае с ML приложением, где важно соблюдать SLA по задержкам.
Учитывая требования по задержкам (p95@250 ms, p99@300 ms) и то, что приложение обрабатывает запросы достаточно
быстро (p95 < 200 ms), использование tcp-nodelay = true
является оправданным решением для минимизации сетевых
задержек и улучшения общей производительности.
so-keepalive
# Activates TCP keepalive
# Effect:
# 1. Keeps the connection active
# 2. Periodically checks if the connection is active
# 3. Helps to detect "dead" connections
# 4. Clears inactive connections
# It is recommended to use:
# 1. For long connections
# 2. If necessary, the definition of broken connections
# 3. To clean up "hung up" connections
so-keepalive = true
Параметр so-keepalive
в конфигурации uWSGI отвечает за включение механизма TCP Keep-Alive на уровне сокетов. Это
системная опция, которая позволяет поддерживать соединение активным, даже если в течение длительного времени не
происходит обмен данными.
Когда so-keepalive
включен, операционная система периодически отправляет контрольные пакеты (keep-alive packets)
для проверки, что соединение всё ещё активно. Это помогает обнаруживать "мертвые" соединения и освобождать ресурсы,
если клиент или сервер больше не доступны.
Использование so-keepalive
может быть полезным для поддержания стабильных соединений между компонентами системы,
особенно если они взаимодействуют через сеть с потенциально переменным качеством соединения. Однако, стоит учитывать,
что избыточное использование keep-alive пакетов может привести к дополнительной нагрузке на сеть, поэтому их
использование должно быть обоснованным.
[x-observability]
disable-logging
В конфигурации uWSGI параметр disable-logging = true
отключает ведение журнала (логирование) для всех
HTTP-запросов, обрабатываемых приложением. Это может быть полезно для уменьшения нагрузки на систему, особенно если
приложение обрабатывает большое количество запросов и логирование каждого из них не требуется. Однако, это также
означает, что вы не будете иметь доступ к журналам запросов для диагностики и анализа, что может затруднить отладку
и мониторинг работы приложения.
Если вам необходимо логирование для целей мониторинга или отладки, рекомендуется установить disable-logging =
false
или использовать более избирательные методы логирования, чтобы снизить нагрузку, но при этом иметь доступ к
необходимой информации.
По умолчанию у uWSGI достаточно подробное логирование. Если выключать, то это подразумевает, что логирует само приложение — вы сами должны об этом позаботиться.
Рекомендуется включать логирование для ошибок в случае, если приложение теряет их. Тут важно быть уверенным, что не залогируется чувствительная информация.
log-4xx
Параметр log-4xx
в конфигурации uWSGI отвечает за логирование HTTP-ответов с кодами состояния 4xx. Эти коды
состояния указывают на ошибки клиента, такие как "404 Not Found" или "403 Forbidden". Когда log-4xx
установлен в
true
, uWSGI будет записывать в журнал все запросы, которые возвращают коды состояния 4xx. Это может быть полезно
для отладки и мониторинга, чтобы выявлять и анализировать проблемы, связанные с клиентскими ошибками.
log-5xx
Параметр log-5xx
в конфигурации uWSGI отвечает за логирование всех HTTP-ответов с кодами состояния 5xx, которые
указывают на ошибки сервера. Когда этот параметр установлен в true
, uWSGI будет записывать в журнал все случаи,
когда сервер возвращает клиенту ошибку 5xx. Это полезно для мониторинга и отладки, так как позволяет быстро выявлять
и анализировать проблемы, связанные с внутренними ошибками сервера.
Пример log-записи
Лог, который предоставляет детальную информацию о ресурсоемкости и производительности обработки конкретного HTTP-запроса в uwsgi:
{address space usage: 13296496640 bytes/12680MB} {rss usage: 12713156608 bytes/12124MB} [pid: 23|app: 0|req:
570/2259] 172.20.0.8 () {40 vars in 535 bytes} [XXX XXX XX XX:XX:XX XXXX] POST /api/v1/endpoint => generated 38
bytes in 1 msecs (HTTP/1.1 200) 4 headers in 119 bytes (1 switches on core 0)
Разбор лога на молекулы:
- {address space usage: 13296496640 bytes/12680MB}: Это показывает объем адресного пространства, используемого процессом. Адресное пространство включает в себя все выделенные процессу виртуальные адреса, которые могут включать как фактически используемую память, так и память, которая зарезервирована, но не используется.
- {rss usage: 12713156608 bytes/12124MB}: RSS (Resident Set Size) — это объем физической памяти, используемой процессом. Это фактически используемая память, загруженная в оперативную память (RAM).
- [pid: 23|app: 0|req: 570/2259]:
- pid: 23: Идентификатор процесса, который обрабатывает запрос.
- app: 0: Индекс приложения, которое обрабатывает запрос. В uwsgi можно запускать несколько приложений, и они индексируются начиная с 0.
- req: 570/2259: Количество запросов, обработанных этим процессом (570) и общее количество запросов, обработанных всеми процессами (2259).
- 172.20.0.8: IP-адрес клиента, который отправил запрос.
- {40 vars in 535 bytes}: Количество переменных окружения (40) и общий объем данных этих переменных (535 байт), переданных с запросом.
- [XXX XXX XX XX:XX:XX XXXX]: Время и дата, когда запрос был обработан.
- POST /api/v1/endpoint: Метод HTTP (POST) и URL, к которому был сделан запрос.
- => generated 38 bytes in 1 msecs: Сервер сгенерировал ответ размером 38 байт за 1 миллисекунду.
- (HTTP/1.1 200): Версия протокола HTTP и код состояния ответа (200 — успешный запрос).
- 4 headers in 119 bytes: Количество заголовков в ответе (4) и общий объем данных этих заголовков (119 байт).
- (1 switches on core 0): Количество переключений контекста (1) на ядре процессора (core 0), которые произошли во время обработки запроса.
Note
В логах uWSGI сообщение вида (1 switches on core 0)
относится к информации о работе планировщика зелёных
потоков (green threads) или корутин. Это сообщение указывает на количество переключений контекста, которые
произошли на определённом ядре процессора:
- 1 switches: Это количество переключений контекста, которые произошли. Переключение контекста — это процесс, при котором выполнение программы переключается с одного потока на другой. В контексте uWSGI это может быть связано с использованием зелёных потоков или корутин, таких как те, что предоставляет библиотека gevent.
- on core 0: Это указывает на ядро процессора, на котором происходят эти переключения. В данном случае это ядро 0.
stats
В uWSGI параметр stats
используется для включения сбора статистики и метрик о работе сервера uWSGI через
Unix-сокет или TCP-сокет. Этот параметр позволяет указать адрес и порт, на котором uWSGI будет предоставлять
информацию о текущем состоянии своих процессов, таких как количество активных воркеров, использование памяти,
количество обработанных запросов и другие метрики.
В данном случае, я использую TCP-сокет на порту 1717. Я могу подключиться к этому порту с помощью инструментов,
таких как nc
(netcat), чтобы получить статистику в формате JSON.
Warning
Если ервер доступен из интернета, убедитесь, что доступ к статистике ограничен только доверенными IP-адресами или внутренними сетями, чтобы предотвратить несанкционированный доступ к информации о приложении.
stats-http
Параметр stats-http
в конфигурации uWSGI позволяет включить HTTP-интерфейс для получения статистики о работе uWSGI.
Когда этот параметр установлен в true
, uWSGI открывает HTTP-эндпоинт, через который можно получить информацию о
текущем состоянии серверов, таких как количество активных воркеров, количество обработанных запросов, использование
памяти и другие метрики.
Этот интерфейс полезен для мониторинга и диагностики работы приложения в реальном времени. Он предоставляет удобный способ интеграции с системами мониторинга и сбора метрик, такими как Prometheus или Grafana.
Рекомендуется использовать этот параметр в сочетании с параметром stats
, который указывает, на каком порту и
адресе будет доступен HTTP-интерфейс для статистики. Если stats = :1717
, то что означает, что статистика будет
доступна по адресу http://<адрес_сервера>:1717
.
Warning
Убедитесь, что доступ к статистике ограничен и защищен, так как она может содержать чувствительную информацию о вашем приложении и сервере.
enable-metrics
enable-metrics
используется для включения системы метрик в uWSGI. Это позволяет собирать и экспортировать метрики
в формате, совместимом с системами мониторинга, такими как Prometheus.
stats
, в отличие от enable-metrics
, используется для предоставления информации о состоянии uWSGI через HTTP или
UNIX сокет. Это позволяет получать статистику о работе uWSGI в реальном времени.
memory-report
Параметр memory-report
в конфигурации uWSGI используется для включения периодической отчетности о потреблении
памяти каждым воркером. Когда этот параметр установлен в true
, uWSGI будет выводить информацию о том, сколько
памяти использует каждый процесс воркера.
Информация о потреблении памяти выводится в стандартный журнал uWSGI. Обычно это тот же журнал, куда выводятся
другие логи uWSGI, такие, как ошибки и информация о запросах. Путь к этому журналу может быть задан в конфигурации
uWSGI с помощью параметра logto
или logto2
. Если эти параметры не указаны, то по умолчанию информация выводится
в стандартный вывод (stdout) или стандартный вывод ошибок (stderr), в зависимости от того, как настроена система и
как запущен uWSGI.
Если вы хотите перенаправить логи в определенный файл, вы можете добавить в конфигурацию следующее:
К тому же, когда параметр memory-report = true
включен в конфигурации uWSGI, информация о потреблении памяти будет
выводиться в отчет, доступный через stats-http
.
Как мониторить утилизацию ресурсов и нагрузку на воркеры?
Для мониторинга потребления ресурсов использовать uwsgitop. В конфиге uWSGI указать:
# Enable uWSGI Stats Server to monitor your uwsgi application.
stats = :1717
stats-http = true
# Always check the memory usage of your apps.
memory-report = true
В контейнере запускаем:
auto-procname
По умолчанию, имена процессов workers — это команды, которые были использованы для их старта.
Если включить данный флаг, uWSGI будет именовать процессы примерно так: master
, worker 1
, woker 2
, и т.д.
Чтобы исключить коллизии именования, используйте procname-prefix
.
Для обогащения информации о процессах можно использовать uWSGI API: uwsgi.setprocname()
.
procname-prefix
Параметр procname-prefix
в конфигурации uWSGI используется для добавления префикса к имени процесса. Это может
быть полезно для идентификации процессов uWSGI в системных утилитах, таких как top
или ps
, особенно когда на
одном сервере работает несколько приложений или экземпляров uWSGI. В данном случае, префикс "your-app-name "
будет
добавлен к имени каждого процесса uWSGI, что упростит мониторинг и управление процессами, связанными с вашим
приложением.
5. Additional uWSGI features
Dynamic Worker Scaling
Указанный ниже пример настроек динамического масштабирования по моему мнению не подходит для ML-приложений, так как они обычно потребляют много CPU ресурса. Их нужно тщательно профилировать через нагрузочное тестирование и заранее планировать ресурсы под ожидаемые RPS. Оставлено для информации, что такая возможность есть.
cheaper-algo = busyness
# Maximum number of workers allowed.
processes = 500
# Minimun number of workers allowed.
cheaper = 8
# Workers created at startup.
cheaper-initial = 16
# Length of a cycle in seconds.
cheaper-overload = 1
# How many workers to spawn at a time.
cheaper-step = 16
# How many cycles to wait before killing.
cheaper-busyness-multiplier = 30
# Below this threshold, kill workers.
cheaper-busyness-min = 20
# Above this threshold, spawn new wokers.
cheaper-busyness-max = 70
# Spawn emergency workers if > queue size.
cheaper-busyness-backlog-alert = 16
# How many emergency workers to spawn.
cheaper-busyness-backlog-step = 2
Allowing Workers to Receive Signals
По умолчанию, workers, неспособны получать сигналы OS. Этот флаг позволяет им получать такие сигналы, напримерsignal.alarm
.
Это необходимо, если вы намерены использовать модуль signal в worker процессах.
py-call-osafterfork
— это параметр конфигурации uWSGI, который управляет вызовом функции os.after_fork()
после
того, как процесс uWSGI форкнут. Это полезно в контексте Python-приложений, которые используют библиотеки, требующие
выполнения определенных действий после форкинга процесса (например, повторное открытие файловых дескрипторов или сокетов).
Рекомендации:
- Когда использовать: Если ваше приложение или используемые библиотеки требуют выполнения специфических операций
после форкинга, вы можете установить этот параметр в
true
. - Совместимость: Убедитесь, что используемые библиотеки поддерживают вызов
os.after_fork()
. Если нет необходимости в таких действиях, лучше оставить этот параметр по умолчанию (обычноfalse
), чтобы избежать ненужных вызовов.
Cron/Timer
The uWSGI master process can manage periodic execution of arbitrary code.
import uwsgi
def periodic_task(signal: int):
print(f"Received signal {signal}")
# execute every 20 minutes on the first available worker
uwsgi.add_timer(99, 1200)
# execute on the 20th minute of every hour, every day
# minute, hour, day ,month, weekday
uwsgi.add_cron(99, 20, -1, -1 , -1 ,-1)
Timers are also available as decorators from uwsgidecorators
module.
import uwsgidecorators
@uwsgidecorators.timer(1200)
def periodic_task(signal: int):
print(f"Received signal {signal}")
do_some_work()
Locks
The uwsgi
module provides locks to synchronize critical sections between workers.
The Cache Subsystem
uWSGI provides an in-memory key-value cache to share between your workers.
Keys or values that are too big for the cache will silently fail to insert.
import uwsgi
uwsgi.cache_set(key, value, timeout)
uwsgi.cache_get(key)
# atomic increments!
uwsgi.cache_inc(key, amount)
uwsgi.cache_dec(key, amount)
Mules
uWSGI mules are extra processes designated to handle tasks asynchronously from the workers serving requests.
You can start one with --mule
and send tasks to it in your Python code.