С момента появления фреймворка Flask восемь лет назад язык Python сильно эволюционировал. Особенно это связано с появлением библиотеки asyncio. С ее помощью были разработаны, например, библиотеки uvloop и asyncpg. Благодаря им производительность очень сильно выросла. К сожалению, использовать Flask c этими библиотеками непросто. Но к счастью, Flask можно совместить с asyncio при помощи фреймворка Quart.
Quart обеспечивает для Flask-приложений самый простой переход на использование asyncio, так как он сам использует Flask-API. Это означет, что любое Flask-приложение можно лекго переделать в Quart-приложение, а затем свободно пользоваться новыми библиотеками, которые выведут производительность на новый, недостижимый для Flask-приложений, уровень.
В данной статье мы детально рассмотрим процесс перехода от Flask ко Quart для типичного CRUD-приложения. Также мы покажем прирост производительности при размещении данного приложения.
Сравнение производительности
Апгрейд данного Flask-pyscopg2-приложения до Quart-asyncpg-приложения дает нам прирост производительности в три раза без каких-либо существенных изменений в коде.
Route | Flask | Quart | Ratio | Req/s | Req/s | (Quart/Flask) ----------------------------------------------- GET /films/995/ | 330 | 1160 | 3.5 GET /films/ | 99 | 195 | 2.0 POST /reviews/ | 325 | 1114 | 3.4
Наше приложение
Для проведения сравнения мы будем рассматривать несложное приложение, которое просто предоставляет RESTful-интерфейс к базе данных. Для микросервисной архитектуры это обычная задача, а код, используемый для ее решения, очень прост.
В данном приложении имеется два блупринта, состоящие в общей сложности из трех маршрутов. Эти маршруты предназначены для представления типичных CRUD-операций, а именно: GET /films/<int:id>/
— для единичного ресурса, GET /films/
— для всех ресурсов и POST /reviews/
— создание нового ресурса.
Искодный кода находится на GitHab и состоит из двух коммитов, Flask и Quart версии.
От Flask к Quart
Эволюция от Flask к Quart весьма проста и состоит из минимальных изменений. А именно, все импорты Flask заменяются на импорты Quart и все функции делаются асинхронными. Вот пример таких изменений. Данный код
def add_review(): data = request.get_json() ...
становится вот таким:
async def add_review():
data = await request.get_json()
...
Со всеми изменениями можно ознакомиться здесь.
От psycopg2 к asyncpg
Преобразовать код psycopg2 для использования asyncpg несколько сложнее, так как эти две библиотеки имеют различное применение. Чтобы сгладить эти различия, во Flask-приложении используется PoolWrapper
. Он обеспечивает контекстному менеджеру psycopg2 соединение с тем же API что и asyncpg. Это позволяет использовать asyncpg, изменяя with
на async with
.
Помимо соединения, asyncpg и psycopg2 также различаются по использованию курсора, транзакциям, аргументам выполнения и форматированию запросов. Со всеми различиями можно ознакомиться здесь.
Размещение
Само по себе приложение Flask едва ли можно масштабировать при размещении. Дело в том, что сервер Flask может обрабатывать только один запрос за раз. Поэтому WSGI-сервер обычно используется вместе с каким-либо асинхронным иснтрументом, например Gunicorn с eventlet.
Quart также лучше размещать вместе с Gunicorn, тогда для запуска Flask и Quart приложений можно будет использовать одну и ту же строку:
$ gunicorn --config gunicorn.py 'run:create_app()'
Конфигурационный файл будет отличаться только использующимся в нем веб-воркером (eventlet во Flask и Quart-UVLoop в Quart).
Измерения производительности проводились с Flask и Quart-приложениями, работающими под Gunicorn.
База данных
Используется база данных Postgresql (Postgresql sample database), что дает возможность приложению работать в стиле CRUD.
Измерение производительности
Для измерения производительности использзуется следующий инструмент. Он настроен на использование 20 соединений в соответствии с размером пула соединений базы данных (это должно обеспечить максимальную пропускную способность, а 20 — это типичное значение, которое мы использовали). Для измерения скорости GET-запросов команда имеет следующий вид:
$ wrk --connections 20 --duration 5m http://localhost:5000/${PATH}/
А для POST-запросов команда будет вот такой:
$ wrk --connections 20 --duration 5m --script post.lua http://localhost:5000/${PATH}/
Файл post.lua можно посмотреть вот здесь.
Конфигурация системы тестирования
База данных Posgres (9.5.10), wrk (4.0), Python (3.6.3) со следующими пакетами: asyncpg (0.13.0), Flask (0.12.2), Gunicorn (19.7.1), psycopg2 (2.7.3.2), Quart (0.3.1). Все это запущено на сервере AWS c4.large.
Результаты
Все результаты представлены в таблице ниже:
Route | Запросы в секунду | Средняя задержка [ms] | | Flask | Quart | Flask | Quart | --------------------------------------------------------------- GET /films/995/ | 330.22 | 1160.27 | 60.55 | 17.23 | GET /films/ | 99.39 | 194.58 | 201.14 | 102.76 | POST /reviews/ | 324.49 | 1113.81 | 61.60 | 18.22 |
Обратите внимание, что Quart-приложение производит в 2 — 3,5 раза больше запросов в секунду с соответствующим уменьшением средней задержки в 2 — 3,5 раза.
Выводы
Трансформация Flask-приложения в Quart-приложение довольно проста, так как у них один и тот же API, и основные усилия тратятся на написание ключевых слов async
и await
в нужных местах. Однако, переход от psycopg2 к asyncpg уже сложнее и, вероятно, весьма затруднителен при использовании SQLAlchemy или другой подобоной ORM.
Производительность приложения заметно увеличилась. Это в основном произошло за счет использования asyncpg и uvloop. Сам по себе Quart предполагает более скромное увеличение производительности: примерно в 1,5 раза.
Итак, в заключении скажем, что переход от Flask-psycopg2-приложений ко Quart-asyncpg-приложениям весьма прост и дает довольно существенный прирост производительности. Также здесь возможно и использование любых других библиотек на основе asyncio. Это означает, что Quart обеспечивает безболезненный переход в асинхронную экосистему.