Апгрейдим Flask при помощи Quart: троекратный выигрыш в скорости

С момента появления фреймворка 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 обеспечивает безболезненный переход в асинхронную экосистему.