klen.github.io

in Blog

Измерение производительности популярных python web-фреймворков

←Ctrl Graphite-beacon — простая система оповещений для Graphite

На праздниках решил протестировать популярные python фреймворки для web на производительность. В отличии от других тестов не стал сосредотачиваться на деплойменте, тестируя разнообразные связки uwsgi/gunicorn/chaussette/waitress/nginx и тд. Но на тестировании именно самих платформ. В частности меня интересовала «плата за асинхронность» — насколько набирающие популярность асинхронные фреймворки превосходят/уступают синхронным WSGI-решениям.

Участники тестирования

Note

Описания взяты с оригиналов

  • Aiohttp 0.16.3 -- Асинхронный фреймворк базирующийся на Asyncio;
  • Bottle 0.12.8 -- Быстрый, простой и легковесный WSGI микрофреймвок;
  • Django 1.8.2 -- Веб-фреймворк для перфекционистов с горящими сроками;
  • Falcon 0.3.0 -- Высоко-производительный фреймворк для построения облачных API;
  • Flask 0.10.1 -- Микрофреймворк базирующийся на Werkzeug, Jinja2 и хороших намерениях;
  • Muffin 0.0.88 -- Асинхронный фреймворк базирующийся на Asyncio и Aiohttp;
  • Pyramid 1.5.7 -- Небольшой, быстрый и понятный веб-фреймворк;
  • Tornado 4.2 -- Асинхронная сетевая библиотека и веб-фреймворк;

Методика тестирования

Тесты прогонялись на Amazon EC2 t2.medium сервере. Для создания нагрузки использовалась утилита WRK запущенная на том же сервере с параметрами:

wrk -d30s -t12 -c400 [URL]

Все приложения (кроме Tornado) запускались при помощи Gunicorn (2 процесса на каждое). Для синхронных WSGI библиотек использовался Meinheld worker.

Приложение на Tornado запускало 2 процесса, используя средства самого фреймворка.

Все тесты производились с использованием Python 3.4.

Каждое приложение тестировалось по трем основным сценариям:

  • JSON-тест -- закодировать небольшой объект в JSON и вернуть клиенту.
  • Remote-тест -- требуется загрузить ответ от другого сервера и вернуть его клиенту.
  • Complete-тест -- Используя ORM загрузить коллекцию объектов из базы, добавить к ней еще один и отрендерить список в шаблоне.

Первый сценарий это своеобразный «Hello World!» лишь несколько усложненный процессом кодирования в JSON, что практически не влияет на результаты. Показывает именно быстродействие в плане обработке HTTP-запросов.

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

Третий сценарий предпалагается как комплексный тест и имитирует реальную жизнь, а именно использование базы данных, ORM, движка шаблонов.

В качестве базы данных использовался Postgresql с дефолтными настройками.

Исходники приложений можно найти на Github.

Результаты

Name 50% (ms) 75% (ms) Avg (ms) Req/s Non 200-x Timeouts
Aiohttp 91.67 103.1 108.01 4093.41
Bottle 24.77 26.23 25.06 15761.45
Django 103.2 112.19 103.36 3696.90
Falcon 19.24 19.81 19.19 20677.13
Flask 64.32 71.59 65.68 6023.40
Muffin 108.07 115.09 171.56 3575.36
Pyramid 41.75 43.49 41.63 9402.69
Tornado 138.24 149.84 136.87 2829.72

В первом простом тесте с хорошим отрывом победили синхронные фреймворки. Не считая Django, но в оправдание последнего, можно сказать, что по-умолчанию этот фреймворк делает множество работы (middlewares by default). Асинхронные фреймворки делят места аутсайдеров и неожиданно для меня на последнем месте оказался Tornado. Очень хорошие результаты у Falcon и Bottle.

Name 50% (ms) 75% (ms) Avg (ms) Req/s Non 200-x Timeouts
Aiohttp 358.08 369.08 338.94 1120.27
Bottle 3363.74 9911.84 6403.92 19.09 335
Django 3317.64 12954.23 6918.64 18.96 300
Falcon 3196.23 12976.84 6696.17 19.28 328
Flask 3306.88 11690.8 6824.88 19.16 363
Muffin 372.95 428.75 376.98 1019.76
Pyramid 3295.1 10518.92 6673.78 19.35 338
Tornado 1994.39 2069.25 1928.31 194.37

Для понимания результатов следующего теста необходимо пояснить, что приложения обращались к nginx настроенному на ответ с задержкой 100ms. Из-за этого результаты синхронных фреймворков очень близки, практически вся их работа после определенного момента сводилась к ожиданию. Опять неожиданные результаты от Tornado, я предполагал, что он будет близок к Aiohttp и Muffin. Но тем неменее Tornado в 10 раз эффективнее в этом кейсе чем ближайший синхронный фреймворк.

Name 50% (ms) 75% (ms) Avg (ms) Req/s Non 2xx Timeouts
Aiohttp 151.78 156.9 254.75 1004.82 68% 236
Bottle 613.5 630.17 1062.86 451.34 178
Django 1610.46 1976.44 2632.36 88.57 42
Falcon 766.75 805.35 1457.99 350.26 81
Flask 1032.63 1649.89 1465.25 222.78 496
Muffin 420.14 485.4 1552.7 819.62
Pyramid 562.44 601.49 812.43 248.42 235
Tornado 937.37 988.86 910.06 418.36

И последний тест. Результаты Aiohttp можно игнорировать тк к сожалению более двух третей запросов вернули 502 ошибки. На первом месте неожиданно, но приятно, оказался мой фреймворк Muffin достаточно быстро обрабатывающий этот тест. Django значительно проигрывает, лишь подтверждая медлительность стандартного движка шаблонов и ORM.

Выводы

Я предлагаю читателю сделать их самостоятельно. Данное тестирование производилось мой чтобы понять выгоду использования синхронных/асинхронных библиотек и показатели производительности популярных решений.

В дальнейшем планирую проводить данные измерения регулярно.

←Ctrl Graphite-beacon — простая система оповещений для Graphite
alt