Analýza výkonu a optimalizácia – výkonný nástroj na overenie zhodnosti výkonu pre klientov.
Analýzu výkonu je možné využiť na identifikáciu úzkych miest v programe, pričom použijeme vedecký prístup k overovaniu pokusov o optimalizáciu. Tento článok určuje všeobecný prístup k analýze výkonu a optimalizácii na príklade webového servera na Go.
Go je tu obzvlášť vhodné, pretože obsahuje nástroje na profilovanie. pprof v štandardnej knižnici.

Stratégia
Vytvorme súhrnný zoznam pre našu štrukturálnu analýzu. Pokúsime sa využiť niektoré údaje na rozhodovanie namiesto toho, aby sme vykonávali zmeny na základe intuícií alebo domnienok. Urobíme to takto:
- Definujeme hranice optimalizácie (požiadavky);
- Vypočítame transakčnú záťaž pre systém;
- Vykonáme test (vytvoríme údaje);
- Pozorujeme;
- Analyzujeme – dodržiavajú sa všetky požiadavky?
- Optimalizujeme vedecky, formulujeme hypotézu;
- Vykonáme experiment na overenie tejto hypotézy.

Architektúra jednoduchého HTTP servera
Pre tento článok budeme používať malý HTTP server na Golangu. Celý kód z tohto článku nájdete .
Analyzovaná aplikácia je HTTP server, ktorý dotazuje PostgreSQL na každý požiadavok. Okrem toho sú k dispozícii Prometheus, node_exporter a Grafana na zhromažďovanie a zobrazenie metrik aplikácie a systému.

Na zjednodušenie predpokladajme, že pre horizontálne škálovanie (a zjednodušenie výpočtov) sú každý servis a databáza nasadené spolu:

Definujeme ciele
V tomto kroku definujeme cieľ. Čo sa snažíme analyzovať? Ako zistíme, kedy skončiť? V tomto článku predpokladáme, že máme klientov a že náš servis bude zpracovávať 10 000 požiadaviek za sekundu.
V podrobne sú prebrané spôsoby voľby a modelovania. Postupujeme rovnako, vytvoríme modely:
- Zdržanie: 99 % požiadaviek by sa malo vykonať za menej ako 60 ms;
- Náklady: služba by mala spotrebovať minimálnu sumu peňazí, ktorú považujeme za rozumne možnú. Na to maximalizujeme priepustnosť;
- Plánovanie kapacít: je potrebné pochopiť a zdokumentovať, koľko inštancií aplikácie bude potrebné spustiť, vrátane celkovej funkcie škálovania, ako aj koľko inštancií bude potrebných na splnenie počiatočných požiadaviek na záťaž a zabezpečenie .
Zdržanie môže vyžadovať optimalizáciu vedľa analýzy, no priepustnosť je potrebné jasne analyzovať. Pri použití procesu SRE SLO požiadavka na zdržanie vychádza od klienta a/alebo podnikania, zastúpeného vlastníkom produktu. A naša služba bude plniť tento záväzok od samého začiatku bez akýchkoľvek nastavení!
Nastavenie testovacieho prostredia
S pomocou testovacieho prostredia budeme môcť generovať dávkovú záťaž na našom systéme. Na analýzu sa budú vytvárať údaje o výkonnosti webovej služby.
Transakčná záťaž
Toto prostredie používa na vytvorenie prispôsobenej frekvencie požiadaviek cez HTTP, kým sa nezastaví:
$ make load-test LOAD_TEST_RATE=50
echo "POST http://localhost:8080" | vegeta attack -body tests/fixtures/age_no_match.json -rate=50 -duration=0 | tee results.bin | vegeta reportSledovanie
Počas vykonávania bude aplikovaná transakčná záťaž. Okrem metriky aplikácie (počet požiadaviek, zdržanie pri odpovedi) a operačného systému (pamäť, CPU, IOPS) sa spustí profilovanie aplikácie, aby sme pochopili, kde sú v nej problémy, ako aj ako sa spotrebúva procesorový čas.
Profilovanie
Profilovanie je typ merania, ktorý umožňuje vidieť, kam ide procesorový čas pri práci aplikácie. Umožňuje určiť, kde presne a koľko ide procesorového času:

Tieto údaje môžu byť použité počas analýzy, aby sa získal prehľad o zbytočne vynaloženom procesorovom čase a vykonávanej zbytočnej práci. Go (pprof) môže generovať profily a vizualizovať ich ako flame graph, pomocou štandardnej sady nástrojov. O ich používaní a sprievodcovi nastavením sa dozviete nižšie v článku.
Vykonávanie, sledovanie, analýza.
Urobíme experiment. Budeme vykonávať, pozorovať a analyzovať, kým nebudeme s výkonnosťou spokojní. Zvolíme náhodne nízku úroveň záťaže, aby sme ju použili na získanie výsledkov prvých pozorovaní. Na každom ďalšom kroku budeme zvyšovať záťaž s určitým koeficientom škálovania, vybraným s určitým rozptýlením. Každé spustenie záťažového testovania sa vykonáva s úpravou počtu požiadaviek: make load-test LOAD_TEST_RATE=X.
50 požiadaviek za sekundu

Všimnite si dva horné grafy. Horný ľavý ukazuje, že naša aplikácia spracováva 50 požiadaviek za sekundu (podľa jej názoru), a horný pravý — trvanie každej požiadavky. Oba parametre nám pomáhajú sledovať a analyzovať: či sa zmestíme do našich hraníc výkonu alebo nie. Červená línia na grafe HTTP Request Latency ukazuje SLO na 60 ms. Na línii je vidieť, že sme omnoho pod naším maximálnym časom odpovede.
Pozrime sa z pohľadu nákladov:
10000 požiadaviek za sekundu / na 50 požiadaviek na server = 200 serverov + 1
Stále môžeme tento ukazovateľ zlepšiť.
500 požiadaviek za sekundu
Zaujímavé veci sa začínajú diať, keď záťaž dosiahne 500 požiadaviek za sekundu:

Opäť na hornom ľavom grafe je vidieť, že aplikácia zaznamenáva bežnú záťaž. Ak to tak nie je — je problém na serveri, na ktorom je aplikácia spustená. Graf s oneskorením odpovede sa nachádza hore vpravo, ukazuje, že 500 požiadaviek za sekundu viedlo k oneskoreniu odpovede v rozmedzí 25-40 ms. 99. percentil sa stále skvele zmestí do SLO 60 ms, vybraného vyššie.
Z pohľadu nákladov:
10000 požiadaviek za sekundu / na 500 požiadaviek na server = 20 serverov + 1
Stále je priestor na zlepšenie.
1000 požiadaviek za sekundu

Skvelý štart! Aplikácia ukazuje, že spracovala 1000 požiadaviek za sekundu, avšak hranica oneskorenia bola prekročená zo strany SLO. To je vidieť na línii p99 na hornom pravom grafe. Napriek tomu, že línia p100 je omnoho vyššia, skutočné oneskorenia sú nad maximom 60 ms. Poďme sa ponoriť do profilovania, aby sme zistili, čo aplikácia naozaj robí.
Profilovanie
Na profilovanie nastavíme záťaž na 1000 požiadaviek za sekundu, potom použijeme pprof na zhromaždenie údajov, aby sme zistili, kde aplikácia trávi procesorový čas. To sa dá urobiť aktivovaním HTTP endpointu pprof, po ktorom pri zaťažení uložíme výsledky pomocou curl:
$ curl http://localhost:8080/debug/pprof/profile?seconds=29 > cpu.1000_reqs_sec_no_optimizations.profVýsledky môžu byť zobrazené takto:
$ go tool pprof -http=:12345 cpu.1000_reqs_sec_no_optimizations.prof
Graf zobrazuje, kde a koľko procesorového času aplikácia utráca. Z opisu od :
Na osi X je vyplnenie profilu zásobníka, zoradené abecedne (to nie je čas), os Y zobrazuje hĺbku zásobníka, počítanú od nuly v [top]. Každý rectangle je rámec zásobníka. Čím širší rámec, tým častejšie sa vyskytuje v zásobníkoch. To, čo je navrch — pracuje na CPU, a nižšie — podriadené prvky. Farby zvyčajne nič neznamenajú, iba sa náhodne vyberajú na odlíšenie rámcov.
Analýza — hypotéza
Pre nastavenie sa zameriame na hľadanie zbytočného utrácania procesorového času. Budeme hľadať najväčšie zdroje zbytočných výdavkov a odstraňovať ich. Berúc do úvahy, že profilovanie veľmi presne odhaľuje, kde presne aplikácia utráca svoj procesorový čas, možno to bude potrebné zopakovať niekoľkokrát a tiež bude potrebné upraviť zdrojový kód aplikácie, reštartovať testy a sledovať, či sa výkon približuje k plánovanej hodnote.
Podľa odporúčaní Brendana Gregga budeme čítať graf zhora nadol. Každý riadok zobrazuje rámec zásobníka (volanie funkcie). Prvý riadok je vstupný bod do programu, rodič všetkých ostatných volaní (inými slovami, všetky ostatné volania ho budú mať vo svojom zásobníku). Ďalší riadok sa už líši:
![]()
Ak umiestnite kurzor na názov funkcie na grafe — zobrazuje sa celkový čas, počas ktorého bola v zásobníku počas ladenia. Funkcia HTTPServe tam bola 65% času, ostatné funkcie runtime, runtime.mcall, mstart a gc, zaberali zvyšok času. Zaujímavý fakt: 5% celkového času bolo vynaložených na požiadavky na DNS:

Adresy, ktoré program hľadá, patria Postgresql. Klikneme na FindByAge:

Zaujímavé, program ukazuje, že v zásade existujú tri hlavné zdroje, ktoré pridávajú oneskorenia: otváranie a zatváranie pripojení, dotazy na údaje a pripojenie k databáze. Na grafe je vidieť, že požiadavky na DNS, otváranie a zatváranie pripojení zaberajú približne 13% celkového času vykonania.
Hipotéza: Znovupoužitie spojení prostredníctvom poolu by malo znížiť čas jedného HTTP požiadavku, čo umožní vyššiu priepustnosť a nižšie oneskorenia..
Nastavenie aplikácie — experiment.
Aktualizujeme zdrojový kód, snažíme sa odstrániť spojenie s PostgreSQL pri každom požiadavku. Prvá možnosť — použitie na úrovni aplikácie. V tomto experimente pool spojení pomocou ovládača SQL pre Go:
db, err := sql.Open("postgres", dbConnectionString)
db.SetMaxOpenConns(8)
if err != nil {
return nil, err
}Realizácia, monitoring, analýza.
Po opätovnom spustení testu s 1000 požiadavkami za sekundu je zrejmé, že p99 latencie sa vrátil do normálu so SLO 60ms!
A čo cena?
10000 požiadaviek za sekundu / 1000 požiadaviek na server = 10 serverov + 1
Poďme to ešte zlepšiť!
2000 požiadaviek za sekundu.

Zdvojenie zaťaženia ukazuje to isté, ľavý horný graf naznačuje, že aplikácia dokáže spracovať 2000 požiadaviek za sekundu, p100 je pod 60ms, p99 spĺňa SLO.
Z pohľadu nákladov:
10000 požiadaviek za sekundu / 2000 požiadaviek na server = 5 serverov + 1
3000 požiadaviek za sekundu.

Aplikácia tu dokáže spracovať 3000 požiadaviek so oneskorením p99 menej ako 60ms. SLO nie je porušené, a cena bola stanovená takto:
10000 požiadaviek za sekundu / 3000 požiadaviek na server = 4 servery + 1 (autor zaokrúhľuje na väčšie, pozn. prekladateľa)
Poďme vyskúšať ešte jedno kolo analýzy.
Analýza — hypotéza
Zhromažďujeme a zobrazujeme výsledky ladenia aplikácie pri 3000 požiadavkách za sekundu:

Stále sa 6% času trávi vytváraním spojení. Nastavenie poolu zlepšilo výkon, ale stále je viditeľné, že aplikácia sa naďalej zaoberá vytváraním nových spojení s databázou.
Hipotéza: Spojenia, napriek existencii poolu, sú stále zamietané a čistené, takže aplikácia ich musí znovu vytvárať. Nastavenie počtu čakajúcich spojení rovná sa veľkosti poolu by malo pomôcť s oneskorením tým, že minimalizuje čas, ktorý aplikácia strávi vytváraním spojenia..
Nastavenie aplikácie — experiment.
Snažíme sa nastaviť rovnému rozmeru poolu (tiež je opísané ):
db, err := sql.Open("postgres", dbConnectionString)
db.SetMaxOpenConns(8)
db.SetMaxIdleConns(8)
if err != nil {
return nil, err
}Realizácia, monitoring, analýza.
3000 požiadaviek za sekundu.

p99 je menej ako 60ms s výrazne menším p100!

Kontrola flame grafu ukazuje, že nastavenie spojenia už nie je viditeľné! Skontrolujeme podrobnejšie pg(*conn).query — tu tiež nezaznamenávame nastavenie spojenia.

Záver
Analýza výkonu je kľúčová pre pochopenie toho, či sú splnené očakávania zákazníkov a nefunkčné požiadavky. Analýza porovnávaním pozorovaní s očakávaniami zákazníkov môže pomôcť určiť, čo je prijateľné a čo nie. Go poskytuje účinné nástroje zabudované v štandardnej knižnici, pomocou ktorých môže byť analýza jednoduchá a dostupná.
Zdroj: habr.com
