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-стек из-за моды не стоит.

Насколько публикация полезна?

Нажмите на звезду, чтобы оценить!

Средняя оценка 0 / 5. Количество оценок: 0

Оценок пока нет. Поставьте оценку первым.

Поделиться: Telegram ВКонтакте