Python
August 2009 19

Сравнение эффективности способов запуска веб-приложений на языке Python

Последнее время в области веб-разработок стал набирать популярность язык программирования Python. Однако, массовому распространение Python мешает проблема эффективного запуска приложений на этом языке. Пока, в большинстве случаев, это удел выделенных или виртуальных серверов. Модульные языки в отличии от монолитного в базовой функциональности php на каждый запрос подгружают как минимум runtime-библиотеку, а как максимум — ещё несколько десятков запрашиваемых пользователем модулей. Поэтому классический подход наподобие mod_php для Python и Perl не очень уместен, а держать приложение постоянно в памяти было дороговато. Но время движется, техника стала мощнее и дешевле, и уже достаточно давно можно спокойно говорить о постоянно запущенных процессах с приложением в рамках массового хостинга.

О чём тут

Время от времени, в сети появляются различные предложения как запустить приложение на Python. Например, недавно хостинг Джино уникально поправил mod_python и предложил хостинг именно с его помощью. Следом за ним, некий хостинг Locum вообще отринул mod_python с его безопасностью (создаётся впечатление, что суть самобытная безопасность — это единственная проблема АйТи на пути к нирване) и провёл победоносное тестирование modwsgi против fastcgi. Комьюнити же, судя по проведённому мною поиску, разрывается между mod_python и FastCGI. Причём, FastCGI обычно имеется ввиду тот, что идёт в поставке Django — flup. Являясь популярным хостингом Python-приложений, мы не смогли пройти мимо и решили внести свою лепту в эту священную войну.

Я искренне считаю, что любая технология должна оптимально соотносить кошерность реализации, производительность, удобство использования и универсальность. Исходя из этого и будет описано каждое из представленных решений. Я подошёл к вопросу выбора субъективно, остановившись на веб-сервере apache как универсальном понятном всем диспетчере процессов. Из www.wsgi.org/wsgi/Servers я выбрал flup ( trac.saddi.com/flup ), python-fastcgi ( pypi.python.org/pypi/python-fastcgi ), и mod_wsgi ( www.modwsgi.org ). Заодно взял mod_python ( www.modpython.org ) — как наиболее популярный среди среднестатистического хостера способ запуска python.

Ближе к телу


image
Я попытался создать идеальные условия для всех вариантов, нет перезагрузок после некоего количества запросов, всё сделано штатными простыми способами. Практически, происходит тестирование эффективности и производительности пути Apache->Паблишер->Приложение. Многие подобные тесты зачем-то тестируют ещё и производительность интерпретатора, но я затруднился объяснить себе зачем сравнивать один и тот же интерпретатор, а в случае разных — формализовать реализацию какой функциональности и почему требуется тестировать. Особенно хочу обратить внимание на то, что все технические тесты дают только сравнительную оценку производительности. Поэтому не делалось никакой специальной настройки и увеличения производительности. Чтобы избежать лишних разглагольствований о php — в тест включён и mod_php.

Для всех демонизированных процессов выбраны условия — 2 предзапущенных процесса по 5 тредов. Кроме специального случая с flup. Все приложения тестируются утилитой ab 100000 (сто тысяч) запросов, 10 одновременно, плюс дополнительный тест mod_python на 10000 (десять тысяч) запросов. Тесты последовательно проводились на апаче с 5-ю, 30-ю и 100 предзапущенными процессами (MPM Prefork) для определения тенденций.

Подопытный

Двухпроцессорный Xeon E5335 2.00GHz, RAM 4Gb, SCSI винчестеры со SCSI-3 интерфейсом. Установлена FreeBSD 7.2-RELEASE amd64, Apache 2.2.11, php 5.2.10, python 2.5.4, mod_wsgi 3.0, mod_python 3.3.1, mod_fastcgi 2.4.6, python-fastcgi 1.1 и fcgi devkit 2.4.0, flup 1.0.2. Все тесты проводились локально на сервере, нагрузка никогда не выходила за 1.
image
image
image

flup

Представляет собой WSGI-сервер с интерфейсом FastCGI. Является основным и единственным штатным способом запуска Django-приложения docs.djangoproject.com/en/dev/howto/deployment/fastcgi. Для тестов я использовал следующую программу:
#!/usr/local/bin/python<br/> <br/>def my_wsgi_application(environ, start_response):<br/>        status = '200 OK'<br/>        output = 'Hello World!'<br/>        response_headers = [('Content-type', 'text/plain')]<br/>        start_response(status, response_headers)<br/>        return [output]<br/> <br/>application = my_wsgi_application<br/>from flup.server.fcgi import WSGIServer<br/>wsgi_opts = {'maxSpare': 5,'minSpare': 5,'maxThreads': 5}<br/>WSGIServer(application,**wsgi_opts).run() <br/>

Такому способу запуска приложений присуще несколько трудностей — невозможность перезапуска приложения без перезапуска сервера, невозможность перезагрузки кода приложения без перезапуска или каких-то сторонних доработок, необходимость самостоятельно объявлять обработчик fastcgi и его параметры в приложении. Это верно и для python-fastcgi. Как видно из результатов, flup имеет некую насыщенность уже на тесте с 5-ю предзапущенными процессами апача. Так же, зафиксировал, что всё что flup не может обработать сразу, он выкидывает. Я получал до 40% ошибок запросов на тестах. Грусть и печаль вызывает этот тест, поскольку программисты по моей статистике редко смотрят как будут работать программы и для многих я открываю сейчас америку. Очень удивившись, я решил посмотреть поведение flup без строгого ограничения запущенных нитей и написал следующую программу, убрав лишние параметры:
#!/usr/local/bin/python<br/> <br/>def my_wsgi_application(environ, start_response):<br/>        status = '200 OK'<br/>        output = 'Hello World!'<br/>        response_headers = [('Content-type', 'text/plain')]<br/>        start_response(status, response_headers)<br/>        return [output]<br/> <br/>application = my_wsgi_application<br/>from flup.server.fcgi import WSGIServer<br/>WSGIServer(application).run() <br/>

Результат был ожидаем. Потерь нет, flup создаёт треды по мере надобности (следил за выводом ps), но как и следовало предполагать, производительность упала почти вдвое.
Итак, передаю тебе пламенный привет, самый популярный на сегодняшний день способ запуска Django…

modwsgi

Представляет собой WSGI-сервер оформленный в виде модуля к Apache. Основной способ применения — в режиме демона. Т.е. когда веб-сервер является только посредником между создаваемыми резидентными программами и управлялкой ими. Является основным рекомендуемым способом запуска Django docs.djangoproject.com/en/dev/howto/deployment/modwsgi Ввиду использования с Apache'ем можно использовать всякие апачевские «штучки» вроде .htaccess и не так пугает системных администраторов. Этот же факт сильно пугает разработчиков, услышавших о nginx и считающих Apache злом во плоти. Программка, использованная мною для тестов, выглядит так:
def my_wsgi_application(environ, start_response):<br/>        status = '200 OK'<br/>        output = 'Hello World!'<br/>        response_headers = [('Content-type', 'text/plain')]<br/>        start_response(status, response_headers)<br/>        return [output]<br/> <br/>application = my_wsgi_application <br/>

Результаты тестирования показывают увеличение производительности с увеличением обработчиков Apache, т.е. отсутствие насыщенности. И явно производительнее flup.

Хочется отметить некоторые особенности modwsgi. Во-первых, он имеет собственную настройку, сколько запросов обработать прежде чем перезагрузиться. Это позволяет ему эффективно бороться с утечками памяти. Я не сделал этой настройки в примерах (как и для других способов), ибо очевидно, что это немного уронит производительность. Во-вторых, он имеет уникальную, в отличии от остальных способов, настройку времени простоя, после чего он перегружается. Это позволяет не держать в памяти развёрнутое приложение или приложение с утекшей памятью в то время, когда оно не требуется. В-третьих, он автоматически перегружается при обновлении файла приложения. Т.е. при модификации программы, мы всегда уверены, что увидим новую версию. Никто из перечисленных способов не умеет ничего этого без специальных доработок. Ещё одной важной особенностью является вынос даже поминания способа запуска приложения из зоны ответственности приложения. Обратите внимание на пример — программа действительно имеет только WSGI-интерфейс и всё.

python-fastcgi

Представляет из себя… бинго! — WSGI_сервер, с интерфейсом FastCGI. На деле — обёртка вокруг стандартного C++ FastCGI. Программа выглядит так:
#!/usr/local/bin/python<br/>import fastcgi<br/>def my_wsgi_application(environ, start_response):<br/>        status = '200 OK'<br/>        output = 'Hello World!'<br/>        response_headers = [('Content-type', 'text/plain')]<br/>        start_response(status, response_headers)<br/>        return [output]<br/> <br/>application = my_wsgi_application<br/>s = fastcgi.ThreadedWSGIServer(my_wsgi_application, workers=5)<br/>s.serve_forever() <br/>

Результаты теста говорят сами за себя. С ростом обработчиков сервера растёт производительность. Очевидно, python-fastcgi — лидер наших тестов (привет, Locum). Вообще, после того как я в принципе победил подъём FastCGI посредством Apache, этот модуль вызвал меньше всего вопросов и нареканий. Естественно, он обладает всеми недостатками такого способа запуска — сложность настройки, зависимость сервера от приложения, отсутствие штатных инструментов перезагрузки (например, чтобы обновить программу), etc.

mod_python

Представляет собой модуль сервера Apache наподобии mod_php. Имеет несколько «зацепок», не имеет WSGI-интерфейса. Основной проблемой считается безопасность, поскольку без доработки исполняется от имени сервера. Правда, этим же недостатком болеет любой встроенный модуль, включая mod_php. Я написал следующую программку для тестирования:
#!/usr/local/bin/python<br/> <br/>def index(req):<br/>        return "Hello World!\n" <br/>

Неожиданно результаты оказались достаточно скромными. В процессе тестирования выяснилась ещё одна особенность. Вот результаты тестов на 10000 тысячах запросов:
image
Видно, что с увеличением числа обработчиков производительность… падает. Это объясняется тем, что при запуске сервера apache не «подсасывает» приложение, а делает это только по факту попадания запроса в один из обработчиков. Соответственно, чем больше я сделал обработчиков, тем больше запросов пришло «в первый раз». Совершенно очевидно, что при наличии 2-3 активных приложений, перегрузка будет достаточно частой. Выбирать ли способ запуска приложения, когда настраивать можно будет только целиком сервер — дело конечно ваше. Также, mod_python имеет проблемы обновления кода. Хотя у него есть соответствующая настройка, нам не удалось его заставить эффективно обновлять код приложения при изменении без перезагрузки всего сервера. На некоторых хостингах он работает без видимой проблемы обновления кода за счёт использования модуля diffpriv. Но возникает вторая проблема — приложение загружается сервером на КАЖДЫЙ запрос, что уже даже экстраполируя наши тесты даёт серьёзное падение производительности. И отдельный острый вопрос, конечно же — выбор «паблишеров» и работа с ними. Оказалось, что mod_python — самый подвал нашего рейтинга по сумме показателей.

mod_php

Для сравнения, я решил прогнать через тесты и php. Программка выглядит вполне очевидно:
<?php echo("Hello, World!"); ?> <br/>

Результаты очевидные, но воображение не поразили. При отсутствии лишней связи и монолитности самого php я ожидал множителя от 2 и выше.

Резюме достаточно простое и очевидное. чем технология проще, тем она обычно производительнее. Лидер рейтинга в номинации производительности несомненно python-fastcgi, лидер в номинации удобства — modwsgi. Более того, modwsgi очевидно на сегодня представляет собой оптимальное решение по суммам характеристик, хотя не является ни самым производительным, ни самым безглючным.
+41
13.1k 103
Support the author
Comments 91