Любому веб-приложению нужен веб-сервер для доступа извне. На самом деле, многие даже не уделяют выбору веб-сервера для своего приложения достаточного внимания: на Django берут "популярный" Gunicorn (а кто-то ещё и обвязывает его Uvicorn'ом), а для FastAPI Uvicorn практически стандарт. Тут "на сцену" выходит Granian, представляющий впечатляющие результаты производительности с простой настройкой.

В своём проекте "Код на салфетке" я использовал uWSGI, но недавно узнал про новый Granian и мне стало интересно на что он способен. Так появилась идея для этой статьи.

В этой статье узнаем:

  • Что такое веб-сервер и какие у них есть интерфейсы взаимодействия.

  • Какие есть популярные WSGI и ASGI веб-серверы.

  • Сравним их производительность.

Если вам интересны подобные материалы, подписывайтесь на наш Telegram-канал «Код на салфетке». У нас много интересного, в комментариях можно обсудить или предложить идею для статьи.


Что такое веб-сервер

Веб-сервер, в рамках применения в Python, это программа, которая запускает Python-приложение и обеспечивает его взаимодействие с внешним миром по протоколу HTTP (или другим, если необходимо, например, WebSocket).

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

Воркеры и процессы — это ключевые понятия для понимания работы веб-серверов:

  • Воркер (worker) — это отдельный процесс или поток, который обрабатывает входящие запросы.

  • Процесс — это независимая программа в операционной системе со своей памятью.

Использование нескольких воркеров позволяет обрабатывать запросы параллельно, что увеличивает производительность.


Интерфейсы взаимодействия

Для "общения" между веб-сервером и приложением существует три интерфейса взаимодействия:

  • WSGI (Web Server Gateway Interface).

  • ASGI (Asynchronous Server Gateway Interface).

  • RSGI (Rust Server Gateway Interface).

Разберём подробнее каждый.

WSGI (Web Server Gateway Interface)

Стандарт интерфейса между Python-приложением и веб-сервером, появившийся ещё в Python 2.2 (PEP 333, потом обновлён до PEP 3333).

  • Синхронный интерфейс взаимодействия — обрабатывает запросы один за другим.

  • Применяется преимущественно в "классических" фреймворках, вроде Django или Flask.

  • Не поддерживает WebSocket, SSE (Server-Sent Events) и некоторые другие возможности.

Достаточно простой интерфейс, подходящий для небольших и низконагруженных приложений.

ASGI (Asynchronous Server Gateway Interface)

Современный стандарт интерфейса для Python-приложений. Придуман как "духовный наследник" WSGI.

  • Синхронный и асинхронный интерфейс взаимодействия — может обрабатывать множество запросов одновременно.

  • Применяется в "молодых" фреймворках, вроде FastAPI, а также может применяться в Django (через Channels) или Flask.

  • Поддерживает WebSocket, SSE, HTTP/2 и так далее.

Актуальный интерфейс взаимодействия для больших и высоконагруженных приложений.

RSGI (Rust Server Gateway Interface)

Новый проект (2024-2025), вдохновлённый WSGI/ASGI, но созданный с прицелом на Rust + Python экосистему. Его продвигают разработчики Granian и Uvicorn.

  • Идея: минималистичный интерфейс между веб-сервером и приложением.

  • Оптимизирован для Rust-бэкендов и Python-приложений, чтобы убрать избыточные прослойки.

  • Цель — заменить ASGI/WSGI в будущих фреймворках.

  • Поддерживает синхронные, асинхронные и нативные Rust-хендлеры.

  • Акцент на высокую производительность и простоту (ASGI со временем оброс сложностями).

Экспериментальный интерфейс, появившийся совсем недавно. Может проявить себя в высоконагруженных приложениях, написанных на Python, или комбинированных системах, использующих не только Python, но и Rust.


Python веб-серверы

Веб-серверов для Python много, но мы разберём и сравним только несколько — те, которые чаще всего встречаются в обучающих статьях, а также рассмотрим на их фоне Granian.

Обратите внимание! Производительность и потребление ресурсов собраны из разных источников и чаще всего описывают "Hello World" тесты. Значения усреднены и могут отличаться на реальных проектах, поскольку на производительность влияет не только сам веб-сервер, но и оптимизация приложения.

Gunicorn (WSGI)

Лёгкий WSGI-сервер для Python (Green Unicorn). Широко используется с Django/Flask. Работает по схеме prefork (несколько процессов-воркеров).

  • Конфигурация: простая. Например, gunicorn --workers 4 myproject.wsgi:application. Хорошо масштабируется на нескольких ядрах. Формула расчета воркеров: рекомендуется число воркеров ≈ (2 × количество CPU ядер) + 1.

  • Особенности: реализует только WSGI, не поддерживает ASGI/async "из коробки". Для работы с асинхронными задачами можно применять сторонние классы воркеров (gevent/eventlet) или использовать gunicorn + uvicorn-воркеры. Не имеет встроенной поддержки WebSocket (только HTTP).

  • Производительность: хорошие показатели для синхронных нагрузок. ~3 000-10 000 запросов/сек — в зависимости от числа воркеров, окружения, нагрузки. При этом память на воркер ~30 MB.

Установка:

pip install gunicorn

Запуск (Django):

gunicorn --workers 4 myproject.wsgi:application

Запуск (Flask):

gunicorn --workers 4 "myapp:app"

uWSGI (WSGI)

"Полноценный" веб-сервер/приложение-сервер. Поддерживает WSGI, FastCGI, HTTP и др. Можно использовать в режиме префорк или с потоками/Greenlets.

  • Конфигурация: очень гибкая и сложная. Требует конфигурационного файла (uwsgi.ini) с множеством опций (процессы, потоки, кеширование, балансировка). Из-за богатого набора опций порог вхождения высок.

  • Особенности: предназначен для высоких нагрузок и масштабируемых систем (например, Emperor mode, multiple apps). Поддерживает горячую замену кода, кэширование и балансировку процессов.

  • Производительность: несколько лучше, чем у Gunicorn в бенчмарках: 10-12 тысяч запросов/сек. Память на воркер чуть выше — 40 MB.

Установка:

pip install uwsgi

Запуск (Django):

uwsgi --http :8000 --module myproject.wsgi:application --processes 4 --threads 2

Запуск (Flask):

uwsgi --http :8000 --module myapp:app --callable app

Пример uwsgi.ini:

[uwsgi]
module = myproject.wsgi:application
master = true
processes = 4
threads = 2
http = :8000

Uvicorn (ASGI)

Лёгкий ASGI-сервер для Python. Построен на основе uvloop и httptools (Cython). Предназначен для асинхронных приложений (Starlette, FastAPI, Django Channels).

  • Конфигурация: очень простая. Запускается одной командой: uvicorn myapp.asgi:application --workers 4. Не имеет сложных конфигов, можно задавать параметры в CLI или скрипте Python.

  • Особенности: поддерживает HTTP/1.1, HTTP/2, WebSocket. Имеет низкий оверхед и малый размер кода, использует готовые асинхронные event loop (uvloop).

  • Производительность: высокая: ~35 000-40 000 запросов/сек. Низкая задержка. Память на воркер примерно 20 MB.

Установка:

pip install uvicorn[standard]

Запуск (FastAPI):

uvicorn main:app --workers 4 --reload

Запуск (Django Channels):

uvicorn myproject.asgi:application --workers 4

Granian (WSGI, ASGI, RSGI, Rust)

Новый HTTP-сервер для Python-приложений, написанный на Rust (использует Hyper/Tokio). Поддерживает сразу WSGI, ASGI и RSGI-интерфейсы.

  • Конфигурация: простой CLI или кодовый API. Например, granian --interface asgi myapp:app для запуска ASGI-приложения. Можно задавать количество процессов/потоков.

  • Особенности: ориентирован на максимальную производительность и пропускную способность. Из коробки поддерживает HTTP/1, HTTP/2, WebSocket'ы, HTTPS, статические файлы. Можно конфигурировать SSL/mTLS, расширения ASGI. Лёгкий (нет проблем с GIL, может использовать Rust-потоки).

  • Производительность: очень высокая: ~40 000-45 000 запросов/сек в ASGI-режиме и ~40 000 запросов/сек в WSGI-режиме. Максимальная пропускная способность. Память на процесс ~15 MB.

GIL (Global Interpreter Lock) — это механизм в Python, который позволяет выполнять только один поток Python-кода одновременно. Это ограничение влияет на производительность многопоточных приложений, но не касается многопроцессных решений.

Установка:

pip install granian

Запуск (FastAPI / ASGI):

granian --interface asgi main:app --workers 4

Запуск (Django WSGI):

granian --interface wsgi myproject.wsgi:application --workers 4

Запуск (Django Channels):

granian --interface asgi myproject.asgi:application --workers 4

Запуск (FastAPI / RSGI — экспериментально):

main.py:

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
async def root():
    return {"message": "Hello from RSGI + FastAPI"}

Команда запуска:

granian --interface rsgi main:app --workers 4

Важно: RSGI не поддерживается существующими фреймворками вроде Django или Flask, поэтому применять его можно только с библиотеками/фреймворками, изначально рассчитанными на асинхронный стек. Для FastAPI RSGI может дать буст, но для Django он неприменим (на момент написания статьи).

Сравнение производительности

RPS и Latency — важные метрики производительности:

  • RPS (Requests Per Second) — количество запросов, которые сервер может обработать за секунду.

  • Latency (задержка) — время, которое требуется для обработки одного запроса.

RPS (запросов в секунду):

  • Gunicorn: ~3 000–10 000 запросов/сек.

  • uWSGI: 10 000–12 000 запросов/сек.

  • Uvicorn: ~35 000–40 000 запросов/сек.

  • Granian:

    • ASGI: ~40 000–45 000 запросов/сек.

    • WSGI: ~40 000 запросов/сек.

Вывод по производительности: ASGI-серверы (Uvicorn, Granian) показывают значительно лучшие результаты по количеству обрабатываемых запросов в секунду по сравнению с традиционными WSGI-серверами. Granian демонстрирует лучшие показания благодаря оптимизациям на Rust и отсутствию ограничений GIL.

Потребление памяти:

  • Gunicorn: ~30 MB на воркер.

  • uWSGI: ~40 MB на воркер.

  • Uvicorn: ~20 MB на воркер.

  • Granian: ~15 MB на воркер.

Вывод по памяти: Granian показывает наименьшее потребление памяти благодаря эффективной реализации на Rust. Uvicorn также демонстрирует хорошие показатели. Традиционные WSGI-серверы потребляют больше памяти из-за особенностей архитектуры Python и дополнительных слоев абстракции.

Задержка (Latency):

  • Gunicorn: ~6-12 мс (средняя задержка).

  • uWSGI: ~4-8 мс (средняя задержка).

  • Uvicorn: ~2-5 мс (средняя задержка).

  • Granian:

    • ASGI: ~0.7-1.2 мс (средняя задержка).

    • WSGI: ~1.5-2.5 мс (средняя задержка).

Вывод по задержкам: Granian демонстрирует исключительно низкую задержку во всех режимах работы. ASGI-реализация показывает субмиллисекундные задержки, что критично для высокочувствительных к латентности приложений. Даже в WSGI-режиме Granian превосходит традиционные серверы по этой метрике.


Пара слов о Granian

Приложения на Rust всё чаще проникают в закулисья Python-библиотек. Первой ласточкой был "нашумевший" пакетный менеджер uv, обеспечивший высокую скорость работы с окружением проекта. Теперь о себе заявляет Granian как новый и, что немаловажно, быстрый веб-сервер.

Ради интереса, перед написанием статьи я решил попробовать его в работе с Django.

Как я упомянул во вступлении, на сайте использовался uWSGI — он неплох, но его главный минус в сложности настройки для новичков. Запуск веб-сервера выглядел так:

command: ["uwsgi", "--ini", "/code/uwsgi.ini"]

Замена на Granian не вызвала вообще никаких проблем, поскольку ему не нужны конфиг-файлы и он запускается одной командой:

command: [
  "granian",
  "--interface", "wsgi",
  "--host", "0.0.0.0",
  "--port", "8000",
  "--workers", "2",
  "--backpressure", "30",
  "--workers-lifetime", "3600",
  "--log-level", "info",
  "pressanybutton.wsgi:application"
]

Настройка параметров Granian

Количество воркеров рассчитывается по той же формуле: (2 × количество CPU ядер) + 1. Для контейнера с 1 CPU достаточно 2-3 воркеров.

Backpressure — это механизм контроля нагрузки. Параметр --backpressure 30 означает, что если в очереди накопилось 30 запросов, сервер начнёт отклонять новые подключения. Это защищает от перегрузки. Для стандартных лимитов PostgreSQL в 100 одновременных подключений, backpressure 30 — разумное значение, оставляющее запас для других процессов.

Workers-lifetime — время жизни воркера в секундах. Через 3600 секунд (1 час) воркер перезапускается, что помогает избежать утечек памяти.


Заключение

Безусловно, в статье упомянуты далеко не все существующие веб-серверы для Python-проектов. Я старался брать те, которые "на слуху", то есть фигурируют в гайдах или книгах. Несмотря на это, получившееся сравнение наглядно демонстрирует разницу в производительности между классическими синхронными (WSGI) и современными асинхронными (ASGI) подходами. Особенно выделяется Granian, предоставляющий в WSGI-режиме схожую с ASGI производительность.

Основные выводы:

  1. ASGI-серверы значительно превосходят WSGI по производительности

  2. Granian показывает лучшие результаты как по скорости, так и по потреблению памяти

  3. Выбор сервера зависит от требований проекта: для простых приложений достаточно Gunicorn, для высоконагруженных — Uvicorn или Granian

  4. Rust-решения в Python-экосистеме становятся трендом, обеспечивая лучшую производительность

Напишите в комментарии ваши мысли по поводу увеличения роли Rust-приложений в Python-библиотеках, а также каким веб-сервером пользуетесь вы! Интересно будет почитать.

Подписывайтесь на наш Telegram-канал «Код на салфетке» — у нас много материалов для новичков по Python и DevOps, а также интересные проекты!

Комментарии (0)


  1. Gorushka
    18.09.2025 11:10

    Скоро всем питоном на раст переедем )))))


    1. proDream Автор
      18.09.2025 11:10

      Ну на раст прям не факт, но вот то, что библиотеки используют его под капотом для ускорения мне определённо нравится


  1. Arduinum
    18.09.2025 11:10

    Хорошо, что Python веб-приложения становятся быстрее. Конечно до Drogon на C++ ещё далеко, но это Python. Ну и конечно Drogon это фреймворк с веб-сервером, а не отдельный веб-сервер. Пока нагрузка небольшая на сервер, то по большому счёту нет сильной разницы какой асинхронный веб-фреймворк использовать. Разница начинается на больших нагрузках.


  1. baldr
    18.09.2025 11:10

    Ради интереса, перед написанием статьи я решил попробовать его в работе с Django.

    Так и чем всё закончилось? Стало быстрее? А что быстрее?

    Не очень понятно что должно измениться в случае WSGI-приложения. Шаблоны рендериться будут с такой же скоростью, да и интерпретатор питона будет тот же самый.


    1. proDream Автор
      18.09.2025 11:10

      Улучшился отклик сайта, и в перспективе он будет обрабатывать большое количество запросов лучше и стабильнее чем uWSGI


      1. baldr
        18.09.2025 11:10

        Простите, но это ответ из серии "дерево справа более зелёное, чем слева".

        Что значит "улучшился"? За счёт чего? У вас в обоих случаях за RSGI/WSGI фронтендом стоит питоновское приложение, одно и то же. И интерпретатор один и тот же. Однопоточный. В случае gunicorn фактически воркер и приложение работают в одном процессе. В RSGI, как я понимаю, бинарник на Rust вызывает Python-код, это уже некоторые накладные расходы. В любом случае, 98% времени будет выполняться именно Python-код джанги и приложения, а здесь выигрыша никакого не предвидится.

        Разница WSGI и ASGI хотя бы понятна из-за чего. Здесь хотелось бы либо объяснения, либо результатов с тестами.

        Опять же, перед всем этим в любом случае будет стоять балансер типа nginx, который возьмёт на себя основные расходы типа терминирования TLS и медленных клиентов.


        1. proDream Автор
          18.09.2025 11:10

          RSGI не применяется в джанге, это отмечено в статье. Granian работает по WSGI, однако, даже в работе синхронно он обеспечивает производительность сопоставимую с ASGI. После реверс прокси идёт именно веб-сервер, у него есть свои задержки выполнения, это сравнение также есть в статье. Если брать значения "в лоб", у granian в шесть раз лучше показать задержки выполнения, а затем уже да, идёт джанга. И вот этого достаточно, чтобы повысить отзывчивость сайта. Преимущества в RPS проявят себя со временем при росте посещаемости. Gunicorn'а у меня никогда не было, я сразу использовал uWSGI и заменил его на Granian, вместо обвзяки Django Cannels + uvicorn.


  1. Arduinum
    18.09.2025 11:10

    Было-бы интересно увидеть статью как автор встраивает RSGI в своё веб-приложение, посмотреть нагрузку в его приложении и услышать его личный отзыв от использования.


    1. proDream Автор
      18.09.2025 11:10

      Думаю, что опробую его в связке с FastAPI и изучу тему тестирования нагрузочного, чтобы примерно представлять производительность


  1. andreymal
    18.09.2025 11:10

    Синхронный интерфейс взаимодействия — обрабатывает запросы один за другим.

    Из первого не следует второе — к WSGI вполне прикручивают потоки или гринлеты (о чём вы сами же потом пишете ниже)

    Не поддерживает ... SSE (Server-Sent Events)

    SSE — это всего лишь один из вариантов streaming response, который прекрасно делается через WSGI (другое дело, что он будет занимать собой воркер, поэтому делать так обычно не очень разумно)

    Синхронный и асинхронный интерфейс взаимодействия — может обрабатывать множество запросов одновременно.

    Из первого не следует второе — стандартный event loop в питоне принципиально однопоточный, поэтому, если не обмазываться костылями вроде воркеров или стороннего пула потоков, запросы будут обрабатываться конкурентно, но не одновременно

    может применяться в Django (через Channels)

    Django уже давно поддерживает ASGI нативно без Channels (единственный мой пост как раз об этом)

    Работает по схеме prefork (несколько процессов-воркеров).

    То есть для uWSGI вы гринлеты упомянули, а gunicorn, у которого буквально слово green в названии, почему-то решили обидеть

    сторонние классы воркеров (gevent/eventlet)

    Они не сторонние

    Производительность: несколько лучше, чем у Gunicorn в бенчмарках

    А вот этот бенчмарк говорит, что uWSGI самый медленный (впрочем, насколько можно верить бенчмарку девятилетней давности, не знаю — но мне до сих пор интересно, что не так с этим бенчмарком)


    1. proDream Автор
      18.09.2025 11:10

      А вот этот бенчмарк говорит, что uWSGI самый медленный (впрочем, насколько можно верить бенчмарку девятилетней давности, не знаю — но мне до сих пор интересно, что не так с этим бенчмарком)

      https://github.com/zauberzeug/nicegui/discussions/3039


  1. Tishka17
    18.09.2025 11:10

    granian - это конечно прикольно, но если с uvicorn вы можете запустить его из асинк функции (проведя нужную инициализацию) с помощью server.serve(). То с гранианом так уже не получится, он сам гоняет свой луп и никак иначе (по крайней мере среди стабильного апи)

    RSGI в целом протокол странный. Описание очень скудное и такое ощущение что никто не задумывался об альтернативных реализациях.


    1. proDream Автор
      18.09.2025 11:10

      Granian также можно вызывать из кода:
      https://github.com/emmett-framework/granian?tab=readme-ov-file#embedding-granian-in-your-project

      Про RSGI не могу ничего сказать. Он ещё очень молод, поэтому и скуден. Интересно будет наблюдать за его развитием, как со стороны применения в Python, так и в самом Rust