Вы написали быстрый асинхронный код на Python с использованием asyncio, но при запуске через классический сервер он работает медленнее синхронного? Это частая боль разработчиков. Проблема не в вашем коде, а в несоответствии протоколов. Если вы используете WSGI для обработки запросов, ваш await просто блокирует поток, отменяя все преимущества многопоточности.
Чтобы получить реальную отдачу от асинхронной модели, нужно правильно подобрать сервер. В этой статье разберем, почему ASGI стал стандартом де-факто для современных веб-приложений, как выбрать между Uvicorn и Gunicorn, и какие ловушки подстерегают при миграции с синхронных фреймворков.
Ключевые выводы
- WSGI - это синхронный интерфейс. Он не понимает
async/awaitнативно. Использование асинхронных функций через WSGI требует обвязки (адаптеров), которая часто приводит к потере производительности. - ASGI - асинхронная спецификация, созданная специально для работы с
asyncio. Она позволяет одному потоку обрабатывать тысячи одновременных соединений без создания новых процессов или потоков для каждого запроса. - Uvicorn - самый популярный ASGI-сервер. Он оптимизирован для высокой пропускной способности и идеален для разработки и продакшена, если приложение чисто асинхронное.
- Gunicorn - классический WSGI-сервер. Для запуска ASGI-приложений ему нужны воркеры вроде
uvicorn.workers.UvicornWorker. Это гибридное решение, которое стоит использовать, только если часть кода остается синхронной. - Переход на ASGI не гарантирует мгновенного ускорения. Если ваши задачи CPU-bound (вычислительные), асинхронность поможет мало. Она спасает I/O-bound задачи (запросы к БД, внешние API).
Почему WSGI не дружит с asyncio
WSGI (Web Server Gateway Interface) появился в 2003 году. Его архитектура построена вокруг концепции «один поток - один запрос». Когда клиент шлет запрос, сервер выделяет поток (или процесс), который выполняет функцию приложения до конца. Внутри этой функции нет места для приостановки выполнения и ожидания данных извне - поток должен быть занят. Когда вы пишетеasync def в фреймворке вроде Django или Flask (в старых версиях), вы фактически создаете эвент-луп внутри одного потока. Но сам WSGI-сервер этого не знает. Он думает, что функция выполняется линейно. Чтобы дать вашему асинхронному коду шанс работать, разработчики пишут адаптеры, которые превращают корутину в обычный вызов. Этот механизм сложен и накладывает накладные расходы.
Вот простой пример проблемы:
- Клиент A отправляет запрос. Сервер открывает поток 1.
- Код в потоке 1 встречает
await http_request(). - В идеальном мире поток 1 должен освободиться, чтобы принять Клиента B.
- Но WSGI-сервер ждет завершения функции. Поток 1 заблокирован, пока не вернется ответ от внешнего API.
- Если таких запросов 100, вам нужны 100 потоков. Операционная система начинает тормозить из-за контекстных переключений.
Как работает ASGI и зачем он нужен
ASGI использует модель событий. Вместо того чтобы блокировать поток, ваша асинхронная функция возвращает объект, который можно «прервать» и «возобновить». Сервер, поддерживающий ASGI, управляет этим циклом через Event Loop (цикл событий). Давайте посмотрим на различия в обработке одного и того же сценария: получение данных из внешней REST API.
Выбор сервера: Uvicorn против Gunicorn
Здесь начинается самое интересное. Многие думают, что нужно просто заменитьgunicorn на uvicorn в команде запуска. Но выбор зависит от вашей архитектуры.
Uvicorn - это легковесный ASGI-сервер, написанный на Python, но использующий libuv (ту же библиотеку, что и Node.js) для операций ввода-вывода. Он очень быстрый и прост в настройке.
Команда запуска для разработки выглядит так:
uvicorn main:app --host 0.0.0.0 --port 8000 --reload
Для продакшена обычно добавляют несколько воркеров:
uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4
Но есть нюанс. Uvicorn по умолчанию использует один поток для цикла событий. Если в вашем коде есть тяжелые CPU-операции (например, сложная математика или обработка изображений без внешних библиотек), они заблокируют весь цикл событий, и другие запросы замрут. Поэтому для CPU-intensive задач лучше использовать процессы, а не потоки.
Gunicorn остается королем WSGI, но он научился работать с ASGI через воркеры. Если у вас старый проект на Flask или Django, где только часть эндпоинтов стала асинхронной, полная миграция на Uvicorn может быть рискованной. Тогда используют гибрид:
gunicorn -k uvicorn.workers.UvicornWorker -w 4 --bind 0.0.0.0:8000 main:app
Здесь Gunicorn управляет процессами (воркерами), а внутри каждого процесса работает Uvicorn как ASGI-сервер. Это дает стабильность управления процессами от Gunicorn и асинхронность внутри воркера.
Типичные ошибки при переходе на асинхронность
Даже после установки правильного сервера, код может работать медленно. Вот три самые частые причины.1. Блокирующие вызовы в async-функциях
Если вы вызываете обычную синхронную функцию (например,time.sleep(1) или requests.get()) внутри async def, вы блокируете Event Loop. Все другие ожидающие запросы зависнут на это время.
* Ошибка: import requests; response = requests.get(url)
* Решение: Используйте асинхронные клиенты, такие как httpx или aiohttp. Например: response = await client.get(url)
2. Синхронная база данных
Многие используют SQLAlchemy в синхронном режиме даже в асинхронных приложениях. Каждый запрос к БД будет блокировать поток. * Ошибка: ИспользованиеSession вместо AsyncSession.
* Решение: Перейдите на драйверы, поддерживающие asyncio, такие как asyncpg для PostgreSQL или aioodbc для других СУБД. Убедитесь, что в конфиге SQLAlchemy указан create_async_engine.
3. Неверное количество воркеров
Новички часто ставят 1 воркер, думая, что асинхронность решит все. Или наоборот, ставят 50 воркеров, забывая, что каждый воркер - это отдельный процесс с своей копией памяти. * Правило: Для I/O-bound задач достаточно 1-2 воркеров на ядро CPU, если нагрузка высокая. Для CPU-bound задач - 1 воркер на ядро. Начинайте с 2-4 и мониторьте использование CPU.
Практический пример: FastAPI vs Django ASGI
Рассмотрим два популярных фреймворка, которые активно используют ASGI. FastAPI построен на ASGI с самого начала. Он использует Pydantic для валидации и генерирует OpenAPI-документацию автоматически. Его главная сила - скорость парсинга и низкие накладные расходы на запрос. Если ваше приложение состоит из множества мелких API-эндпоинтов, FastAPI + Uvicorn будет быстрее, чем Django. Django (начиная с версии 3.0) получил экспериментальную, а затем и полноценную поддержку ASGI. Однако Django исторически ориентирован на ORM и админку, которые могут быть тяжелыми. При использовании Django в асинхронном режиме важно помнить, что ORM по-прежнему может выполнять синхронные операции, если вы не будете осторожны. Тем не менее, для крупных корпоративных приложений с сложной бизнес-логикой Django ASGI остается сильным выбором благодаря зрелости экосистемы.Чеклист перед деплоем асинхронного приложения
Прежде чем отправлять код в прод, пройдите по этому списку:- Все ли HTTP-клиенты заменены на асинхронные (
httpx,aiohttp)? - База данных использует асинхронный драйвер (
asyncpg,aiomysql)? - Отсутствуют ли вызовы
time.sleep()илиos.system()в асинхронных функциях? - Сервер настроен на правильное количество воркеров (обычно равное числу ядер CPU)?
- Настроено ли логирование ошибок в Event Loop (чтобы ловить исключения, проглоченные корутинами)?
- Проведена ли нагрузочная тестирование (например, с помощью Locust или
k6)?
FAQ: Частые вопросы о совместимости
Можно ли запустить асинхронный код на Gunicorn без Uvicorn?
Технически да, если использовать другие ASGI-воркеры, такие как granian или старые версии websockets воркеров, но Uvicorn является наиболее стабильным и оптимизированным выбором. Сам по себе Gunicorn (без ASGI-воркера) не сможет эффективно исполнять async/await код, так как его базовая архитектура синхронная.
Какой сервер лучше для WebSocket: Uvicorn или Daphne?
Оба поддерживают WebSocket. Daphne изначально создавался командой Django и тесно интегрирован с Django Channels. Uvicorn часто показывает лучшую общую производительность для чистых ASGI-приложений и имеет более активное сообщество вне экосистемы Django. Для проектов на FastAPI или Starlette Uvicorn предпочтительнее из-за скорости и простоты конфигурации.
Стоит ли мигрировать с WSGI на ASGI, если приложение не нагружено?
Если приложение небольшое и количество пользователей меньше 1000 одновременно, разница в производительности может быть незаметна. Однако переход на ASGI открывает возможности для будущего масштабирования и поддержки Real-time функций (WebSocket, SSE). Если вы планируете рост, лучше начать с ASGI сразу, чтобы избежать сложной рефакторинга позже.
Почему мой асинхронный код медленнее синхронного?
Это происходит, если ваши задачи являются CPU-bound (занимают процессорное время), а не I/O-bound (ждут сеть/диск). Асинхронность помогает только там, где есть ожидание. Если вы делаете сложные вычисления, лучше использовать многопоточность или multiprocessing, либо вынести эти задачи в отдельные сервисы (Celery/RQ).
Нужен ли Nginx перед Uvicorn?
Для продакшена - почти всегда да. Nginx работает как reverse proxy: он обрабатывает статические файлы, сжимает ответы, управляет таймаутами и балансирует нагрузку между несколькими экземплярами Uvicorn. Сам Uvicorn хорош для обработки логики, но Nginx надежнее для работы с сетью и клиентами.