FastCGI, mod_perl и прочие

С января 2001 года сфера моей профессиональной деятельности тесно связна с созданием web-сервисов, в основном аналитических.

Одной из технологий, используемых в работе, является mod_perl. FastCGI не использовался ни разу. Почему? Как часто отвечают: ``по историческим причинам''. А вот сейчас решил посмотреть в сторону FastCGI подробней. Специфика нового проекта подразумевает наличие frontend'да, например, nignx. ``Нет, проблем'', - говорю сам себе: ``frontend'ом будет nignx, а backend'ом - Apache''. Но не так все просто.

В рассылке по nignx, до того как было все разложено по полочкам в различных документациях и заметках, часто задавались вопросы по использованию PHP в режиме FastCGI. То есть люди массово избавлялись от Apache mod_php, переходя не FastCGI. При этом, что в тот момент, не знаю как сейчас, патч php-fpm (http://php-fpm.anight.org/) не был включен в официальные исходники, так что многие пользовалось spawn-fcgi от lighttpd.

Что это? Дань моде или нечто большее? Тем не-менее этот процесс подтолкнул меня посмотреть подробней, какие есть у FastCGI преимущества. Да, я знаю, mod_perl и mod_php координально отличаются в области загрузки кода, но будем считать, что php акселераторы работают хорошо и это не является причиной перехода с mod_php на FastCGI.

Известно, что процессы Apache с mod_perl очень прожорливы на память. Может с FastCGI ее требуется меньше? Теоретически разница не должна быть слишком большой: ведь под mod_perl можно все используемые модули загрузить до начала создания дочерних процессов.

Стоп! Если мне не изменяет память, то mod_php так не может! Наверно это и есть причина перехода с mod_php на FastCGI. Хм, но ведь и spawn-fcgi в этом вопросе не помощник...

Тем не-менее с FastCGI решил разобраться до конца. Прописал в конфиге nignx какой порт слушать и занялся perl. Для perl существует три основных модуля FCGI, FCGI::ProcManager и FCGI::Spawn. (о FCGI::Async и других разговор не ведем). Первый является основным, второй - это менеджер процессов. Третий является надстройкой над первым двумя и предназначен для запуска скриптов через require, то есть является в некотором смысле аналогом Apache::PerlRun, поэтому мы его не рассматриваем.

Модули FCGI, FCGI::ProcManager являются очень ``зрелыми'' и все приведенные примеры предназначены для запуска FastCGI из-под ``пускалок'', а не как самостоятельные, поэтому все примеры слегка модифицируем. Перед основным циклом открываем сокет, который будет слушать nignx:

use FCGI;
use CGI;
my $socket = FCGI::OpenSocket(":9000", 5);
my $request = FCGI::Request(\*STDIN, \*STDOUT, \*STDERR, \%ENV, $socket);
my $count = 0;
while($request->Accept() >= 0) {
      $count++;
      print <<TEXT;
Content-Type: text/html
<h1>hello</h1>
$count
<hr>
TEXT
     print "$_ = $ENV{$_}<br>\n" foreach sort keys %ENV;
     print "<hr>\n";
     my $query = CGI->new();
     print "$_ = ", $query->param($_), "<br>\n" foreach sort $query->param();
 }
FCGI::CloseSocket($socket);

Но это пример имеет большой недостаток: всего лишь один процесс FastCGI. На помощь приходит модуль FCGI::ProcManager, который создает до основного цикла заданное количество потомков, выполняющих всю полезную работу по обработке запроса:

use FCGI;
use FCGI::ProcManager;
use CGI;
my $proc_manager = FCGI::ProcManager->new({ n_processes => 10 });
my $socket = FCGI::OpenSocket(":9000", 5);
my $request = FCGI::Request(\*STDIN, \*STDOUT, \*STDERR, \%ENV, $socket);
$proc_manager->pm_manage();
my $count = 0;
while($request->Accept() >= 0) {
$count++;
print <<TEXT;
Content-Type: text/html
<h1>hello</h1>
$count
<hr>
TEXT
    print "$_ = $ENV{$_}<br>\n" foreach sort keys %ENV;
    print "<hr>\n";
    my $query = CGI->new();
    print "$_ = ", $query->param($_), "<br>\n" foreach sort $query->param();
}
FCGI::CloseSocket($socket);

Кстати, если кто знает ответе: в линуксе до сих про плохо с большим количеством прослушивающих один сокет процессов и проходится перед аccept делать монопольную блокировку файла ``регулировщика''?

Вернемся к основной теме. В примерах использования FCGI::ProcManager используется модуль CGI::Fast, используя которых вышеприведенный вариант можно привести к следующему виду:

BEGIN {
   $ENV{FCGI_SOCKET_PATH} = ":9000";
   $ENV{FCGI_LISTEN_QUEUE} = 5;
}
use CGI::Fast;
use FCGI::ProcManager;
my $proc_manager = FCGI::ProcManager->new({ n_processes => 10 });
$proc_manager->pm_manage();
my $count = 0;
while(my $query = CGI::Fast->new()) {
    $count++;
    print <<TEXT;
Content-Type: text/html
<h1>hello</h1>
$count
<hr>
TEXT
    print "$_ = $ENV{$_}<br>\n" foreach sort keys %ENV;
    print "<hr>\n";
    print "$_ = ", $query->param($_), "<br>\n" foreach sort $query->param();
}

Теперь рассмотрим фрейморки Catalyst и CGI::Application.

Для Catalyst создаем новый проект
catalyst.pl MyApp

и запускаем

myapp_fastcgi.pl -listen=localhost:9000 -nproc=10

Все просто. А вот с CGI::Application немного сложней. Модуль CGI::Application::FastCGI, как оказалось, предназначен для работы из-под ``пускалки'', поэтому используем не его, а непосредственно FCGI::ProcManager.

BEGIN {
    $ENV{FCGI_SOCKET_PATH} = ":9000";
    $ENV{FCGI_LISTEN_QUEUE} = 5;
}
use WebApp;
use CGI::Fast;
use FCGI::ProcManager;
my $proc_manager = FCGI::ProcManager->new({ n_processes => 10 });
$proc_manager->pm_manage();
while(my $query = CGI::Fast->new()) {
    my $app = WebApp->new(QUERY => $query);
    $app->run();
}

Ну вот, с запуском разобрались, теперь посмотрим на использование памяти в реальных условиях. Как и ожидалось выигрыша по памяти почти нет, разумеется, когда Apache собран без излишеств.

Так что за nignx можно смело ставить либо mod_perl, либо FastCGI. Это когда один скрипт. А когда скриптов несколько и они используют множество общих модулей, то выигрыш варианта с mod_perl очевиден.

Ну вот пожалуй и все.

P.S. Если я где-то ошибся в рассуждения, просьба поправить.

20 декабря 2007, Николай.

Оценил:)

Оценил:)

Хороший черновик, все бы такие были:) Вопрос - а как можно правильно мониторить память в задачах с использованием mod_perl(2)?

Спасибо за статью

Почерпнул немного новой информации

Да mod_perl наше фсе :)

и еще
20 декабря 2007, - будущеее :)

Быстренько

Быстренько ссылки тырят ? http://www.realcoding.net/article/view/4749
------------------
use Source();

Причины

Причины перехода с mod_ на fcgi банальны:
1) Под апачем suexec по понятным причинам работает только для cgi/fcgi. В результате переезд на fastcgi позволяет добится появление слоя безопаности характерного для cgi (выполнение скриптов под uid/gid вхоста, а не под uid/gid апача) и при этом иметь производительность не хуже чем на mod_, по крайней мере все результаты тестов, которые я видел, дают разницу между mod_ и fcgi в пределах прогрешности измерения. На самом деле есть различные примочки и для реализации подобного на mod_ (ничего навскидку кроме suphp в столь ранний час не приходит) но как-то они не вызывают доверия в продакшн плане, впрочем могу и ошибаться в последний раз интересовался подобными костылями года полтора назад.

2) Плюс имеем несколько большую изолированность интерпретатора и выполняемого скрипта от процессов вебсервера - что дает несколько большую стабильность решения. Плюс меньшее влияние утечек памяти в скриптах на процессы веб сервера.

3) Масштабируемость с помощью какого угодно количества внешних фастги серверов на одной или нескольких отдельных выделенных машинах (и балансировка между ними с помощью того же nginx например)

Теперь по поводу статьи:
1) Какой бы не был по заявленному функционалу патч php-fm это все же ОГРОМНЫЙ патч и довольно небрежный местами, особенно для пхп4. По сути он может пригодится в одном случае - для повышения управляемости fcgi серверов при работе с веб серверами типа nginx/lighttpd, которые не имею встроенных мендежеров/"пускалок". Т.е. советовать кому-то его надо с обязательными оговорками. Тем более, что для апача можно и должно и нужно использовать mod_fastcgi/mod_fcgid(этот не рекомендую для использования во freebsd jail из-за шаред мемори) дающие огромные возможности по управлению, в частности они позволяют использовать такую забавную вещь как динамические fcgi сервера.
Т.е. на мой скромный взгляд в результате под nginx/lighttpd - предпочтительнее использовать все же spawn-fcgi как более универсальное и более объяснимое решение.

2) FCGI::ProcManager имеет одну припаршивейшую недодумку - проверку на существования родителя в цикле менеджера процессов, не надо никому объяснять что это приводит к невозможности демонизации рядом ПО, а также рядом подземных стуков при демонизации с помощью ПО которому все же удается это сделать. Да и на самом деле держать в памяти еще однин процесс, который следит за процессом менеджера fcgi, который в свою очередь следит на чайлдами fcgi - так и до бреда не далеко.
Кстати, пара активных человек пытается это всеобще поправить, если память мне не изменяет с января 07 года, на всяких хранлищах их отсылают к автору модуля, который сгинул по всем признакам еще года 3 назад.

3) Вы абсолютно зря отступили от примера для FCGI::ProcManager в мане, наличие $proc_manager->pm_pre_dispatch(); и $proc_manager->pm_post_dispatch(); в цикле обработки запроса, очень желательно, в частности при отсутствии последнего при падении любого из 10 чайлдов, новый чайлд менеджер просто не создаст, ну и т.д.

4) FCGI::Spawn отнюдь не аналог Apache::Perlrun это видно даже по документации. Это нечто среднее, предназначенное для работы с несколькими скриптами в пределах одного проекта, предварительная загрузка модулей и т.д. Ппример в мане для bugzilla.
При этом имеет следующие ошибки/недоделки:
- отсутствует pm_post_dispatch(); см. п. 2
- если вы забыли предварительно подгрузить какой-либо модуль, то вот это delete $main::{ $_ }, приведет к чудесным и совершенно спорадическим undefined subroutine.
- require больше предназначена для выполнения модулей поэтому ненулевая вероятность схлопотать did not return true value, кстати прямой print в броузер гламура тоже не добавляет.
- любой exit в скрипте приведет к завершению чайлда, если учесть то что я написал в первом подпункте, то по окончанию чайлдов эффект будет вполне предсказуемый... Это даже не говоря о том что в таком варианте теряется минимальный смысл использования fcgi.
ну и т.д.
Если резюмировать - я слабо представляю как этот модуль мог-может работать в принципе сколь нибудь предсказуемо.
Все вышеизложенное кстати я для себя исправил, также добавил некоторую фичастость - встроенную демонизацию опциональную, проверку на существование файла, перезапуск чайлдов по указанному количеству запросов и т.д.
После полного тестирования отправляю патч автору - так что может в скорости будет верия 0.2 :)

5) Мне кажется основная ценность данного способа выполнения perl скриптов совсем не в том что их можно быстрее немного выполнять из-под бэкэнда в вашем (и моем отчасти случае). А в том что для ряда отдельных проектов на перле вроде nagios/bugzilla у меня awstats еще клиентский например можно вообще отказаться от тяжелого бэкэнда, и выполнять перловые скрипты сразу на nginx, тем более что другим способом выполнять их на нем невозможно :)

Спасибо за

Спасибо за ценные замечания!

Они помогут, если буду более плотно заниматься FastCGI,
сэкономить время, так как настолько подробной, как вы, я не смотрел модуль
FCGI::ProcManager, да и FastCGI в реальный проектах не использовал,
только mod_perl.

Про "FCGI::Spawn отнюдь не аналог Apache::PerlRun".
Недавно кому-то говорил, что "аналогичный" и "подобный" разные по смыслы слова,
а тут сам их смешал. :-).

Кстати, по поводу suexec.
Вы не смотрели http://dklab.ru/lib/dklab_apache/ ?
Там, кажется, есть движение в этом направлении, по крайней мере для mod_php.
Сам не смотрел, так как использовать suexec не было необходимости.

Вопрос.
А почему Apache - это тяжелый бекенд?
Если он собран по минимуму с mod_perl, разве он так тяжел.
Конечно есть варианты, когда Apache собирают одновременно
и с mod_perl, и с mod_php, и с pod_ssl.
Но ведь можно для каждой задачи свой Apache, а впереди тоже
nginx, разруливающий запросы.

Почему apache "тяжелый"

Уважаемый nick!

Чтобы понять, почему Apache+mod_perl является "тяжелым" достаточно на рабочем сервере воспользоваться ps. Размер каждого процесса apache в лучшем случае 15 Мб (обычно раза в 3-4 больше).

Использовать для каждой задачи свой apache можно (я так и делаю, когда необходимо обеспечить изолированность скриптов друг от друга), но выигрыш по занимаемой памяти возможен только в случае очень точной настройки Apache (количество процессов, количество запросов на один процесс и т.п.).

А какже COW, а

А какже COW, а какже разделяемая память?!!!
На память в mod_perl я не жалуюсь.

Пояснения

Вопрос не в том, жалуетесь Вы, или нет. Вопрос: сколько одновременно сервер сможет обрабатывать запросов без использования файла подкачки. Про COW и разделяемую память ничего не скажу - этим вопросом не владею, но из воспоминаний конференции fido7.ru.perl там не все так хорошо, как хотелось бы. Так вот, 500Мб хватит в лучшем случае на 500/15 = 33 одновременных обращения. Apache без mod_perl, скажем, 1.8 Мб., или 500/1.8 = 277 одновременных обращений. Теперь зададимся первоначальным вопросом: учитывая цифры 277 и 33 можно сказать, что apache+mod_perl - "тяжелый" бэкенд?
Если у Вас с mod_perl все гораздо лучше, пожалуйста, сообщите свои цифры, для обсуждения.

Предзагрузка

Сравнивать Apache+mod_perl с просто Apache - это очень некорректно. Надо сравнивать приложение под Apache+mod_perl с приложением FastCGI, но и то в одинаковых условиях. А именно настроить mod_perl так, чтобы загрузка perl модулей происходила до того, как Apache начнет создавать дочерние процессы. Об этом смотрите в официальной документации по mod_perl, там, кстати, есть и цифры.

Про сравнения

Quote:
Сравнивать Apache+mod_perl с просто Apache - это очень некорректно.
Для пояснения, почему apache+mod_perl называют "тяжелым" бэкэндом, очень даже корректно.

Контекст

Понятно. Просто привык, что комментарии к статьи следует рассматривать в контексте самой статьи!

patch

я, собственно, сюда за патчем к FCGI::Spawn. Отпиши его по адресу в мане. Спасибо.

> Так вот, 500Мб

> Так вот, 500Мб хватит в лучшем случае на 500/15 = 33 одновременных обращения

Коллега, использование FastCGI даёт возможность сменить mpm_prefork на mpm_worker для PHP, так как mod_php очень плохо совмещается с трёдовой моделью (по крайней мере так было год назад). Футпринт памяти для apache+mpm_worker не в пример приятнее, так что в данном случае ситуация не до такой степени печальна и разница в количестве одновременно обрабатываемых соединений меньше.

А lighttpd?

nginx умеет общаться только с внешними FastCGI-серверами по TCP. Однако у lighttpd имеется свой менеджер FastCGI-процессов. Если им воспользоваться, то http-сервер получается всего один, что весьма приятно в плане администрирования. При этом он легко отдаёт много статики, как nginx. Да, COW-памятью при этом воспользоваться не получается, то есть по сравнению с комбинацией "лёгкий proxy + apache/mod_perl" имеется некоторый проигрыш по памяти. Правда, не стоит сильно переоценивать COW, поскольку имеет место диффузия: страницы по ходу работы текут из shared memory в память экземпляра, но не обратно. А некоторые модули (Spreadsheet::ParseExcel) вообще, похоже, отъедают память быстро и необратимо. MaxRequestsPerChild и Apache::SizeLimit помогают, но не принципиально: частые гибели/рождения httpd снижают быстродействие.

Интересно, что Вы считаете оптимальным WEB-сервером для Perl под Win32, где fork'а в ядре ОС нет совсем. Мне очень бы хотелось иметь что-то типа lighttpd/FastCGI, но пока оно, похоже, не работает. А ближе всего на это походит IIS/fcgiext.

Я на днях

Я на днях заинтересовался использованием FCGI, и почитал кучу статей по теме mod_php против php-fcgi.

Конечно многие спорят, проводят тесты (причем в зависимости от способа тестирования преимущество не обязательно исключительно у одной стороны), конечно, некоторые нюансы не так влияют на перл, потому что в php-fcgi нет интерфейса к технологии со стороны скрипта..
Вобщем факты:
1. nginx+xx-fcgi при аналогичном apache+mod.. количестве процессов потребляет существенно меньше памяти, и хотя "на высокопроизводительном продакшне слабых серваков не бывает", но облегчение такое что на реальных задачах "на 4 гигах ОЗУ высвобождается почти 2 гига", которые затем могут быть замечательно использованы под всевозможные кеши (filesystem, network buffers, mysql, memcached, reverse proxy, etc).
2. php-fcgi позволяет эффективно использовать persistent database connection, что существенно уменьшает количество необходимых потомков mysql, и тоже в свою очередь экономит память (реально для mod_php нужно чтобы количество процессов mysql было несколько больше количества процессов apache, для fcgi при использовании persistent DB connection количество интерпретаторов равно количеству mysql).
3. минус в том что mod.. работает через API, что есть быстрее чем xx-fcgi (при работе API не происходит переключения задач, потому что код приложения выполняется внутри адресного пространства сервера), но при сколько-нибудь существенном времени работы скрипта это уже не так существенно (реально можно считать что каждое переключение задач занимает порядка микросекунды, сетевой стек еще в несколько раз медленней; при пересылке данных переключение задачи происходит ориентировочно каждые 8000-65000 байт пересылаемых данных).
4. Еще одно важное преимущество perl-fcgi в том что для перезапуска скрипта mod_perl требуется перезапуск всего сервера http, а с perl-fcgi можно перезапускать только fcgi конкретной подсистемы, что уменьшает общий downtime системы и по сути тоже увеличивает производительность.

Что касается win32, тут альтернативы IIS пока нет, а результат того что Microsoft недавно стала спонсором Apache, может быть совсем неожиданным :)