Развертываем WSGI приложение…

Статья, обобщение которой войдет в доклад для RuPyRu2007.

Итак, вопрос развертывание WSGI-приложений. Тема, а особенно на русском, не особенно развита. Краткий обзор решений.

mod_python

Скорость: 5
Удобство: 4

Один из самых быстрых методов развертывания WSGI-приложений — apache+mod_python. В этом случае все приложение и все зависимые модули загружаются в память, чем и достигается максимальное быстродействие. Существует ряд модулей (~1, ~2, ~3, ~4), преобразовывающие WSGI-протокол в формат обработчика mod_python. Конфигурационный файл для всех модулей примерно одинаков и выглядит так:

<VirtualHost *>    <Location /wsgi>        SetHandler mod_python        # конвертер wsgi <-> mod_python        PythonHandler modpython_gateway::handler         # в зависимости от конвертера, либо application, либо wsgi.application        # т.е. запускаемое wsgi-приложение        PythonOption wsgi.application testapp::app    </Location></VirtualHost>

Конвертер указывается опцией PythonHandler, а запускаемое приложение — PythonOption wsgi.application (зависит от модуля-конвертера).

Из недостатков можно отметить большой объем потребляемой памяти, как следствие, статику желательно отдавать либо “легким” apache, либо nginx/lighttpd. Т.е. сложность настроек растет, что не радует.

FastCGI

Здесь основная идея в том, что FastCGI сервер (flup) загружает интерпретатор и все зависимые модули приложения и принимает запросы от фронтенд-сервера (apache, lighttp, nginix), не запуская интерпретатор заново.

Иными словами, запущено два сервиса: FastCGI-сервер и http-сервер. Http-сервер отдает статику и перенаправляет запросы на динамику к FastCGI-серверу, который их и обрабатывает.

Прежде настройки фронтенд-сервера нужно обеспечить поддержку FastCGI со стороны приложения. Как сказано выше, это достигается использованием flup — сервера FastCGI на Python. Он может обрабатывать запросы как в threaded режиме, так и в preforked. Скрипт для запуска FastCGI сервера с WSGI-приложением выглядит примерно так:

from testapp import appfrom flup.server.fcgi import WSGIServer # threadedWSGIServer(app, bindAddress=('/tmp/fcgi_wsgi.socket')).run()

FastCGI+Apache

Скорость: 2
Удобство: 2

Чем неприятно удивил mod_fcgi, так это:

  • необходимостью прописывать для fcgi-сервера абсолютный путь вне определения VirtualHost
  • скоростью — аналогичные решения с nginx/lighttpd дают бОльшую скорость

В целом, я не вижу целевой аудитории apache+mod_fastcgi: если уже стоит apache, то есть смысл использовать mod_python, а если уж использовать FastCGI, то в качестве фронтенда резонно брать более легкий сервер — или nginx, или lighttpd.

В любом случае, конфиг таков:

NameVirtualHost *FastCGIExternalServer /var/www/wsgi.fcgi -socket /tmp/fcgi_wsgi.socket<VirtualHost *>    RewriteEngine On    RewriteRule ^/wsgi/(.*)$ /wsgi.fcgi/$1 [QSA,L]</VirtualHost>

Еще раз отмечу, что FastCGI-сервер определяется вне виртуального хоста и для него указывается абсолютный путь, а для rewrite-правил используется относительный путь к fcgi.

FastCGI может “общаться” с фронтенд-севером либо через unix socket, либо через tcp. В первом случае чуть быстрее, во втором — более гибко (в общем случае. FastCGI может быть на другой машине), подробности см. у Ковырина. Конфиги особо не отличаются, но для единообразия я указываю настройки для unix socket.

FastCGI+lighttpd

Скорость: 4
Удобство: 4

Помимо того, что с lighttpd FastCGI работает быстрей, с ним еще и удобней работать — есть возможность создавать отдельные конфиги для конкретных WSGI-приложений. Например, общий файл настроек lighttpd может не включать настроек для модуля mod_fastcgi, но отдельный конфиг может изменить это, что-то вроде такого:

server.modules   += ( "mod_fastcgi", "mod_rewrite" )fastcgi.server    = ( "/wsgi.fcgi" =>     (         "wsgi" => (            "socket" => "/tmp/fcgi_wsgi.socket",            "check-local" => "disable"        )    ))url.rewrite                   = ( "^/wsgi/(.*)" => "/wsgi.fcgi/$1")

FastCGI+nginx

Скорость: 4
Удобство: 4

Что касается nginx vs. lighttpd, то здесь, IMHO, уже играют личные предпочтения да нюансы использования. Что касается моего мнения, то мне nginx чуть больше нравится.

Для секции server я использую (у меня стоит сборка Павла Аммосова для Debian) примерно такие параметры для запуска WSGI-приложения через FastCGI:

location /wsgi {    fastcgi_pass    unix:/tmp/fcgi_wsgi.socket;    fastcgi_intercept_errors    off;    include fastcgi_params;}

По скорости FastCGI nginx примерно равен lighttpd. Да и конфиги похожи. Так что вопрос выбора между nginx и lighttpd — дело вашего вкуса.

Python-решения

Существует несколько pure Python WSGI-серверов. У каждого из них свой способ запуска. Для того, чтобы унифицировать и упростить развертывание, Ян Бикинг создал Paste. Использование Paste выходит за рамки данного обзора (тем не менее, в будущем я обязательно коснусь этой темы). Так что я приведу “низкоуровневый” способ запуска WSGI-приложений для наиболее интересных pure Python решений.

Есть один нюанс, который стоит помнить — помимо отдачи динамического контента, нужно еще отдавать и статику, так что если выбранное решение этого не умеет , вам понадобится отдельный http-сервер. Если нужен Python powered httpd — возьмите apricot. Он, конечно же, проигрывает по скорости таким серверам как apache/lighttpd/nginx, однако компенсирует гибкостью и простотой.

Twisted

Скорость: 2
Удобство: 5

Twisted Web2 показывает весьма приличную скорость — даже чуть выше чем у Apache+FastCGI. Twisted Web2 выглядит вполне достойным кандидатом для использования для средненагруженных серверах. Скорость отдачи статики, конечно же, не может сравниться с apache/lighttpd/nginx, но зато вам не нужно настраивать отдельный сервер (особенно, если Twisted уже используется как платформа для сетевых сервисов). И еще — это Python, так что вы можете отдавать статику так как вам нужно, например, с хитрой авторизацией.

Итак, пример tac (tac — twisted app config) для запуска WSGI-приложения testapp.app:

from twisted.application import service, strportsfrom twisted.web2 import wsgi, channel, serverfrom testapp import app as wsgi_appapplication = service.Application('Twisted WSGI/Nevow test')wsgi_site = server.Site(wsgi.WSGIResource(wsgi_app))wsgi_httpd = strports.service('tcp:8080', channel.HTTPFactory(wsgi_site))wsgi_httpd.setServiceParent(application)

CherryPy

Скорость: 5+
Удобство: 4

CherryPyWSGIServer — феноменально быстрый WSGI-сервер. Быстрее, чем mod_python! Что интересно, cherrypy.wsgiserver фактически не зависит от остальных компонент CherryPy и доступен в виде отдельного модуля. Запуск производится следующим способом:

from cherrypy import wsgiserverfrom testapp import appwsgi_apps = [('/wsgi', app)]wsgiserver.CherryPyWSGIServer(('', 8080), wsgi_apps).start()

CherryPy как и Twisted умеет отдавать статику (Twisted даже чуть быстрей), но лучше это делать при помощи отдельного сервера. Да, я не оговорился, CherryPy обрабатывает WSGI быстрей, чем отдает статику, причем разница — в два-три раза.

Итоги

Для интранета я бы рекомендовал Twisted Web2 — качественное, “всё включено”, решение. Вам не нужно настраивать отдельный сервер для отдачи статики, да и производительность для pure Python весьма достойна.

Для интернета четкой рекомендации дать сложно, вот от чего можно смело отказываться, так это от apache+mod_fastcgi: ни скорости, ни гибкости настройки. Интересно выглядит новичок CherryPy+nginx (т.е. nginx для отдачи статики и как reverse proxy для CherryPy), но требуются дополнительные исследования этого вопроса — более полное тестирование производительности, решение вопросов массового развертывания: инфраструктура запуска/останова, запускать ли приложения в одном экземпляре CherryPyWSGIServer, или “разводить” на разные порты. Для тех кто хочет “прям сейчас и без дополнительной возни”, но может мириться с более серьезным потреблением памяти, я советую apache+mod_python. Если же приходится действовать в режиме экономии памяти (например, на VDS), то lighttpd/nginx+FastCGI выглядит оптимальным вариантом. Запускать FastCGI-сервер в threaded или preforked, использовать unix socket или tcp — зависит от конкретной ситуации.

Резюме:

  • интранет — Twisted Web2
  • интернет
    • прям сейчас и без дополнительной возни - apache+mod_python
    • оптимально - lighttpd/nginx+FastCGI
    • многообещающе - nginx+CherryPy
Подписаться Комментировать

Комментарии

5.02.2007 0:25 Юревич Юрий

Завидую я вам. Увидеть бы все доклады, которые войдут в RuPy2007

5.02.2007 1:22 Юревич Юрий

Что-то я не понимаю, почему CherryPy может быть таким быстрым… Как ни крути, это все же интерпретируемый код, и как он может работать быстрее чего-то скомпилированного Сишного, не понятно. Есть, где подробней узнать, сравнения почитать?

Еще непонятно, почему mod_python + Apache по скорости поставлен выше FastCGI + lighty/hginx (5 против 4). Вот дядька Каплан-Мосс говорит, что у него другие данные:

5.02.2007 2:33 Юревич Юрий

А где же SCGI? Я сейчас на production установил именно его (патченный 1.12). Дело в том, что под debian x64 пакета fastcgi не существует, а компиляется он криво… вот так и получилось…
И обязательно указывай, какой апач. У меня один 1.34, второй 2.2, если очень попросишь и дашь тулзы, измерю скорость :)

p.s. Сорцы FastCGI не обновлялись уже два года. Поэтому удивляться нечего, что под x64, который поддерживается только с debian 4 (etch), он не компиляется — dependencies устарели.

p.p.s FastCGI может работать через external server и самозапускаться с помощью apache. Об этом у тебя тоже ни слова.

5.02.2007 9:31 Юревич Юрий

Что-то я не понимаю, почему CherryPy может быть таким быстрым… Как ни крути, это все же интерпретируемый код, и как он может работать быстрее чего-то скомпилированного Сишного, не понятно. Есть, где подробней узнать, сравнения почитать?

Типичное заблуждение скомпилированный-интерпретируемый. Так вот, скомпилированный Сишный код httpd работает не напрямую с твои кодом, а через посредника (wsgi-modpy handler в mod_python, flup для fastcgi), и в случае простого кода (как в моем случае), тормозит именно посредник (насчет mod_fastcgi тормозит, думаю, он). CherryPyWSGIServer же работает без посредников, да еще и худо-бедно оптимизирован. Отсюда и выигрыш.

Подробнее узнать — только самому померять. У меня получились (при прочих равных) примерно такие “попугаи”:

apache+mod_python — 710 rps
apache+mod_python raw (приложение для modpy, без WSGI, аналогичное тестовому) — 780 rps
apache+mod_fastcgi — 250 rps
lighttpd/nginx+fastcgi — 620 rps
twisted — 340 rps
cherrypy — 1190 rps

У знатоков сразу возникнут вопросы flup threaded или preforked? apache threaded или preforked? С какими настройками? Так вот, разница между threaded/preforked (у меня) в rps была в единицах rps, округлял я до десятков. Во-вторых, разница между threaded/preforked есть, она ощущается во времени ответа под нагрузкой. Но поскольку определение “самого быстрого” — не цель обзора, какой-либо анализ данных я не делал. Взял как есть, перевел в пятибалльную систему.

Еще непонятно, почему mod_python + Apache по скорости поставлен выше FastCGI + lighty/hginx (5 против 4). Вот дядька Каплан-Мосс говорит, что у него другие данные:

Здесь несколько факторов: во-первых, развертывание Django на modpy происходит за счет собственного modpy handler, во-вторых, у меня очень простое приложение, буквально четыре строки. И меня не покидает ощущение, что будь приложение “потяжелей”, разница между различными методами развертывания (по скорости) будет всё меньше и меньше. А вот разница между различными режимами настройки (threaded/preforked) будет расти.

Опять же, стоит понимать, что статья — не тест скорости, а лишь оценка различных методов развертывания WSGI-приложений. И скорость здесь не цель, а один из учитываемых факторов.

P.S.Почему то к оценке “удобство” никто не придрался :-D

5.02.2007 9:46 Юревич Юрий

А где же SCGI?

Ну будет на рисунке слово FastCGI заменено на SCGI :-) Всё равно, основной тормоз будет в flup. Есть, конечно, надежда, что SCGI в нем реализован чуть быстрее, чем FastCGI, но зато теряется универсальность — nginx, например, не умеет SCGI… Возможно, apache покажет себя лучше. Но это уже не так важно — принципиально расстановка сил не поменяется.

Хотя, конечно, SCGI я упустил из виду.

Я сейчас на production установил именно его (патченный 1.12). Дело в том, что под debian x64 пакета fastcgi не существует, а компиляется он криво… вот так и получилось…
И обязательно указывай, какой апач. У меня один 1.34, второй 2.2, если очень попросишь и дашь тулзы, измерю скорость :)

Конечно. А человек, который работает с lighttpd/nginx столько же подробностей скажет и про “свой” httpd. Я не ставил целью выяснить, что на Apache-2.0.55+FastCGI-1.12buriy5 будет 714,7, а на Apache-2.0.59+FastCGI-1.12 будет 713,3 (ага, с погрешностью измерения +-3 :-D )

Вот если бы ты методику предложил внятную… Я бы с инструментами тебе помог. А так, резон перемерять?

5.02.2007 14:08 Юревич Юрий

Типичное заблуждение скомпилированный-интерпретируемый. Так вот, скомпилированный Сишный код httpd работает не напрямую с твои кодом, а через посредника (wsgi-modpy handler в mod_python, flup для fastcgi), и в случае простого кода (как в моем случае), тормозит именно посредник (насчет mod_fastcgi тормозит, думаю, он).

Звучит логично. Мне думается, что теоретически это обосновывается, грубо говоря, тем, что дополнительный уровень тормозит буквально просто на лишнем копировании объектов туда и сюда.

5.02.2007 21:36 Юревич Юрий

Спасибо за информацию.

Я, правда, пока остановился на apache+mod_fastcgi. :)

mod_python я использовать не хочу, как и дополнительный веб-сервер помимо основного апача. Так вот и получается, что альтернативы — хуже. ;)

5.02.2007 22:01 Юревич Юрий

То, что на этом же apache крутится mod_php совсем не означает, что всё кроме mod_fastcgi хуже.

mod_proxy+CherryPy, IMHO, может тебе подойти.

6.02.2007 0:52 Юревич Юрий

mod_proxy+CherryPy, IMHO, может тебе подойти

Для TurboGears — возможно, но для Pylons думаю вряд ли удастся использовать CherryPy?

6.02.2007 1:05 Юревич Юрий

why not? CherryPyWSGIServer не зависит от других “вишенок” и вполне работает самостоятельно.

hint:

[server:main]
use = egg:PasteScript#cherrypy
host = 127.0.0.1
port = 8000

правда, сам paste.script-1.1 не без греха… так что стоит брать с svn

6.02.2007 12:51 Юревич Юрий

Спасибо большое, записал . А не подскажешь, где почитать про настройку mod_python? Или пример конфига глянуть.

7.02.2007 22:44 Юревич Юрий

TwistedWeb2 очень сырой, но интересный продукт. При работе с ним натолкунлся на несколько багов в коде web2, но с одним из них пришлось провозиться, наверное, пару часов: В цикле в twisted.web2.channel.fastcgi.FastCGIChannelRequest.write пропущено n += 1, из-за этого получается безконечный цикл в результате которого вылетает весь процесс Twisted.
Pythy, можешь отписать разработчикам, а то я с анг. не дружу :-) ?

7.02.2007 23:41 Юревич Юрий

Напишу. Но баг без юнит-теста фактически игнорируется. Даже с юнит-тестом может висеть несколько месяцев, пусть даже серьезный и легкоисправляемый. Записал в TODO, как напишу юнит-тест — добавлю в trac.

8.02.2007 8:21 Юревич Юрий

Я думаю ты покоду уже понял что проблема возникает при отправке пакета размером больше 64Kb. Реализация FastCGI Twisted разбивает большие пакеты на 64Kb, а без счетчика n, дело дальше первых 64Kb не уходит.
К тому же, похоже, FastCGI Twisted может отдавать пакеты только целиком, большой недостаток.

9.02.2007 18:09 Юревич Юрий

получается что CherryPy субъективно лучше Flup?

9.02.2007 20:32 Юревич Юрий

Чем мерять лучше/хуже? Быстрее — похоже, что да. Но CherryPy отдает уже готовый http на выходе, а flup — fastcgi/scgi/ajp. Опять же, flup умеет работать и как preforked, и как threaded. CherryPy — только threaded. Так что у каждого своя ниша :-)

10.02.2007 23:45 Юревич Юрий

Thank you for a great analysis!

To answer Иван Сагалаев, CherryPy can be faster because:

1. An HTTP server spends more time waiting on socket I/O than almost anything else,
2. I worked hard to minimize Python function calls, which are usually the slowest part of any Python code, and
3. I worked hard to minimize the number of bytecodes used to accomplish each logical task; in other words, most of the work is done by Python in C instead of by CherryPy in Python. This is easier to accomplish when you use mostly native types, and the WSGI specification helps that by mandating the use of native types.

21.02.2007 8:59 Юревич Юрий

Добавление: говорят , что вышел mod_wsgi . Автор уверяет, что mod_wsgi быстрее , чем mod_python+wsgi_handler Я правда не нашел где mod_wsgi можно раздобыть.

14.04.2007 17:35 Юревич Юрий

раздобыть mod_wsgi можно на гугле.коде

реально быстрее всех.

Форма комментирования для «Развертываем WSGI приложение...»

Обязательное поле. Не больше 30 символов.

Обязательное поле

Пожалуйста, введите символы, которые вы видите на изображении

13.09.2007 11:52 astoon
Появилась ли какая-нибудь новая информация относительно mod_wsgi ? Хотелось бы сравнить nginx+CherryPyWSGIServer и Apache+mod_wsgi …
20.09.2007 20:05 Юревич Юрий
Про CherryPy я обязательно расскажу в контексте PasteScript/PasteDeploy. Про mod_wsgi постараюсь, но не обещаю.