Disassembler

Artificial intelligence is no match for natural stupidity.
11srpna2012

Apache a PHP šestkrát jinak


Původně jsem chtěl o kombinování Apache s PHP psát už minulý týden, ale s popisováním multi-processing modulů Apache jsem se rozšoupl natolik, že mi to vystačilo na celý článek. Nicméně znalost rozdílů mezi jednotlivými MPM je poměrně důležitá k pochopení dnešního článku, ve kterém už propojení Apache a PHP skutečně rozeberu, takže možná byla moje grafomanie nakonec užitečná.

Obsah


Článek je docela dlouhý, tak pro lepší orientaci přidávám obsah:

Testovací prostředí


U každého způsobu budu uvádět i snippety z konfigurace a benchmark. Testuji na virtuálním Ubuntu 12.04 Server se dvěma jádry z Intelu i3-2100T (2,5 GHz) a 1 GB RAM. Balíčky sosám přímo z oficiálních repozitářů. Nastavení MPM nechávám ve výchozím stavu, tj.

<IfModule mpm_prefork_module>
    StartServers          5
    MinSpareServers       5
    MaxSpareServers      10
    MaxClients          150
    MaxRequestsPerChild   0
</IfModule>
<IfModule mpm_worker_module>
    StartServers          2
    MinSpareThreads      10
    MaxSpareThreads      20
    ThreadLimit          40
    ThreadsPerChild      10
    MaxClients           80
    MaxRequestsPerChild   0
</IfModule>

PHP je nainstalováno společně se všemi rozšířeními, co na serveru běžně používám, tj. APC, CURL, GD, GeoIP, Imagick, IMAP, intl, mcrypt, mysqlnd, pgsql, sqlite a suhosin.

Benchmark Apache pak probíhá z jiného virtuálního stroje na témž fyzickém hardware, takže propustnost sítě by měla být celý a ničím nerušený 1 Gbit. Apache benchmark je spouštěn s parametry

ab -n 5000 -c 50 http://192.168.x.y/

a zajímá mě u něj hlavně průměrná a nejdelší doba potřebná k vyřízení požadavku. Index.php, který je requestován obsahuje pouze phpinfo(), protože se momentálně nestarám o rychlost zpracování skriptů, ale pouze o rychlost probuzení interpretu.

Poslední údaj, který mě zajímá, je využití paměti. Zde budu měřit rozdíl mezi tím, kolik paměti je využito hned po startu webserveru a kolik si Apache s PHP sežerou ve špičce.

Prefork + mod_php5


První a zároveň asi nejméně náročnou metodou, jak zajistit, aby váš Apache uměl přelouskat i Prasečí Hromádky Písmenek (odtud akronym PHP) je užití stoletého mod_php, resp. mod_php5. Tento modul je ovšem možno použít pouze s prefork MPM, protože funguje tak, že si do každého podřízeného procesu natahá celé PHP, včetně konfigurace, rozšíření a všeho dalšího, co by mohlo být potřeba. Jak zmiňuju v minulém článku, je to fajn, pokud potřebujete absolutní stabilitu a bezpečnost, ale je potřeba si uvědomit, že Apache si tahá PHP skutečně do každého podprocesu. Každého! I když jen načítáte ikonku v záložkách prohlížeče nebo když vyhledávací roboti requestují robots.txt nebo sitemap.xml nebo je prostě obecně prováděn jakýkoliv statický request. Vždycky je v podprocesu Apache přítomen PHP interpret se všemi výše uvedenými serepetičkami. A jelikož požadavků na statický obsah bývá tak desetkrát více než požadavků na obsah dynamický, žere v těchto případech mod_php5 paměť naprosto zbytečně.

Pokud instalujete z .deb balíčků, pak je konfigurace tohohle otesánka je veškerá žádná. V opačném případě musíte do konfiguráku Apache přidat něco takového:

LoadModule php5_module /usr/lib/apache2/modules/libphp5.so

<FilesMatch "\.php$">
    SetHandler application/x-httpd-php
</FilesMatch>

Server API PHP hlásí jako

Apache 2.0 Handler

nebo třeba

Apache 2.4 Handler Apache Lounge

záleží, z jakého jste kmene.

Benchmark pak ukázal hodnoty

Concurrency Level:      50
Time taken for tests:   14.122 seconds
Complete requests:      5000
Failed requests:        0
Write errors:           0
Total transferred:      443960000 bytes
HTML transferred:       443000000 bytes
Requests per second:    354.06 [#/sec] (mean)
Time per request:       141.218 [ms] (mean)
Time per request:       2.824 [ms] (mean, across all concurrent requests)
Transfer rate:          30701.14 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    1   3.8      0      47
Processing:     4  140 119.6    131    3232
Waiting:        1   10  20.1      5     923
Total:          4  141 119.8    132    3233

Percentage of the requests served within a certain time (ms)
  50%    132
  66%    166
  75%    190
  80%    205
  90%    258
  95%    305
  98%    354
  99%    405
 100%   3233 (longest request)

Časy jsou nádherné, ale rozdíl využití pamětí mezi startem a špičkou je 397 MB s tím, že využití paměti skáče podle počtu podprocesů. Tedy s každým novým podprocesem Apache se najednou alokuje tolik paměti, kolik je třeba pro běh podprocesu Apache a PHP uvnitř něj.

Prefork + mod_cgi


Tahle kombinace se dá jednoduše popsat dvěma slovy. „Nedělejte to.“ Zmiňuju ji jen proto, že to jde, ale to ani zdaleka neznamená, že je to dobý nápad. Jedná se totiž o propojení skrze full-featured Common Gateway Interface (CGI), což v praxi znamená, že se na každý požadavek, který bude zpracovávat PHP, naspawnuje nový PHP-CGI proces, vyřídí to, co po něm chcete, a zase si chcípne. V konfiguraci to může vypadat třeba takto

ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/
<Directory /usr/lib/cgi-bin>
    AllowOverride None
    Options None
    Order allow,deny
    Allow from all
</Directory>
AddHandler php-cgi .php
Action php-cgi /cgi-bin/php5

Jak ze snippetu můžete vidět, je pro tuto prasečinu mít potřeba binárku php-cgi (nebo symlink či nějaký wrapper) na správném místě a také Apachi povolit mod_actions.

SAPI se v takovém případě začne hlásit známým

CGI/FastCGI

A výkon začne být opravdu směšný

Concurrency Level:      50
Time taken for tests:   238.124 seconds
Complete requests:      5000
Failed requests:        0
Write errors:           0
Total transferred:      438640000 bytes
HTML transferred:       437680000 bytes
Requests per second:    21.00 [#/sec] (mean)
Time per request:       2381.242 [ms] (mean)
Time per request:       47.625 [ms] (mean, across all concurrent requests)
Transfer rate:          1798.89 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    1   0.8      1       5
Processing:   303 2335 1632.9   2003   16207
Waiting:      130 1173 1610.5    742   15521
Total:        304 2337 1633.0   2004   16209

Percentage of the requests served within a certain time (ms)
  50%   2004
  66%   2212
  75%   2341
  80%   2420
  90%   2699
  95%   6578
  98%   7202
  99%  10958
 100%  16209 (longest request)

Průměrně 2,3 vteřiny a nejvíce pak 16 vteřin na request. Rozdíl mezi obsazenou pamětí po startu a ve špičce sice klesl na 344 MB, ale pro nasazení na produkčním (a vlastně i jakémkoliv jiném) systému je tato kombinace nepoužitelná. Problém se škálováním je zde úplně stejný jako u mod_php5 a to si nejsem úplně jist, zda by při zvýšení výpočetního výkonu nešlo využití ve špičce ještě dál, protože oba virtuální procesory potily virtuální krev.

Worker + mod_cgid


Vyměníme tedy prefork za lehčí a rychlejší processing model a zkusíme, jak se s plným CGI popere tentokrát. Rozdíl mezi mod_cgi a mod_cgid je v zásadě pouze ten, že mod_cgid spolupracuje s worker MPM a je schopen si pomocí direktivy ScriptSock otevřít socket skrze který je možno si s jeho CGI daemonem povídat. My si však budeme muset vystačit se stejnou konfigurací jako v předchozím případě. A není překvapením, že i výsledek benchmarku bude skoro stejný.

Concurrency Level:      50
Time taken for tests:   224.246 seconds
Complete requests:      5000
Failed requests:        0
Write errors:           0
Total transferred:      438640000 bytes
HTML transferred:       437680000 bytes
Requests per second:    22.30 [#/sec] (mean)
Time per request:       2242.459 [ms] (mean)
Time per request:       44.849 [ms] (mean, across all concurrent requests)
Transfer rate:          1910.22 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    1   0.8      1       8
Processing:   315 2200 1701.0   1778   12304
Waiting:       89 1179 1652.3    684   10907
Total:        316 2201 1701.1   1778   12305

Percentage of the requests served within a certain time (ms)
  50%   1778
  66%   1987
  75%   2125
  80%   2217
  90%   2716
  95%   6618
  98%   7112
  99%   7555
 100%  12305 (longest request)

Nejdelší požadavky jsou nepatrně rychleji vyřízené, ale k dokonalosti to má stále daleko. Co už ale trochu odlišné od předchozího případu je, je využití paměti. Rozdíl totiž je „jen“ 329 MB, což je o nějakých 15 mega méně než s preforkem. Právě tolik totiž udělá overhead Apache, který použitím worker MPM téměř odstraníme. Nicméně ošklivé CGI, spouštěné při každém požadavku, zůstává.

Worker + mod_fastcgi


Když už se to na plnou hubu jmenuje mod_fastcgi, tak už by to mohlo být FastCGI, že? Naštěstí pro nás se konečně tento modul už umí dorozumět plnohodnotným FastCGI a pro svoje podprocesy má dokonce vlastní mimoapačovský process manager - fcgi-pm. Společně s balíčkem libapache2-mod-fastcgi se mi na Ubuntu nainstalovala i obecná část konfigurace

<IfModule mod_fastcgi.c>
    AddHandler fastcgi-script .fcgi
    FastCgiIpcDir /var/lib/apache2/fastcgi
</IfModule>

Kterou ale nevyužiju a místo toho si nakonfiguruju vlastní handler pro PHP

FastCgiServer /usr/lib/cgi-bin/php5 -processes 20
AddHandler php-fastcgi .php
Alias /php5.fcgi /usr/lib/cgi-bin/php5
Action php-fastcgi /php5.fcgi virtual

Tady už to začíná být trochu zajímavé. Soubor /php5.fcgi ve skutečnosti neexistuje a mám ho zde proto, abych se vyhnul zbytečné definici ScriptAlias, kterou jsem používal u plného CGI. Direktiva FastCgiServer pak určuje binárku nebo wrapper, který fcgi-pm naspawnuje hned po startu Apache a který bude požadavky později zpracovávat. Já jsem opět přímočaře zvolil binárku PHP a parametrem processes ji přikázal nasázet ve dvaceti kopiích, čímž jsem vlastně definoval statický fond (pool) podprocesů. Bohužel mi ale v takovém případě nebude fungovat APC, protože opcode cache je sdílena mezi podprocesy vytvořenými PHP a v mém případě podprocesy vytváří přímo Apache, takže si vzájemně nevidí do sdílené paměti. Kdybych ale použil třeba následující wrapper

#!/bin/sh
PHP_FCGI_CHILDREN=20
export PHP_FCGI_CHILDREN
PHP_FCGI_MAX_REQUESTS=1000
export PHP_FCGI_MAX_REQUESTS

exec /usr/bin/php5-cgi

A nechal jej fcgi-pm naspawnovat pouze jednou, pak budu mít ve výsledku pořád 20 podprocesů starajících se o FastCGI, ale opcode cache mezi nimi už sdílená bude.

Výsledek benchmarku pak bude vypadat takto

Concurrency Level:      50
Time taken for tests:   14.061 seconds
Complete requests:      5000
Failed requests:        0
Write errors:           0
Total transferred:      428590000 bytes
HTML transferred:       427630000 bytes
Requests per second:    355.59 [#/sec] (mean)
Time per request:       140.612 [ms] (mean)
Time per request:       2.812 [ms] (mean, across all concurrent requests)
Transfer rate:          29766.01 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    1   0.9      0      15
Processing:    24  139  24.8    134     674
Waiting:       22   96  19.2     89     181
Total:         29  140  24.7    135     675

Percentage of the requests served within a certain time (ms)
  50%    135
  66%    146
  75%    154
  80%    161
  90%    174
  95%    183
  98%    194
  99%    202
 100%    675 (longest request)

A rozdíl využití paměti mezi startem a špičkou bude pouze 112 MB, což jsou údaje naprosto přenádherné a s ledovým klidem použitelné v produkčním prostředí. V tomto případě je ale u využití paměti trochu chyták, protože FastCGI procesy jsou naspawnovány ihned po startu Apache, takže místo pěti mega pro samotného Apache bude při startu sežráno dalších cca 100 MB FastCGI procesy. I tak jsou to ale nižší hodnoty než u předchozích případů.

Worker + mod_fcgid


Mod_fcgid je mladší bratr a tak trochu kříženec mod_fastcgi a mod_cgid. Zajímavý nápad, bohužel uchopený za špatný konec. Sice je rychlejší než všechny výše uvedené, ale implementuje jen základní sadu FastCGI protokolu, takže se jedná spíše o „urychlovač“ než o nástroj, který by byl to pravé pro pohon vašeho PHP. Konfigurace, ač v mém případě vypadá jednoduše, je pro skutečný produkční server o něco složitější.

<Directory /var/www/>
    Options +ExecCGI
</Directory>

AddHandler fcgid-script .php
FcgidMaxProcesses 20
FcgidWrapper /usr/bin/php5-cgi

V adresáři s vašimi PHP soubory je třeba zapnout ExecCGI a navíc je záhodno nepouštět požadavky přímo na binárku PHP jako to dělám já, ale opět si vyrobit vlastní wrapper a hezky jej poladit. Proces, který je uveden v direktivě FcgidWrapper Apache spustí při prvním požadavku a dle potřeby bude přidávat či ubírat další procesy, jak mu ostatní Fcgid* direktivy dovolí. Oproti mod_fastcgi se tedy jedná o dynamický pool, spravovaný process managerem přímo integrovaným do Apache. Benchmark mod_fcgid s výchozím nastavením PHP pak dopadne takto:

Concurrency Level:      50
Time taken for tests:   14.107 seconds
Complete requests:      5000
Failed requests:        0
Write errors:           0
Total transferred:      426250565 bytes
HTML transferred:       425290542 bytes
Requests per second:    354.42 [#/sec] (mean)
Time per request:       141.075 [ms] (mean)
Time per request:       2.821 [ms] (mean, across all concurrent requests)
Transfer rate:          29506.38 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    1   1.0      1      10
Processing:     4  129 722.7     36   13302
Waiting:        3  124 722.3     32   13297
Total:          4  130 723.1     37   13309

Percentage of the requests served within a certain time (ms)
  50%     37
  66%     44
  75%     50
  80%     53
  90%     65
  95%     82
  98%   1062
  99%   3057
 100%  13309 (longest request)

13 vteřin na nejdelším requestu je pravděpodobně způsobeno prodlevou při probuzení CGI daemona prvním requestem nebo možná zdržením mezi zabitím starého podprocesu a spawnem nového při vysokém vytížení CPU. Ale 130 ms průměr a 37 ms medián? No sakra, to už jsou hodně slušná čísla. A když k tomu přidáme rozdíl vytížení paměti pouhých 181 MB, tentokrát bez podvodu s předem naspawnovanými procesy, skoro to vypadá, že máme vítěze. Ale jak už jsem naznačil, mod_fcgid trpí několika neduhy, vinou kterých jej není možno doporučit. Mimo výše uvedenou teoretickou bezpečnostní díru v podobě nutnosti zapnutého ExecCGI, ne úplně triviálního nastavení a občasného zahlcení požadavky je navíc mod_fcgid nekompatibilní s APC, protože si PHP podprocesy spawnuje sám a použití wrapperu podobného jako u mod_fastcgi by zmařilo účel a funkce, které mod_fcgid nabízí. Ale nezoufejte, ideální kombinace už je na dosah.

Worker + mod_fastcgi + PHP-FPM


Mod_fastcgi umí jednu velmi šikovnou věc. Velice jednoduše se dá nakonfigurovat tak, aby požadavky na vyřízení FastCGI requestů mohl posílat i mimo své prostředí, na TCP porty nebo sockety daemonů, které už se o zpracování požadavků postarají. A právě tohohle se chopila komunita kolem PHP, která už se nemohla dál dívat na ubohé výkony a nedostatek funkcí u předchozích modulů, proklamujících podporu FastCGI. PHP-FPM je samostatný FastCGI process manager, který do hry navíc přináší množství velice užitečných featur a nebojím se říci, že je v současnosti nejefektivnějším řešením pro virtuální hostingy webů.

Jelikož se jedná o program postavený úplně mimo Apache, je výuková křivka pro práci s ním trochu strmější, ale po pár pokusech a omylech velmi rychle přijdete na to, co je kde třeba nastavit a jak to vlastně celé funguje. Pro začátek je důležité zmínit, že PHP-FPM běží jako služba, která je schopna obhospodařovat více procesních poolů zároveň a že každý pool může být nastaven úplně jinak. Mimoto lze měnit administrátorskou část konfigurace PHP za jízdy, bez toho aniž by byly násilně přerušeny běžící FastCGI requesty. Dále je PHP-FPM také schopen spawnovat pracovní vlákna každého poolu pod jiným uživatelem a s jinými proměnnými prostředí nebo chrootem. Nebudu dál vyjmenovávat proč je PHP-FPM svatým grálem pro práci s PHP (což si ostatně můžete přečíst sami), takže pění chvály ukončím s tím, že PHP-FPM je jediná aplikace se zkratkou „PHP“ v názvu, u které je vidět, že při jejím návrhu občas někdo i trochu přemýšlel.

Konfigurace Apache je v zásadě jednoduchá a podobná jako u „holého“ mod_fastcgi

FastCgiExternalServer /tmp/php5.fcgi -socket /var/run/php5-fpm.sock
AddHandler php-fastcgi .php
Alias /php5.fcgi /tmp/php5.fcgi
Action php-fastcgi /php5.fcgi virtual

/php5.fcgi a /tmp/php5.fcgi jsou opět jen virtuální handlery a všechny .php soubory jsou zpracovávány způsobem definovaným v direktivě FastCgiExternalServer. Tam je řečeno, že se má vše posílat kamsi na socket. Pojďme se tedy podívat na druhou stranu socketu, kde sedí PHP-FPM konfigurace, která jej vytvořila. Můj pool, který na socketu naslouchá, je nastaven takto

[www]
user = www-data
group = www-data

listen = /var/run/php5-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

pm = dynamic
pm.max_children = 20
pm.start_servers = 4
pm.min_spare_servers = 4
pm.max_spare_servers = 8

Max_children, start_servers, min a max_spare servers... kde já jsem to jenom viděl? Pokud tušíte, že dynamický mód process manageru se nastavuje a funguje téměř stejně jako worker MPM Apache, pak tušíte správně.

Concurrency Level:      50
Time taken for tests:   14.522 seconds
Complete requests:      5000
Failed requests:        0
Write errors:           0
Total transferred:      430345000 bytes
HTML transferred:       429385000 bytes
Requests per second:    344.31 [#/sec] (mean)
Time per request:       145.220 [ms] (mean)
Time per request:       2.904 [ms] (mean, across all concurrent requests)
Transfer rate:          28939.49 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    1   0.7      0      22
Processing:    31  144  21.8    145     297
Waiting:       22  101  10.4    101     190
Total:         36  145  21.6    146     297

Percentage of the requests served within a certain time (ms)
  50%    146
  66%    153
  75%    157
  80%    160
  90%    169
  95%    178
  98%    190
  99%    200
 100%    297 (longest request)

Průměr i medián vyšel nějakých 145 ms, nejdelší request pak pod 300 ms. Když si vyscrollujete nahoru na Prefork + mod_php5 a znovu se podíváte na jeho benchmark, zjistíte, že rychlost vyřizování requestů je srovnatelná, ale co se modularity a škálování výkonu týče, strčí PHP-FPM hravě všechny do kapsy. Po startu webserveru si Apache a čtyři procesy PHP-FPM poolu dohromady vyžádali závratných 13 MB paměti. Při práci si pak můj pool uzmul 86 MB navíc, z nichž polovinu okamžitě po opadnutí špičky vrátil.

Lepiči kódu si pak musí dát pozor na změnu v SAPI, které se PHP-FPM hlásí jako

FPM/FastCGI

Hon na paměť


Jsem si celkem jist, že se najde spousta takových, co si už po přečtení druhého odstavce budou klepat na čelo a ptát se, proč jsem tak posedlý šetřením paměti, když je od toho aby se využívala. Pokusím se tedy uvést věci na pravou míru. Samozřejmě souhlasím s tím, že paměť může být obsazena jakkoliv, pokud je využita efektivně a pokud je jí systém schopen okamžitě alokovat rozumně velké množství, když si to některý z procesů vyžádá. Ale to, že mám k dispozici prostředky, stejně jako v jakékoliv jiné situaci neznamená, že jimi musím plýtvat. Proč bych měl mít 300 MB overheadu, když ho s jinými nástroji mít nemusím? A proč by mi měl po vyřízení velkého množství požadavků zbýt kýbl nečinných procesů, když mi jich stačí jen pár? U větších virtualhostů je škálování klíčovým prvkem úspěchu.

Představte si docela reálnou situaci, kdy mám železo s 8 GB paměti, na kterém běží webserver s PHP, MySQL, pgSQL, nějaký FTP server a třeba mailserver nebo alespoň smarthost, plus samozřejmě monitoring a další věci, o které běžný návštěvník nezavadí. Na takovém serveru mám třeba sto webů, z nichž alespoň jeden je napsán otřesným prasečím způsobem. V php.ini mám memory_limit nastaven na výchozích 128 MB. V normálním provozu mám obsazené asi 4 GB paměti, když tu najednou se onen prasečí web ukáže býti e-shopem a ponoukne svým zákazníkům nabídku, která se neodmítá. V tu chvíli přijde padesát uživatelů a začne web načítat. Špatně napsaná stránka se zacyklí a začne požírat paměť, což sice dělala i předtím, ale nikoho to kvůli nižší návštěvnosti moc netrápilo. A jak se takhle ona veselá stránka načítá padesátkrát, tak se ke čtyř gigům běžného využití přidá 50x128 MB prasokódu. 4096 MB + 50 x 128 MB, to máme něco málo přes deset giga, což jaksi do osmi giga RAM nenacpeme, takže systém začne swapovat. A jakmile systém začne swapovat, je to zatraceně špatně, protože vyřizování požadavků se začne prodlužovat a požadavky hromadit ve frontách a pokud se zácpa zavčas neprošťouchne, služby, dost možná i s celým serverem v závěsu, půjdou na tlamičku. A tomu všemu je možno hravě předejít použitím správných nástrojů a vyladěním jejich konfigurací.

Dodatek ke konfiguraci PHP


Nepoužití archaické kombinace prefork + mod_php5 má mimo lepšího využití systémových prostředků ještě jeden efekt. Lépe udržovatelnou konfiguraci PHP. V případě mod_php5 lze PHP konfigurovat per-vhost přímo ve <VirtualHost> bloku Apache pomocí php_admin_value a php_admin_flag a per-directory pak v souborech .htaccess pomocí obdobných php_value a php_flag. Nevýhodou tohoto řešení je, že nemůžete použít předdefinované konstanty PHP. Mít v .htaccess souboru například něco takového

php_value error_reporting E_ALL & ~E_DEPRECATED & ~E_NOTICE

nepřipadá v úvahu a je třeba požadované nastavení vyjádřit přímo číselnou hodnotou. Nejen že je pro výše uvedený příklad hodnota v PHP 5.2, 5.3 a 5.4 pokaždé jiná, ale kolik z vás si ji pamatuje a vypočetlo by ji z hlavy? Já myslím, že rozhodně méně, než kolik si pamatuje názvy konstant.

Moduly, komunikující s PHP skrze CGI nebo FastCGI protokol je možno per-directory nastavovat skrze soubor .user.ini, či jiný, definovaný php.ini direktivou user_ini.filename. Tento soubor už PHP konstanty zvládá a zapisuje se do něj úplně stejně jako do php.ini. Samozřejmě je možno upravovat pouze PHP_INI_USER a PHP_INI_PERDIR hodnoty. Per-vhost nastavení je trochu nepraktičtější, protože je proveditelné pouze v [HOST=] a [PATH=] ini sekcích (viz manuál PHP), ale na druhou stranu zas máte vše na jednom místě, což někomu může vyhovovat více než roztroušení vhosti.

No a konečně PHP-FPM zase všechny strčil do kapsy. Nejen že zvládá práci s .user.ini, ale administrátorská část konfigurace se dá vyrábět jak per-vhost, tak i per-pool a možnosti nastavení jdou až tak daleko, že pro různé pooly můžete jako základ využít úplně odlišné php.ini soubory.

Takže kdybyste náhodou chtěli zahostovat web na moderním a velmi dobře škálovatelném serveru s worker MPM a PHP-FPM, jsem vám k službám. Na stránku vygenerovanou jedním takovým totiž právě teď koukáte.

Update - 21.10.2015 - mod_proxy_fcgi


V době psaní toho článku byl Apachův mod_proxy_fcgi těžce experimentální a ani zdaleka se nejednalo o nijak známý a používaný způsob propojení Apache a PHP-FPM. Pořádně použít se dá až od Apache 2.4.10, kdy se konečně naučil pracovat i s unixovými sockety tak, jak to umí mod_fastcgi. Cca od roku 2015 je mod_proxy_fcgi jedinou smysluplnou volbou, protože je ještě o fous rychlejší než mod_fastcgi a konfiguruje se mnohem snadněji. V zásadě potřebujete akorát nahrát proxy moduly a skrze direktivy FilesMatch a SetHandler přesunout odpovědnost za zpracování všech souborů s příponou PHP kamsi do socketu, na kterém sedí PHP-FPM.

LoadModule proxy_module /usr/lib/apache2/modules/mod_proxy.so
LoadModule proxy_fcgi_module /usr/lib/apache2/modules/mod_proxy_fcgi.so

<FilesMatch ".+\.php$">
    SetHandler "proxy:unix:/var/run/php5-fpm.sock|fcgi://localhost"
</FilesMatch>

Statistiky a benchmarky bohužel poskytnout nemůžu, protože testovací prostředí vypadá dávno jinak a nedávaly by v kontextu ostatních benchmarků smysl.