uWSGI долго был стандартом для Django и Flask за nginx. В 2026 году проект находится в режиме поддержки: критические исправления выходят, но новых возможностей почти нет, а новые проекты чаще собирают на Gunicorn или Uvicorn. При этом uWSGI остаётся быстрым и предсказуемым сервером приложений. Проблемы начинаются, когда конфигурацию копируют из старого руководства и не измеряют, что именно ограничивает производительность. Разберём конфиг, процессы, память, связку с nginx и диагностику.
Как uWSGI расходует ресурсы
Мастер-процесс читает конфиг, поднимает воркеры и следит за ними. Каждый воркер — отдельный процесс Python со своим интерпретатором и приложением. Потоки внутри воркера возможны, но GIL разрешает выполняться только одному потоку Python одновременно. Поэтому процессы дают параллелизм для CPU-задач, а потоки помогают только при ожидании ввода-вывода: базы данных, HTTP-клиентов, файлов.
Отсюда практическое правило: начинайте с числа процессов, равного числу доступных ядер, и не умножайте его на два «на всякий случай». Для приложения, которое считает шаблоны и сериализует JSON, лишние процессы только увеличивают потребление памяти и переключения контекста. Потоки добавляйте после замера, когда профилирование показывает ожидание, а не счёт.
Минимальный рабочий конфиг
Ниже — основа для Django-проекта. Пути и имя модуля замените на свои. Конфиг лежит в /etc/uwsgi/shop.ini, приложение — в /srv/shop, виртуальное окружение — рядом.
[uwsgi]
chdir = /srv/shop
home = /srv/shop/venv
module = config.wsgi:application
env = DJANGO_SETTINGS_MODULE=config.settings
master = true
processes = 4
threads = 2
enable-threads = true
lazy-apps = true
socket = /run/uwsgi/shop.sock
chmod-socket = 660
vacuum = true
need-app = true
harakiri = 30
max-requests = 2000
max-requests-delta = 200
buffer-size = 8192
post-buffering = 8192
limit-post = 10485760
stats = 127.0.0.1:1717
log-4xx = true
log-5xx = true
master управляет воркерами, need-app запрещает старт с пустым приложением, vacuum удаляет сокет при выходе. harakiri убивает зависший запрос через 30 секунд, max-requests перезапускает воркер после 2000 запросов, а max-requests-delta разводит перезапуски по времени.
lazy-apps загружает приложение в каждом воркере отдельно. Это расходует память, но избавляет от проблем с библиотеками, которые не любят fork. Для небольшого приложения можно сравнить RSS без него.
Процессы, потоки и GIL
Для CPU-нагрузки processes примерно равно числу ядер, выделенных контейнеру или ВМ. В Kubernetes смотрите лимит CPU, а не nproc хоста. Если лимит — два ядра, четыре процесса будут драться за кванты и дадут худшую задержку.
Потоки имеют смысл, когда обработчик ждёт. Включать threads без enable-threads бессмысленно: uWSGI не запустит потоки Python. Некоторые расширения и драйверы не потокобезопасны, поэтому проверяйте это на стенде.
Для асинхронных приложений на asyncio uWSGI — не лучший выбор. Он умеет работать с гринлетами через плагин gevent, но экосистема и документация давно сместились к Uvicorn и Hypercorn. Если у вас Django с синхронными вьюхами, uWSGI остаётся нормальным вариантом.
Память и перезапуск воркеров
Python-приложения накапливают память: кеши, фрагментация, утечки в расширениях. max-requests — грубая, но надёжная страховка. Маленькое значение вызывает перезапуски и всплески задержки, большое позволяет воркеру разрастись. Начните с 1000–3000 запросов и смотрите на RSS.
Для наблюдения за памятью подойдут ps -o pid,rss,vsz,cmd -C uwsgi, smem -P uwsgi и systemd-cgtop. Если RSS растёт пилообразно и возвращается после перезапуска — это нормальный цикл. Если растёт монотонно между перезапусками, ищите утечку, а не увеличивайте лимит.
Альтернатива фиксированному числу воркеров — подсистема cheaper: она держит минимум процессов и добавляет новые при нагрузке.
cheaper = 2
cheaper-initial = 2
cheaper-step = 1
cheaper-rss-limit-soft = 268435456
Автоматика не заменяет понимание: при резком скачке запуск воркера занимает время, и запросы ждут.
Nginx и unix-сокет
В продакшене uWSGI слушает unix-сокет, а nginx проксирует в него запросы. Сокет быстрее TCP на localhost и не занимает порт. Права должны давать доступ nginx и uWSGI, но не всему миру.
upstream shop {
server unix:/run/uwsgi/shop.sock;
}
server {
listen 80;
server_name shop.example.ru;
client_max_body_size 10m;
location /static/ {
alias /srv/shop/static/;
expires 30d;
}
location / {
include uwsgi_params;
uwsgi_pass shop;
uwsgi_read_timeout 35s;
uwsgi_buffering on;
}
}
Таймаут nginx должен быть больше harakiri, иначе nginx отдаст 504 раньше, чем uWSGI завершит запрос. Статику лучше отдавать напрямую nginx или через static-map с offload-threads, чтобы не занимать Python-воркеры картинками и CSS.
Для systemd удобно держать запуск в отдельном юните:
[Unit]
Description=uWSGI shop
After=network.target
[Service]
User=www-data
Group=www-data
RuntimeDirectory=uwsgi
ExecStart=/srv/shop/venv/bin/uwsgi --ini /etc/uwsgi/shop.ini
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
Диагностика и нагрузочный тест
Проверьте, что конфиг читается и приложение импортируется, затем подключитесь к сокету напрямую, минуя nginx.
uwsgi --ini /etc/uwsgi/shop.ini --show-config
uwsgitop 127.0.0.1:1717
curl --unix-socket /run/uwsgi/shop.sock http://localhost/health
wrk -t4 -c100 -d30s http://127.0.0.1/
uwsgitop показывает очередь, время запроса и состояние воркеров. Если очередь растёт, а воркеры заняты на 100% — упираетесь в CPU. Если воркеры простаивают — проверяйте сеть, nginx или блокировки. ss -lnt показывает backlog listen-очереди, а netstat -s | grep -i listen — счётчики переполнений.
Нагрузочный тест с той же машины годится для грубой проверки. Честнее — с отдельного хоста и с профилем, похожим на реальный трафик. Смотрите не только среднее время, но и p95/p99: хвост задержки замечают пользователи.
Чек-лист перед продакшеном
- Приложение стартует с
need-app, без «пустого» WSGI-объекта. - Число процессов соответствует лимиту CPU, а не количеству ядер хоста.
- Таймауты nginx, uWSGI и балансировщика согласованы.
- Сокет имеет минимальные права, а
vacuumвключён. - Настроены
max-requests,harakiriи мониторинг RSS. - Логи 4xx/5xx собираются и не теряются при перезапуске.
- Есть health-check, который проверяет приложение, а не только сокет.
Итог
uWSGI в 2026 году — зрелый инструмент для синхронных Python-приложений. Производительность определяется не магическими параметрами, а числом воркеров под доступные ядра, контролем памяти и честными замерами. Начните с простого конфига, включите статистику, снимите p95/p99 под реалистичной нагрузкой и только потом меняйте лимиты. Для новых asyncio-проектов разумнее смотреть в сторону Uvicorn, но переписывать работающий uWSGI-стек из-за моды не стоит.