WSGI серверы кратко

За последние несколько дней обнаружилась (для меня) уйма интересных WSGI серверов. Я уже довольно давно использую (естественно, для Джанги) Apache mod_wsgi для продакшена и CherryPy’евский WSGI-сервер для разработки. Но недавно обнаруженный fapws2 заинтересовал меня и я наконец-то собрался его скомпилировать (основная загвоздка была в необходимости обновления libevent), потом моё внимание обратили на cogen, и вот сегодня уже — на spawning.

И я решил взять и погонять всех сразу apachebenchmark’ом. Тестировал просто — “ab -c 50 -n 500”, ничего особенно не тюнинговал, тестирование на абсолютную объективность и точность не претендует. Всех конкурентов запускал так, чтоб они могли скушать оба ядра процессора (иначе это было бы не спортивно со стороны Spawning’а, который запускает несколько процессов и может использовать все ядра).

Итак, протестированные герои:

  • Apache2 mod_wsgi 2.0 daemon mode
  • Twisted
  • CherryPy
  • Spawning
  • Cogen
  • Fapws2

Каждый из них работает как веб-сервер для джанги с byteflow (в качестве БД использован дамп этого самого блога). Каждый запущен в двух экземплярах, сидит за nginx’ом, исключая Spawning, у которого запущено два процесса и сидит он сам.

Вот результаты.

Лидеры — fapws2 и совсем немножечко медленее — cogen. На погрешность я бы не списывал, потому что два дня назад такая же ситуация была, и при каких условиях ни тестирую — fapws2 самую чуточку быстрее. Что характерно для лидеров — у них даже самые долгие запросы в 2 секунды укладываются, не то, что у других.

Аутсайдеры — CherryPy со своими 5,5 секундами на самом длинном запросе и Spawning, который так хвалили, со своими 4 секундами на длинный запрос и 80 Мб скушанного ОЗУ — это против 32 у фапвса и 40 у когена!

Про Твистед и Апач можно сказать, что Твистед в принципе немного быстрее, но смысла никакого использовать всё равно нету. Если хочется скорости — есть fapws2 или cogen, если хочется огромного количества фич — есть Апач, который почти всегда есть… В общем, не вижу смысла.

Между cogen и fapws2 тоже можно выбрать: cogen написан на чистом питоне (он использует py-epoll), а fapws2, использующий libevent, имеет внутри кусок C, и потому его необходимо компилировать (к примеру, для винды это — чистейшее мучение, на винде я бы использовал коген). Но fapws2 явно несколько меньше ОЗУ хочет, что приятно в стеснённых условиях VPS, ну и немножечко, но быстрее. ;-)

Так что буду я, похоже, на fapws2 переползать. Тем более у моего VPS целых 4 ядра, зря пропадают с апачем только… :-)

Pingbacks: 1

nginx vs CherryPy
А еще есть Fapws, который тоже довольно быстрый.http://piranha.org.ua/blog/2008/08/28/running-django/http://piranha.org.ua/blog/2008/07/31/wsgi-servers-short/
softwaremaniacs.org, 20:56 (after 272 days)

Comments: 51 (already: 15) Comment post

Сашко, а под виндой cogen ты и не запустишь, так как epoll — чисто линуксовый механизм;) и под freebsd тож, там kqueue

/me пока-что остановился на cogen, траблов не наблюдаю, да и код иногда интересно покурить =) а c С тяжко как-то

slav0nic , 16:25

Сашко, а под виндой cogen ты и не запустишь, так как epoll — чисто линуксовый механизм;)

А, я что-то увлёкся. :) Но cogen для винды юзает iocp просто, вот и всё. А во фре — таки kqueue.

Alexander Solovyov , 16:40

А под nginxовским mod_wsgi не пробывал?

Salvator , 17:08

Нет, руки не дошли. К сожалению, его для джанги в продакшене особо не поиспользуешь — он же блокирует при случае весь процесс nginx’а, а это не очень весело.

Alexander Solovyov , 17:29

Вот тут не понял:

Тем более у моего VPS целых 4 ядра, зря пропадают с апачем только… :-)

Почему пропадают-то? Раз он плодит отдельные процессы, то ОС нормально раскидает их на все доступные ядра.

Иван Сагалаев , 21:37

Не раскидает, потому что я юзаю фичу mod_wsgi — daemon mode. При этом вся джанга выделяется в один отдельный процесс (ты если в результаты посмотришь, можешь их увидеть запущенные под моим пользователем, а не под апачевским), и, соответственно, юзает только одно ядро. Зато апачевские детки маленькие получаются, без питона инсайд. :)

Alexander Solovyov , 23:57

ну привет

WSGIDaemonProcess ipidevtrac user=ipidevtrac group=nogroup processes=4 threads=16

?

на самом деле при daemon режиме mod_wsgi форкается как раз вместе с питоном внутри. Зато не грузится и парсистся каждый раз, а просто отправляет wsgi на обработку существующему форку. При этом может ещё и потоки создавать (питоновские, т.е. аккурат в питоновском потоке вызывает application(env, get_response)) чтобы долгие запросы не блочили весь форк.

only worker_mpm конечно.

Vadim Fint , 15:44 (after 2 days)

Чорт, а слона-то я и не заметил. Я как-то абсолютно пропустил processes. :\

В общем, попробовал с processes=4 threads=16, выходит 25 запросов в секунду (24.8-24.97). Но при этом возникает 4 процесса, каждый по 30 мегов. : processes=2 threads=4 даёт результаты получше (25.5), и эти два процесса едят мегов по 22-23, что уже явно лучше.

В общем, апач — не аутсайдер, но пока cogen и fapws2 — быстрее. А насколько апач быстр по сравнению с ними у тебя? Не хочешь запустить ab у себя?

Не кажется мне, что он вот так возьмёт и ни с чего обгонит.

Alexander Solovyov , 11:16 (after 3 days)

конечно просто так он не обгонит — с чего бы это :). Апач надо очень грамотно собирать, не включая огромную кучу ненужных модулей. Часто я делаю лишь autoindex alias dir logio log_config rewrite so mime authz_host info env setenvif negotiation — мне этого за глаза хватает (в дефолтной поставке модулей раза в три поболе). Только статическая линковка. Ещё к этому всему надо грамтно настроить worker mpm — по дефолту там очень щадящие настройки.

Если настроение будет — прогоню тест, похожий на твой.

Vadim Fint , 10:39 (after 4 days)

Я в курсе про собирание без лишних модулей, но возиться мне лень. :) Проще разобраться с runit’ом и использовать fapws2.

Ну в общем если сделаешь, то это будет хорошо. :)

Alexander Solovyov , 11:05 (after 4 days)

Интересно, что в cogen написано: “HTTP handling code taken from the CherryPy WSGI server”. Выигрыш, таким образом, получается именно в несколько другой организации обработки запросов.

Андрей Стромнов , 08:09 (after 1 day)

Да, прикольная тема. Я ещё хочу yield потестировать, но пока с ним траблы есть, вот с автором переписываюсь…

Alexander Solovyov , 12:39 (after 1 day)

Запости пожалуйста результаты.

Eugene Lazutkin , 18:58 (after 18 days)

Что то я не пойму зачем ставить nginx перед апачем? apache + worker_mpm + mod_wsgi2 рвёт на моей машинке любые варианты с не-на-си написанными серверами.

Vadim Fint , 15:46 (after 2 days)

У меня вот прямо сейчас fapws2 в среднем 1800 мс на запрос, апач — 1980. У питоновского когена — 1810, при этом оно не может загрузить проц на 100% — колеблется 97-98 постоянно.

Alexander Solovyov , 11:24 (after 3 days)

1.8сек на запрос?! При столь тяжелой бизнес-логике производительность сервера толком ничего не меняет (:

Vadim Fint , 10:40 (after 4 days)

Ога, я сча решил плюнуть и протестировать их на тупом приложении, которое делает return HttpResponse("hello world"). Но пока нету настроения написать и опубликовать что-то, а вкратце — fapws2 всех, кроме когена, в два раза обгоняет. Коген где-то на 20% медленнее.

Alexander Solovyov , 11:04 (after 4 days)

Александр, а как получилось собрать libevent? Я на убунте пробовал собрать — что-то не вышло.

Dyadya Zed , 19:45 (after 7 days)

Я соврал. Fapws2 и mod_wsgi рулят, cogen отстаёт в 2 раза, остальные ещё медленнее.

Alexander Solovyov , 22:59 (after 7 days)

я много копался в исходнике mod_wsgi.c. Там просто нечему тормозить (:

Причём, все же, и fapws2 с его libevent он, скорее всего, может обогнать при правильной настройке. Я уже запланировал сей бенчмарк на выходные ))

Vadim Fint , 01:42 (after 8 days)

Давай-давай, у меня на этом приложении вышло 3900 запросов для fapws2 и 3500 для mod_wsgi при такой настройке:

WSGIScriptAlias / /home/piranha/dev/web/perftest/django.wsgi

:D С демон-модом вышло 3100.

Alexander Solovyov , 09:37 (after 8 days)

Как поставить fapws2 под убунтой? Нашел libevent в исходниках, скомпилировал, но не заработало.

Есть какой-то более быстрый путь?

Dyadya Zed , 21:43 (after 7 days)

Ну, у меня libevent 1.4.6 скомпилировался очень просто. ./configure && make && make install. А что значит “не заработало”?

Alexander Solovyov , 22:37 (after 7 days)

1.4.3 пока лучше — в 1.4.4..1.4.6 народ ещё не уверен.

Vadim Fint , 01:43 (after 8 days)

вот ошибка.. Поставил libevent, fapws2 не ставится.

root@z:/usr/local/dl/fapws2-0.3# python setup.py install We will use the followinf libevent sources: ./libevent

running install running build running build_py running build_ext building ‘_evhttp’ extension gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fPIC -I./libevent -I/usr/include/python2.5 -c fapws2/_evhttp.c -o build/temp.linux-x86_64-2.5/fapws2/_evhttp.o fapws2/_evhttp.c:24:27: error: http-internal.h: No such file or directory fapws2/_evhttp.c:25:17: error: log.h: No such file or directory fapws2/_evhttp.c: In function ‘send_error’: fapws2/_evhttp.c:35: warning: implicit declaration of function ‘evhttp_response_code’ fapws2/_evhttp.c:38: warning: implicit declaration of function ‘evhttp_send_page’ fapws2/_evhttp.c: In function ‘evhttp_handle_request’:

Dyadya Zed , 17:39 (after 9 days)

Ну собственно оно хочет, чтоб сорцы либевента лежали в ./libevent. Так что либо переложи, либо измени путь. :)

Alexander Solovyov , 12:05 (after 10 days)

Спасибо, все получилось. По дороге пришлось в setup.py исправить путь к libevent.so с /usr/lib/libevent.so на /usr/local/lib/libevent.so

Dyadya Zed , 10:26 (after 11 days)

Попробовал запустить byteflow через fapws2, взял конфиг из примеров fapws2 для джанги. Ругаецца, говорит,

We have an error in the python code associated to the url :/ Traceback (most recent call last): File “run.py”, line 25, in generic res=django_handler.handler(environ, start_response) File “/usr/lib/python2.5/site-packages/fapws2/contrib/django_handler.py”, line 7, in handler for key,val in res.headers.items(): AttributeError: ‘HttpResponseRedirect’ object has no attribute ‘headers’

Есть предложение добавить готовый конфиг для fapws2 в byteflow..

Dyadya Zed , 16:56 (after 11 days)

Аа, я забыл автору патч отправить. Замени res.header.items на res.items пока, а я тут с автором всё дообсуждаю.

Alexander Solovyov , 20:36 (after 11 days)

Поменял, в ответ получил такое:

We have an error in the python code associated to the url :/ Traceback (most recent call last): File “run.py”, line 21, in generic res=django_handler.handler(environ, start_response) File “/usr/lib/python2.5/site-packages/fapws2/contrib/django_handler.py”, line 5, in handler res=djhand(environ, start_response) File “/usr/lib/python2.5/site-packages/django/core/handlers/wsgi.py”, line 216, in __call__ response = self.get_response(request) File “/usr/lib/python2.5/site-packages/django/core/handlers/base.py”, line 67, in get_response response = middleware_method(request) File “/home/zam/byteflow/apps/middleware/url.py”, line 76, in process_request if not self._urlExists(old_url[1]): File “/home/zam/byteflow/apps/middleware/url.py”, line 108, in _urlExists if resolve(path) is None: raise Http404 # None?!? You mean 404… File “/usr/lib/python2.5/site-packages/django/core/urlresolvers.py”, line 299, in resolve return get_resolver(urlconf).resolve(path) File “/usr/lib/python2.5/site-packages/django/core/urlresolvers.py”, line 238, in resolve for pattern in self.urlconf_module.urlpatterns: File “/usr/lib/python2.5/site-packages/django/core/urlresolvers.py”, line 262, in _get_urlconf_module raise ImproperlyConfigured, “Error while importing URLconf %r: %s” % (self.urlconf_name, e) django.core.exceptions.ImproperlyConfigured: Error while importing URLconf ‘urls’: Signal receivers must accept keyword arguments (**kwargs).

./start: line 3: 19900 Segmentation fault (core dumped) python run.py

Dyadya Zed , 01:18 (after 12 days)

Как можно вместе запустить fapws2 и nginx или fapws2 и lighthttpd? Только через fastcgi или еще можно как-то?

Byteflow не запускается под fapws2. Выдает ошибку:

We have an error in the python code associated to the url :/ Traceback (most recent call last): File “run.py”, line 21, in generic res=django_handler.handler(environ, start_response) File “/usr/lib/python2.5/site-packages/fapws2/contrib/django_handler.py”, line 5, in handler res=djhand(environ, start_response) File “/usr/lib/python2.5/site-packages/django/core/handlers/wsgi.py”, line 216, in call response = self.get_response(request) File “/usr/lib/python2.5/site-packages/django/core/handlers/base.py”, line 67, in get_response response = middleware_method(request) File “/home/z/byteflow/apps/middleware/url.py”, line 76, in process_request if not self._urlExists(old_url[1]): File “/home/z/byteflow/apps/middleware/url.py”, line 108, in urlExists if resolve(path) is None: raise Http404 # None?!? You mean 404… File “/usr/lib/python2.5/site-packages/django/core/urlresolvers.py”, line 299, in resolve return getresolver(urlconf).resolve(path) File “/usr/lib/python2.5/site-packages/django/core/urlresolvers.py”, line 238, in resolve for pattern in self.urlconf_module.urlpatterns: File “/usr/lib/python2.5/site-packages/django/core/urlresolvers.py”, line 262, in geturlconf_module raise ImproperlyConfigured, “Error while importing URLconf %r: %s” % (self.urlconf_name, e) django.core.exceptions.ImproperlyConfigured: Error while importing URLconf ‘urls’: Signal receivers must accept keyword arguments (kwargs).

Dyadya Zed , 23:09 (after 17 days)

Как можно вместе запустить fapws2 и nginx или fapws2 и lighthttpd? Только через fastcgi или еще можно как-то?

FastCGI тут при чём? :) Там HTTP. :-)

django.core.exceptions.ImproperlyConfigured: Error while importing URLconf ‘urls’: Signal receivers must accept keyword arguments (kwargs).

А что, runserver работает? ;) Обновление должно помочь.

Alexander Solovyov , 15:32 (after 23 days)

Да ни при чём тут и fapws. В 1.0alpha1 отрефакторили диспатчер сигналов в джанге. Они стали почти в два раза шустрее, но api сменилось. Собсна, это глюк самого byteflow.

Vadim Fint , 15:41 (after 23 days)

А где я сказал, что фапвс тут при чём? Ты невнимательно прочёл. :) Вообще я хотел сказать, что я это уже фиксил, вот и всё.

Alexander Solovyov , 15:43 (after 23 days)

а ты имел ввиду “обновите byteflow”. А мне почудилось “обновите fapws” oO

Vadim Fint , 15:44 (after 23 days)

В общем, обновлял я byteflow, не помогает. Под mod_wsgi отлично работает, а под fapws2 — фиг. Где-то недофиксил.

FastCGI тут при чём? :) Там HTTP. :-)

Да, я уже разобрался, что к чему.

Dyadya Zed , 00:34 (after 24 days)

да, runserver работает отлично. А fapws2 — фиг. Обновлял byteflow несколько раз, думал не проходит обновление.

dyadyazed , 01:19 (after 24 days)

в urls.py что то вроде

import django; import sys; sys.stderr.write(''.join(django.__file__, '\n')); sys.stderr.flush

вас скорее всего удивит ;)

Vadim Fint , 03:07 (after 24 days)

Точно что-то с sys.path — у меня последняя джанга (ревизия “russellm: Fixed #8475 …”) работает с последним byteflow без всяких проблем. Fapws2 в том числе.

Alexander Solovyov , 11:25 (after 24 days)

А как ты сделал, чтобы он модуль находил? У меня byteflow движек работает через fapws2, а мой тестовый проект выдает ошибку

Traceback (most recent call last):

File “./run.py”, line 21, in generic res=django_handler.handler(environ, start_response) File “/usr/lib/python2.5/site-packages/fapws2/contrib/django_handler.py”, line 8, in handler res=djhand(environ, start_response) File “/var/lib/python-support/python2.5/django/core/handlers/wsgi.py”, line 239, in __call__ response = self.get_response(request) File “/var/lib/python-support/python2.5/django/core/handlers/base.py”, line 128, in get_response return self.handle_uncaught_exception(request, resolver, exc_info) File “/var/lib/python-support/python2.5/django/core/handlers/base.py”, line 148, in handle_uncaught_exception return debug.technical_500_response(request, *exc_info) File “/var/lib/python-support/python2.5/django/views/debug.py”, line 39, in technical_500_response html = reporter.get_traceback_html() File “/var/lib/python-support/python2.5/django/views/debug.py”, line 113, in get_traceback_html return t.render(c) File “/var/lib/python-support/python2.5/django/template/__init__.py”, line 176, in render return self.nodelist.render(context) File “/var/lib/python-support/python2.5/django/template/__init__.py”, line 768, in render bits.append(self.render_node(node, context)) File “/var/lib/python-support/python2.5/django/template/debug.py”, line 81, in render_node raise wrapped django.template.TemplateSyntaxError: Caught an exception while rendering: No module named drone

Drone , 21:09 (after 198 days)

Ну ты бы показал, как запускаешь.

P.S. 4 пробела перед кодом — и он нормально отобразится. :)

Alexander Solovyov , 22:29 (after 198 days)

есть два проекта. Один на движке byteflow, другой сделан по мотивам djangobook.

python manage.py runserver

тестовый сервер поднимает оба.

fapws2 прекрасно обслуживает byteflow, а второй сайт выдает ошибку при обращении к любой странице.

run.py взял из примеров fapws2 для django.

Посмотрел исходники byteflow

PROJECT_ROOT = os.path.normpath(os.path.dirname(__file__))
sys.path.insert(0, os.path.join(PROJECT_ROOT, 'apps'))
sys.path.insert(0, os.path.join(PROJECT_ROOT, 'compat'))

и на основании PROJECT_ROOT определяешь все пути.

Django отладочный сервер и сторонние сервера по-разному понимают пути поиска кода. Это из-за того, что ты определил PROJECT_ROOT, можешь спокойно писать в INSTALLED_APPS название приложений и оба сервера его схватывают?

Drone , 11:06 (after 199 days)

Ну не из-за того, что я его определил, а из-за того, что я его вставил в sys.path (точнее, я туда вставил apps).

А у тебя не запускается, судя по всему, потому, что все импорты у тебя рассчитаны на то, что в sys.path попадет директория, содержащая твой проект, а не директория самого проекта — т.е. все импорты должны быть from project.app.models import Model. Переделай скрипт для запуска, чтоб он это учитывал.

Или второй вариант: переделать проект так, как я. :-)

Alexander Solovyov , 12:59 (after 199 days)

ps aux|grep run

18538 26682 2.2 0.7 22600 15744 ? S Aug02 453:04 python run.py

Cogen жрет процессор как фиг знает что, сайт из 1 приложения, который отдает 7 страниц. Это ужос просто, при запуске через FastCgi сайт сожрет столько процессора только через 2-3 года.

или лыжи не едут или я..

L0rda , 18:10 (after 16 days)

О да, я тоже обращал внимание, но не то, чтоб очень очень. Похоже, лыжи такие. :\

Alexander Solovyov , 20:15 (after 16 days)

Люди, а поделитесь кто-нибудь fawps2, а то издох сайт www.opensource4you.com

Pavel , 15:05 (after 33 days)

Фу ты, не туда написал коммент.

Pavel , 15:06 (after 33 days)

йей! спасибо )

Pavel , 17:51 (after 33 days)

www.opensource4you.com в дауне

http://my.piranha.org.ua/fapws2.tar.bz2 ошибка 404

bergamot , 08:20 (after 91 day)

Comment form for «WSGI серверы кратко»

Required. 30 chars of fewer.

Required.

Comment post