Prestatieanalyse en tuning ā een krachtig instrument voor het beoordelen van de prestatiegeschiktheid voor klanten.
Prestatieanalyse kan worden gebruikt om knelpunten in een programma te identificeren, waarbij een wetenschappelijke benadering wordt toegepast bij het evalueren van tuningexperimenten. Dit artikel definieert een algemene benadering voor prestatieanalyse en tuning met een voorbeeld van een webserver geschreven in Go.
Go is hier bijzonder geschikt voor, omdat het profileringsinstrumenten heeft. pprof in de standaardbibliotheek.

Strategie
Laten we een samenvattende lijst maken voor onze structurele analyse. We zullen proberen enkele gegevens te gebruiken om beslissingen te nemen in plaats van veranderingen aan te brengen op basis van intuĆÆtie of gissingen. Zo doen we dat:
- We definiƫren de grenzen van optimalisatie (vereisten);
- We berekenen de transactionele belasting voor het systeem;
- We voeren een test uit (genereren gegevens);
- We observeren;
- We analyseren ā worden alle vereisten nageleefd?
- We tuner wetenschappelijk, maken een hypothese;
- We voeren een experiment uit om deze hypothese te testen.

Architectuur van een eenvoudige HTTP-server
Voor dit artikel zullen we een kleine HTTP-server in Golang gebruiken. Alle code uit dit artikel is te vinden .
De te analyseren applicatie is een HTTP-server die PostgreSQL aanroept bij elk verzoek. Daarnaast zijn er Prometheus, node_exporter en Grafana voor het verzamelen en weergeven van metrics van de applicatie en het systeem.

Voor het gemak nemen we aan dat voor horizontale schaalvergroting (en om de berekeningen te vereenvoudigen) elke service en database samen worden uitgerold:

We definiƫren doelstellingen
In deze fase bepalen we een doel. Wat proberen we te analyseren? Hoe weten we wanneer we moeten stoppen? In dit artikel nemen we aan dat we klanten hebben en dat onze service 10.000 verzoeken per seconde moet verwerken.
In de manieren van selectie en modellering uitvoerig worden besproken. Laten we het op dezelfde manier doen, laten we modellen bouwen:
- Vertraging: 99% van de verzoeken moet binnen 60 ms worden uitgevoerd;
- Kosten: de dienst moet minimaal kosten wat wij redelijkerwijs denkbaar achten. Daarom maximaliseren we de doorvoer;
- Capaciteitsplanning: er is inzicht en documentatie nodig over hoeveel exemplaren van de applicatie moeten worden uitgevoerd, inclusief de totale schaalfunctie, evenals het aantal exemplaren dat nodig is om te voldoen aan de initiƫle vereisten voor de belasting en het waarborgen. .
Vertraging kan optimalisering vereisen in aanvulling op de analyse, maar de doorvoer moet duidelijk worden geanalyseerd. Bij het gebruiken van het SRE SLO-proces komt de vereiste vertraging van de klant en/of het bedrijf, vertegenwoordigd door de producteigenaar. En onze service zal deze verplichting vanaf het begin nakomen zonder enige aanpassingen!
Configureren van de testomgeving
Met de testomgeving kunnen we een gecontroleerde belasting op ons systeem genereren. Er worden prestatiegegevens van de webservice verzameld voor analyse.
Transactiƫle belasting
Deze omgeving maakt gebruik van om een aangepaste frequentie van HTTP-verzoeken te genereren totdat het wordt gestopt:
$ 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 reportObservatie
Tijdens de uitvoering wordt er een transactiĆŖle belasting toegepast. Naast de applicatiestatistieken (aantal verzoeken, vertraging bij antwoorden) en besturingssysteem (geheugen, CPU, IOPS) wordt er ook applicatieprofilering uitgevoerd om te begrijpen waar de problemen zich bevinden en hoe de CPU-tijd wordt verbruikt.
Profilering
Profilering is een type meting dat laat zien waar de CPU-tijd tijdens de werking van de applicatie heen gaat. Het maakt het mogelijk om precies te bepalen waar en hoeveel CPU-tijd wordt verbruikt:

Deze gegevens kunnen tijdens de analyse worden gebruikt om inzicht te krijgen in de nutteloos verkwiste CPU-tijd en de onnodige werkzaamheden die worden uitgevoerd. Go (pprof) kan profielen genereren en visualiseren in de vorm van een flame graph, met behulp van de standaardset van gereedschappen. Ik zal hun gebruik en de handleiding voor configuratie hieronder in het artikel bespreken.
Uitvoering, observatie, analyse.
Laten we een experiment uitvoeren. We gaan uitvoeren, observeren en analyseren totdat de prestaties naar wens zijn. We kiezen willekeurig een lage belasting om deze te gebruiken voor de eerste waarnemingen. Bij elke volgende stap verhogen we de belasting met een bepaalde schalingfactor, gekozen met een bepaalde spreiding. Elke uitvoering van de belastingstest wordt gedaan met aanpassing van het aantal verzoeken: make load-test LOAD_TEST_RATE=X.
50 verzoeken per seconde

Let op de twee bovenste grafieken. De linker bovenste toont aan dat onze applicatie 50 verzoeken per seconde verwerkt (volgens zijn mening), terwijl de rechter bovenste de duur van elk verzoek weergeeft. Beide parameters helpen ons te kijken en te analyseren: passen we binnen onze prestatiegrenzen of niet. De rode lijn op de grafiek HTTP Request Latency toont een SLO van 60ms. Uit de lijn blijkt dat we ver onder onze maximale responstijd zitten.
Laten we kijken vanuit het kostenperspectief:
10000 verzoeken per seconde / 50 verzoeken per server = 200 servers + 1
We kunnen deze waarde nog steeds verbeteren.
500 verzoeken per seconde
Interessantere dingen beginnen te gebeuren wanneer de belasting 500 verzoeken per seconde bedraagt:

Op de linker bovenste grafiek is te zien dat de applicatie een normale belasting vastlegt. Als dat niet het geval is, is er een probleem met de server waarop de applicatie draait. De grafiek met de responstijd rechtsboven geeft aan dat 500 verzoeken per seconde leiden tot een responstijd van 25-40ms. De 99e percentiel past nog steeds mooi binnen de eerder gekozen SLO van 60ms.
Vanuit het kostenperspectief:
10000 verzoeken per seconde / 500 verzoeken per server = 20 servers + 1
Er kan nog steeds verbetering plaatsvinden.
1000 verzoeken per seconde

Geweldige lancering! De applicatie geeft aan dat het 1000 verzoeken per seconde heeft verwerkt, maar de grens voor de vertraging werd overschreden aan de SLO-kant. Dit is te zien aan de p99-lijn op de rechter bovenste grafiek. Hoewel de p100-lijn veel hoger is, zijn de werkelijke vertragingen boven het maximum van 60ms. Laten we de profilering induiken om te ontdekken wat de applicatie eigenlijk aan het doen is.
Profilering
Voor de profilering zetten we de belasting op 1000 verzoeken per seconde en gebruiken dan pprof om gegevens te verzamelen om te ontdekken waar de applicatie CPU-tijd besteedt. Dit kan worden gedaan door de HTTP-endpoint te activeren. pprof, waarna de resultaten bij belasting worden opgeslagen met curl:
$ curl http://localhost:8080/debug/pprof/profile?seconds=29 > cpu.1000_reqs_sec_no_optimizations.profDe resultaten kunnen als volgt worden weergegeven:
$ go tool pprof -http=:12345 cpu.1000_reqs_sec_no_optimizations.prof
De grafiek toont waar en hoeveel CPU-tijd de applicatie verbruikt. Volgens de beschrijving van :
Op de X-as staat de profielopvulling, alfabetisch gesorteerd (dit is geen tijd), de Y-as toont de diepte van de stack, beginnend vanaf nul in [top]. Elk rechthoekje is een stackframe. Hoe breder het frame, hoe vaker het voorkomt in de stacks. Wat bovenaan staat, draait op de CPU, en daaronder zijn de kindelementen. Kleuren betekenen meestal niets en worden eenvoudigweg willekeurig gekozen om de frames te onderscheiden.
Analyse - hypothese
Voor de configuratie concentreren we ons op het zoeken naar nutteloze CPU-tijdverspilling. We zullen de grootste bronnen van nutteloze verspilling identificeren en verwijderen. Houd er rekening mee dat profiling heel nauwkeurig onthult waar de applicatie zijn CPU-tijd besteedt, en dat het mogelijk is dit meerdere keren moet gebeuren, en dat we ook de broncode van de applicatie zullen moeten aanpassen, tests herstarten en observeren hoe de prestaties dichter bij de beoogde komen.
Volgend de richtlijnen van Brendan Gregg lezen we de grafiek van boven naar beneden. Elke regel toont een stackframe (functieaanroep). De eerste regel is het startpunt van het programma, de ouder van alle andere aanroepen (met andere woorden, alle andere aanroepen zullen deze in hun stack hebben). De volgende regel is al anders:
![]()
Als je met de muis over de functienaam in de grafiek gaat, wordt de totale tijd weergegeven die deze in de stack was tijdens het debuggen. De functie HTTPServe was daar 65% van de tijd, andere functies van de runtime, runtime.mcall, mstart en gc, namen de rest van de tijd in beslag. Een interessant feit: 5% van de totale tijd werd besteed aan DNS-verzoeken:

De adressen die het programma zoekt, behoren tot Postgresql. Klik op FindByAge:

Opmerkelijk, het programma toont aan dat er in wezen drie belangrijke bronnen zijn die vertragingen veroorzaken: het openen en sluiten van verbindingen, het opvragen van gegevens en het verbinden met de database. Op de grafiek is te zien dat DNS-verzoeken, het openen en sluiten van verbindingen ongeveer 13% van de totale uitvoeringstijd in beslag nemen.
Hypothese: Herbruik van verbindingen via een pool moet de tijd voor ƩƩn HTTP-verzoek verminderen, wat zorgt voor een hogere bandbreedte en lagere latencies..
Applicatie-instelling - experiment
We updaten de broncode en proberen de verbinding met PostgreSQL voor elk verzoek te verwijderen. Eerste optie - het gebruik van op applicatieniveau. In dit experiment gaan we verbindingspool instellen met behulp van de SQL-driver voor Go:
db, err := sql.Open("postgres", dbConnectionString)
db.SetMaxOpenConns(8)
if err != nil {
return nil, err
}Uitvoering, observatie, analyse
Na het herstarten van de test met 1000 verzoeken per seconde is het duidelijk dat p99 van de latenties binnen de norm van de SLO van 60 ms is gekomen!
Wat zijn de kosten?
10000 verzoeken per seconde / op 1000 verzoeken per server = 10 servers + 1
Laten we het nog beter maken!
2000 verzoeken per seconde

Het verdubbelen van de belasting toont hetzelfde, de grafiek linksboven laat zien dat de applicatie in staat is 2000 verzoeken per seconde aan te kunnen, p100 lager dan 60 ms, p99 voldoet aan de SLO.
Vanuit het kostenperspectief:
10000 verzoeken per seconde / op 2000 verzoeken per server = 5 servers + 1
3000 verzoeken per seconde

Hier kan de applicatie 3000 verzoeken verwerken met een p99-latentie van minder dan 60 ms. De SLO wordt niet overschreden, en de kosten zijn als volgt:
10000 verzoeken per seconde / op 3000 verzoeken per server = 4 servers + 1 (de auteur heeft afgerond naar boven, opmerking van de vertaler)
Laten we nog een ronde van analyse proberen.
Analyse - hypothese
We verzamelen en tonen de resultaten van de applicatiedebugging bij 3000 verzoeken per seconde:

Nog steeds wordt 6% van de tijd besteed aan het opzetten van verbindingen. De configuratie van de pool heeft de prestaties verbeterd, maar het is nog steeds duidelijk dat de applicatie door blijft gaan met het maken van nieuwe verbindingen met de database.
Hypothese: Verbinden, ondanks dat er een pool is, worden nog steeds afgewezen en gewist, waardoor de applicatie ze opnieuw moet opzetten. Het instellen van het aantal wachtende verbindingen op de grootte van de pool moet de latentie helpen verminderen door de tijd te minimaliseren die de applicatie nodig heeft om een verbinding te maken..
Applicatie-instelling - experiment
We proberen te stellen gelijk aan de grootte van de pool (ook beschreven ):
db, err := sql.Open("postgres", dbConnectionString)
db.SetMaxOpenConns(8)
db.SetMaxIdleConns(8)
if err != nil {
return nil, err
}Uitvoering, observatie, analyse
3000 verzoeken per seconde

p99 minder dan 60 ms met een aanzienlijk lagere p100!

Controle van de flame graph toont aan dat het opzetten van verbindingen niet meer merkbaar is! Laten we verder controleren. pg(*conn).query - we merken ook hier geen opzet van een verbinding.

Conclusie
Prestatieanalyse is cruciaal om te begrijpen of de verwachtingen van klanten en niet-functionele vereisten zijn vervuld. Analyse door observaties te vergelijken met klantverwachtingen kan helpen vast te stellen wat aanvaardbaar is en wat niet. Go biedt effectieve tools, ingebouwd in de standaardbibliotheek, waarmee analyse eenvoudig en toegankelijk kan worden gemaakt.
Bron: habr.com
