Optimalizace výkonu webové aplikace

Rychlost webové stránky je v dnešní době velmi důležitá. Uživatelé nechtějí ztrácet čas čekáním na načtení a vykreslení. U dynamických stránek musíme počítat s výpočty, databázovými dotazy a komplikovanou logikou od které se odvíjí celkový čas obsloužení HTTP požadavku. 

Pokud se postupem času aplikace doplňuje o novou funkcionalitu, dochází ke stále více operacím a její celková komplexita stoupá. Mohou se objevit výkonové problémy a ve výsledku to může pocítit i uživatel v podobě dlouhého čekání na načtení webové aplikace.

Jak se to může stát?

Říká se, že aplikace je tak rychlá, jako její nejpomalejší část. Řešení jejího výkonu není jednorázové, ale kontinuální a komplexní. I když ze začátku vše běželo svižně a nic nenaznačovalo vznik problému, s vývojem, přidáváním další logiky a používáním aplikace se mohou projevit tzv. úzká hrdla (bottleneck), která ve výsledku zpomalí celou aplikaci. Obecně můžeme říct, že výkonnostní problémy se mohou objevit na třech vrstvách:

  • Datová vrstva - špatně pracujete s daty, kterých je moc, jsou ve špatném tvaru nebo jsou špatně uložen.
  • Aplikační logická vrstva - kód, který reprezentuje logiku aplikace (například odeslání objednávky) je neefektivní a vykonává příliš mnoho operací, často zbytečných.
  • Serverová vrstva - aplikace mají pro svůj běh nějaké omezené výpočetní zdroje. Ty jsou dané serverem, na kterém běží. Pokud je server pomalý, nebo aplikace musí obsloužit příliš mnoho uživatelů (například během spuštění registrace), můžete narazit i na úzké hrdlo ze strany serveru.

Řešení výkonu je na každé vrstvě jiné a vyžaduje různé nástroje a techniky. V následující části článku ukážeme, jakým způsobem hledáme úzká hrdla v kódu aplikace na logické vrstvě a kde začít s optimalizací. 

Způsoby hledání úzkých hrdel

Při hledání výkonnostních problémů máme několik úrovní, kde problém najít:

  • Code review - V první řadě máme šanci problém najít při code review. Hledání úzkých hrdel je jeden z cílů našeho code review, abychom problém nalezli a odstranili ještě předtím, než nastane. Úzká hrdla mají často komplexní povahu, proto se občas stane, že tento proaktivní přístup zklame a musíme problém najít reaktivně.

  • Monitorování - aplikace, které provozujeme na naší infrastruktuře, máme šanci monitorovat pomocí různých nástrojů a reaktivně zjišťovat problémy reakční doby. Stihneme tak postihnout problémy dokud jsou ještě malé a neblokující. Příklad takového nástroje je Dynatrace.

  • Profilovací nástroje - pokud se problém v aplikaci nachází, najde ho v lepším případě vývojář při vývoji, v horším případě až uživatel. Problém se ale projeví pouze svým důsledkem, tedy pomalou odezvou. Pokud ho objevíme, přicházejí na řadu profilovací nástroje, které nám pomohou najít konkrétní chybu.

Profilovací nástroje

Profilovacím nástrojem nebo-li profilerem rozumíme službu nebo doplňek, která nám poskytne detailní informace o tom, co se v aplikaci děje a proč. Pokud používáme nějaký framework (Yii2, Nette...), pak pravděpodobně nějaký primitivní profiler už máme. Zjistíme tedy například jaký modul potřeboval kolik času či paměti, případně kolik databázových požadavků aplikace musela udělat. Takto můžeme najít pouze velké a dobře viditelné (a často i relativně hloupé) chyby.

Pokud hledáme ale komplexnější problém, musíme jít do hloubky. Pro takovou analýzu existuje řada nástrojů, my jsme se rozhodli pro Blackfire.io. 

Analýza skrze Blackfire.io

Když najdeme situaci, kdy vyřízení HTTP požadavku trvá naší PHP aplikaci déle než by mělo (ať už při vývoji, nebo až při následném monitoringu), nastává detektivní práce s jediným cílem – najít kdo za to může a zneškodnit ho. Nelekejte se, vývojáři zůstanou v pořádku, hledáme část kódu, kde vykonání požadavku trvá nejvíce času. Blackfire.io dokáže zobrazit postupný průchod kódem pomocí orientovaného grafu, kdy jednotlivé uzly značí vykonávané funkce. U každé funkce je počet volání, čas vykonání a kolik procent z celkového vyhodnocení zabrala.

Na obrázku výše můžete vidět část takového grafu z reálného problému, který jsme řešili. Jde o zcela běžný požadavek na vykreslení filtračního formuláře a výsledků vyhledávání. Problémem je, že 42,55 % času požadavku zabírá funkce pro překlad řetězců. Z grafu vidíme podezřelé chování, kdy z 80x volané funkce attributeLabels se najednou 6320x volá _tF

Je to dáno tím, že pokaždé, když někdo požaduje název atributu z modelu, jsou získány všechny a vybrán požadovaný. Stále dokola se vyhodnocuje stejný kód a výsledek je zahozen, aby se po chvilce vykonal opět znovu. Stačilo tedy přidat dočasné uložení názvů po prvním přeložení a další volání už využívaly tyto uložené hodnoty a nemuselo dojít znovu k přeložení. 

Jak je možné vidět na obrázcích níže, tato úprava snížila počet volání funkce _tF na 302 a z původní 1.03 s na vykonání nyní stačí 50.4 ms (rozdíl v počtu volání je způsoben zbylými volajícími (callers)).

Díky tomu, že Blackfire umí zpracovat konkrétní požadavek (HTTP i CLI), nemusíme ručně procházet funkce a náročně hledat a určovat místo, které nám zpomaluje celou aplikaci. Z grafu je krásně vidět postup požadavkem a nárůsty volání jednotlivých funkcí. 
Blackfire umí vyhodnocovat spotřebu času, vizualizovat práci s pamětí nebo databází, porovnávat výsledky měření i navrhovat doporučení na změnu. Nemá smysl je všechny vypisovat, raději běžte na Blackfire.io a vyzkoušejte si to na vlastní aplikaci. 

Zapojení Blakfire.io do vývoje

Instalace Blackfire není nijak náročná. Jedná se o přídavnou knihovnu PHP a je velice dobře zdokumentovaná. My jsme zvolili integraci do našeho vývojového Docker image, který používají všichni vývojáři. 

Blackfire.io nepoužíváme při každodenním vývoji. Záleží to na úkolu a povaze problému, který řešíme. Při tvorbě jednoduchých stránek nebo REST endpointů jej nevyužíváme tak často. Naopak pokud se pustíme do refaktoringu, vývoje nových backendových komponent nebo přímo do optimalizace, pak je Blackfire.io náš nejlepší přítel. 

Reference
https://pehapkari.cz/video/honza-mikes-profilovani-a-zrychlovani-aplikace-s-blackfire-io
https://blackfire.io/docs/up-and-running/index

Mohlo by se vám líbit