Hlavní navigace

Povolání: ladič HTTP serveru

19. 10. 1999
Doba čtení: 8 minut

Sdílet

Tak by se s trochou nadsázky mohla jmenovat funkce, kterou bych mohl zastávat. Ne že bych byl nějaký guru, který dokáže citovat dlouhé pasáže ze zdrojových kódů Apache, ale za tu dobu, co provozujeme Navrcholu se dá říct, že jsem už na nějaký ten fígl, jak získat trošku výkonu navíc, přišel. A dnes se s vámi o tu trochu podělím.

Když jsem se před mnoha měsíci (vlastně už bych mohl říkat lety, protože jsou to dva roky :) setkal s HTTP serverem Apache, vystřídal můj prvotní šok z toho, jak složitá je jeho konfigurace (vážně mi to tak tenkrát připadalo), úžas z možností, které tento program nabízel. A když si připočtete, že za tu dlouhou dobu se ještě mnohé zlepšilo, je jasné, že Apache je rozhodně špičková záležitost.

Jenže nic není dokonalé a tak i Apache má rozhodně jisté rezervy, zejména ve výkonu. Nic však není ztraceno, lecos lze ještě zdokonalit šikovným nastavením (poznámka: mluvím samozřejmě primárně o un*xové verzi Apache, ale je možné, že některé dále uvedené techniky platí i pro další platformy).


Kompilace

Při ladění je asi vhodné začít od kompilace. Doporučuji nezahrnovat do serveru ty moduly, které bezprostředně nepotřebujete. Na toto téma se před časem vedla zuřivá debata v linuxové konferenci, přičemž její resume nebylo jednoznačné. Já osobně se kloním k tomu, co jsem napsal výše, a to hned ze dvou důvodů:

Cloud23

  • Apache bez modulů zabírá méně paměti
    Proti tomuto tvrzení stojí fakt, že nepoužitý modul nealokuje ani žádné zdroje, takže zůstává na počátečním (obvykle nevelkém) množství paměti a navíc se nevyužívané paměťové stránky postupem času odswapují. Naopak lze říct, že pokud modul nepotřebuji, není důvod, aby zabíral i to malé množství paměti. Každý bajt dobrý.
  • Nezakompilovaný modul nemůže škodit za vašimi zády
    Pokud modul do serveru vůbec nezahrnete, nemůže se stát, že nějaká zapomenutá direktiva z konfiguračního souboru způsobí nějaké problémy, ať už jde o neočekávané chování nebo třeba snížení výkonu, neboť Apache se odmítne spustit, bude-li konfigurace obsahovat něco, čemu neporozumí (protože tomu rozuměl jenom právě onen modul).

Ještě bych doplnil, že kompilace Apache netrvá zase až tak dlouho, aby nebylo možné v případě potřeby modul přidat. Druhou možností, kterou lze pro podobné případy snadno aplikovat, je použití tzv. Dynamic Shared Objects (DSO), které Apache nabízí od verze 1.3. DSO jsou vlastně sdílené knihovny, a jako takové je lze do programu přidávat za běhu. Nejsem ovšem schopen fundovaně konstatovat, zda dynamicky linkovaný kód není z principu pomalejší.

O tom, které moduly se do Apache zakompilují a které nikoliv, rozhoduje použití parametrů –enable-module a –disable-module skriptu configure.


Konfigurace

Při výkonnostním ladění Apache je třeba se zamyslet, co vlastně nejvíce program zpomaluje. Hlavní příčiny jsou to zřejmě asi dvě. První jsou diskové operace obecně a druhou pak to, že Apache paralelní požadavky obsluhuje tak, že zakládá kopie sebe sama a těm pak předává dotazy. K tomuto klonování slouží funkce fork(), která je sama o sobě poměrně náročná (říká se, že oproti některým jiným unxům je na tom ještě Linux docela dobře, ale i tak to dělá docela dost).

Minimalizace nutnosti provádět fork()
Vzhledem k tomu, že jsem napsal, že fork() je jednou z hlavních příčin zatěžování serveru, bylo by záhodno použití této funkce minimalizovat. Úplně největší vliv má nastavení konfigurační direktivy MaxRequestsPer­Child. Ta nastavuje maximální počet požadavků, které jeden dětský proces vyřídí, než se odebere do věčných lovišť. Cílem tohoto nastavení je omezit riziko, že program díky chybám postupně vyčerpá všechnu volnou paměť. Dříve bývala tato volba nastavena na nějakou směšně malou hodnotu (kterou jsem už zapomněl, ale která znamenala, že třeba v případě Navrcholu by dětské procesy umíraly ve špičce zhruba jednou až dvakrát za vteřinu). Nyní si již zřejmě autoři Apache věří, protože výchozí hodnota je 0, tedy proces nikdy nezemře (verze 1.3.9). Rozhodně doporučuji zvýšit toto nastavení minimálně na několik tisíc požadavků, ne-li více. V jednom případě jsem radikálním zvýšením původního nastavení dosáhl přibližně dvacetinásobného snížení zátěže (bráno podle dlouhodobé průměrné hodnoty loadu serveru).
Další konfigurační direktivy, které mají zejména vliv na okamžitou rychlost odezvy, jsou MinSpareServers a MaxSpareServers. Ty vymezují rozsah, v němž se pohybuje počet „flákajících“ se dětských procesů, tedy těch, které nevyřizují žádný požadavek a trpělivě čekají. Apache se snaží dosáhnout toho, aby počet takových procesů neklesl pod hodnotu MinSpareServers a zároveň nestoupl nad hodnotu MaxSpareServers. Cílem je zajistit, aby vždy pokud možno existovala rezerva a nebylo nutné zakládat dětský proces teprve v okamžiku příchodu požadavku.

Další příčinou forkování jsou stránky dynamicky generované externími neperzistentními procesy, tedy převážně CGI skripty. Každé volání takového skriptu opět znamená provedení forku(), protože v un
xech je spouštění nového programu řešeno dvojstupňově – jiný (rodičovský) proces se naklonuje a pak nahradí svůj kód kódem nově spouštěného programu. To má samozřejmě i svoje výhody, ale o tom až jindy.
Tady už bohužel zřejmě není jiné cesty, než použít některou z metod, jak zajistit, aby takový externí proces běžel trvale – obecně třeba FastCGI (různé servery i programovací jazyky) nebo pomocí specializovaného modulu, jako je mod_perl (pouze Apache a Perl). Úprava existujících CGI pro tyto podmínky naštěstí bývá poměrně jednoduchá. Další cestou je pak použití některého jazyka začleněného do HTML, pakliže je interpretr zabudován přímo v HTTP serveru (například PHP3). Výhodou takového řešení je, že proces nezabírá systémové prostředky, pokud není využíván, a přitom není nutné kvůli jeho vykonání provádět další fork(), nevýhodou je naopak to, že před každým použitím se musí provést zpracování zdrojového kódu (parsing).

Je očividné, že čím méně běží procesů, tím lépe se serveru bude dýchat. Cestou, která k tomu vede, je dosažení co nejvyšší rychlosti řešení požadavků. Pokud bude proces rychle připraven odpovídat dalšímu žadateli, nebude se zase muset zakládat tolik procesů a tím pádem tolik forkovat. Tomu může napomoci správné nastavení direktiv KeepAlive, KeepAliveTimeout a MaxKeepAliveRe­quests. Metoda KeepAlive (ponech naživu) vychází z předpokladu, že uživatel kromě požadované HTML stránky bude zřejmě stahovat i obrázky nebo další objekty, které jsou do ní vloženy. Spojení mezi serverem a klientem se tedy (pokud oba tuto funkci podporují) neuzavře po prvním poslaném souboru, ale nadále existuje pro další požadavky a tato situace se opakuje až do vypršení časového limitu (KeepAliveTimeout) nebo do vyčerpání limitu na počet požadavků v rámci jednoho spojeni (MaxKeepAlive­Requests).
Pokud lze předpokládat, že drtivá většina požadavků bude jednorázová (jako například u veřejných počítadel atd.), můžeme podporu KeepAlive úplně vypnout. Stejně tak se bez této vlastnosti obejdete například pokud obrázky posílá jiný server než vlastní stránku – ten může mít KeepAlive vypnuto, zatímco u toho s obrázky není naškodu jej zapnout.
Pokud usoudíte, že je lépe KeepAlive nevypínat, je možné si pohrát s nastavením timeoutu a limitu na množství požadavků. Při určení těchto hodnot můžete například vycházet s průměrného množství obrázku na stránku, jejich souhrnné velikosti a předpokládané obvyklé rychlosti spojení návštěvníků. To by mělo postačovat k přibližnému určení vhodných čísel.

Podobný vliv může mít i nastavení volby TimeOut, která určuje, jak dlouho má proces čekat na data od klienta. Výchozí hodnota je 300 sekund, tedy pět minut, což je podle mě přehnané, ale při stavu dnešních telefonních linek jeden nikdy neví :(.


Minimalizace diskových operací
Diskové operace, které je nucen HTTP server provádět, bych rozdělil na dva druhy. Prvním jsou ty, ke kterým dochází v důsledku požadavku – tedy hlavně přečtení souboru z disku. Druhou skupinou jsou požadavky, které vznikají jaksi „bokem“ a lze je minimalizovat.

Ty první lze omezit zřejmě pouze jedním způsobem a tím je použití experimentálního modulu mod_mmap_static, který umožňuje pomocí direktivy MMapFile namapovat výhradně statické dokumenty do paměti. Přístup k nim je pak samozřejmě mnohonásobně rychlejší, ale má to i svoje nevýhody. Například neexistuje zpětná vazba na soubor, takže pokud se tento změní, je nutné o tom Apache informovat ručně, jinak bude vracet stále stará data.

Druhou skupina příčin diskových operací nelze už příliš paušalizovat. Mohu pouze uvést některé nejobvyklejší případy:

  • Použití souborů definovaných direktivou AccessFileName (.htaccess)
    tyto soubory umožňují definovat různá nastavení, jako například přístupová práva k souborům, pomocí běžných konfiguračních voleb Apache
    Problém: při drtivé většině dotazů musí Apache zjistit, zda tento soubor existuje a to nejenom v aktuálním adresáři, ale také vzhledem k dědičnému charakteru i ve všech rodičovských adresářích!
    Řešení: nastavit direktivu AllowOverride na hodnotu None pro kořenový adresář (např. v sekci Directory).
  • Vypnutá volba FollowSymLinks
    Apache nebude umět pracovat se symbolickými odkazy
    Problém: při každém načtení souboru je nutné zjišťovat pomocí funkce lstat(), zda jde o soubor nebo symbolický odkaz. S tím souvisí i další volba SymLinksIfOwner­Match, která zajišťuje to, že symbolický odkaz nebude následován, pokud nemá shodného vlastníka, jako cílový soubor či adresář.
    Řešení: zapnout podporu symbolických odkazů a vypnout volbu SymLinksIfOwner­Match pomocí direktivy Options FollowSymLinks -SymLinksIfOwner­Match buď globálně v konfiguraci serveru nebo pro jednotlivé adresáře. Pozor, vypnutí kontroly vlastnictví symbolických odkazů může být potenciálně nebezpečné.
    (tato pasáž článku o symbolických odkazech byla později upravena, protože při vydání obsahovala chybné informace, na což nás upozornil Yenya Kasprzak. Jemu za to děkujeme a vy si svou konfiguraci upravte, pokud jste se řídili původním návodem)

Možná by se našly i další případy.


Úplně poslední možností, jak dosáhnout zrychlení, která mě napadá, je vypnutí překládání IP adres návštěvníků na doménová jména direktivou HostNameLookups off. Je ale třeba počítat s tím, že jména se tím pádem neobjeví v logovacích souborech, nebudou je mít k dispozici CGI skripty a samozřejmě pak podle nich ani nelze řídit kontrolu přístupu. V mnoha případech je možná lepším řešením rozběhat caching nameserver přímo na stroji, kde běží Apache a ten pak používat jako primární nameserver. Překlady adres tak budou velice pravděpodobně po nacachování výrazně rychlejší, než kdyby se měly jména zjišťovat někde po síti.


Zdaleka ne všechny výše uvedené postupy jsou z mojí hlavy. Na některé jsem přišel praxí, jiné jsem někde okoukal, některé vědomosti mám z linuxové konference. A tak je šířím dál a doufám, že vám budou k užitku.