SRE: Ydelsesanalyse. En opsætning ved hjælp af en simpel webserver på Go

Ydelsesanalyse og optimering — et kraftfuldt værktøj til at teste ydeevne for kunder.

Ydelsesanalyse kan bruges til at identificere flaskehalsene i programmet ved at anvende en videnskabelig tilgang til test af konfigurationseksperimenter. Denne artikel definerer en generel tilgang til ydelsesanalyse og optimering ved at bruge en webserver skrevet i Go som eksempel.

Go er særligt velegnet til dette, da det har profileringsværktøjer pprof i standardbiblioteket.

SRE: Ydelsesanalyse. En opsætning ved hjælp af en simpel webserver på Go

Strategi

Lad os oprette en opsummeringsliste til vores strukturelle analyse. Vi vil forsøge at bruge nogle data til at træffe beslutninger i stedet for at foretage ændringer baseret på intuition eller gætterier. For dette gør vi følgende:

  • Definerer grænserne for optimering (krav);
  • Beregner den transaktionelle belastning for systemet;
  • Udfører testen (opretter data);
  • Observerer;
  • Analyserer — opfyldes alle kravene?
  • Optimerer videnskabeligt, laver en hypotese;
  • Udfører eksperimentet for at teste denne hypotese.

SRE: Ydelsesanalyse. En opsætning ved hjælp af en simpel webserver på Go

Arkitektur af en simpel HTTP-server

I denne artikel vil vi bruge en lille HTTP-server i Golang. Al koden fra denne artikel kan findes her.

Den analyserede applikation er en HTTP-server, der forespørger Postgresql ved hver anmodning. Desuden er der Prometheus, node_exporter og Grafana til at indsamle og vise applikationens og systemets metrics.

SRE: Ydelsesanalyse. En opsætning ved hjælp af en simpel webserver på Go

For enkelhedens skyld antager vi, at hver service og database implementeres sammen for horisontal skalerbarhed (og forenkling af beregninger):

SRE: Ydelsesanalyse. En opsætning ved hjælp af en simpel webserver på Go

Definerer mål

I dette trin identificerer vi målet. Hvad forsøger vi at analysere? Hvordan ved vi, hvornår det er tid til at stoppe? I denne artikel vil vi antage, at vi har kunder, og at vores service vil håndtere 10.000 anmodninger pr. sekund.

I Google SRE Book gennemgår detaljerede metoder til valg og modellering. Lad os gøre det samme, bygge modeller:

  • Forsinkelse: 99% af anmodningerne skal behandles på mindre end 60 ms;
  • Omkostninger: tjenesten skal forbruge det mindst mulige beløb, som vi finder rimeligt. For dette maksimerer vi gennemstrømningen;
  • Kapacitetsplanlægning: det kræver forståelse og dokumentation af, hvor mange instanser af applikationen der skal køres, inklusive den samlede skaleringsfunktion, samt hvor mange instanser der er nødvendige for at opfylde de indledende belastningskrav og sikre overflødighed n+1.

Forsinkelsen kan kræve optimering udover analysen, men båndbredden skal åbenlyst analyseres. Ved brug af SRE SLO-proceduren kommer kravet om forsinkelse fra kunden og/eller virksomheden, repræsenteret af produkt ejeren. Vores service vil opfylde dette krav fra starten uden nogen justeringer!

Konfigurere testmiljø

Med testmiljøet kan vi generere en kontrolleret belastning på vores system. Der vil blive oprettet data om webservice-ydeevne til analysen.

Transaktionel belastning

Dette miljø bruger Vegeta til at skabe en konfigurerbar HTTP-anmodningsfrekvens, indtil det stoppes:

$ 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 report

Overvågning

Under kørslen vil der blive anvendt en transaktionel belastning. Ud over applikationsmålinger (antal anmodninger, svarforsinkelse) og operativsystemet (hukommelse, CPU, IOPS) vil applikationens profilering blive udført for at forstå, hvor problemerne er, samt hvordan CPU-tid forbruges.

Profilering

Profilering er en målingstype, der giver mulighed for at se, hvor CPU-tiden går under applikationens drift. Det gør det muligt at bestemme hvor præcist og hvor meget CPU-tid der bruges:

SRE: Ydelsesanalyse. En opsætning ved hjælp af en simpel webserver på Go

Disse data kan bruges under analysen til at få indsigt i unødvendigt brugt CPU-tid og udført unødvendigt arbejde. Go (pprof) kan generere profiler og visualisere dem som flame graph ved hjælp af det standardværktøjssæt. Jeg vil fortælle om deres brug og opsætningsvejledning lidt længere nede i artiklen.

Udførelse, overvågning, analyse.

Lad os udføre et eksperiment. Vi vil udføre, observere og analysere indtil vi er tilfredse med præstationen. Vi vælger vilkårligt en lav belastning for at anvende den til de første observationers resultater. Ved hvert næste trin vil vi øge belastningen med en skaleringsfaktor, der er valgt med en vis variation. Hver kørsel af belastningstestingen udføres med justering af antallet af anmodninger: make load-test LOAD_TEST_RATE=X.

50 anmodninger pr. sekund

SRE: Ydelsesanalyse. En opsætning ved hjælp af en simpel webserver på Go

Bemærk de to øverste grafer. Den øverste venstre viser, at vores applikation behandler 50 anmodninger pr. sekund (ifølge den), og den øverste højre viser varigheden af hver anmodning. Begge parametre hjælper os med at se og analysere: om vi holder os inden for vores præstationsgrænser eller ej. Den røde linje på grafen HTTP Request Latency viser SLO på 60ms. På linjen kan vi se, at vi ligger langt under vores maksimale svartid.

Lad os se på omkostningerne:

10000 anmodninger pr. sekund / på 50 anmodninger pr. server = 200 servere + 1

Vi kan stadig forbedre dette tal.

500 anmodninger pr. sekund

Mere interessante ting begynder at ske, når belastningen bliver 500 anmodninger pr. sekund:

SRE: Ydelsesanalyse. En opsætning ved hjælp af en simpel webserver på Go

Igen ses det på den øverste venstre graf, at applikationen registrerer en normal belastning. Hvis ikke, er der et problem med serveren, som applikationen kører på. Grafen med svartid er placeret øverst til højre, som viser, at 500 anmodninger pr. sekund førte til en svartid på 25-40ms. 99. percentil passer stadig glimrende ind i SLO på 60ms, valgt ovenfor.

Set fra en omkostningsvinkel:

10000 anmodninger pr. sekund / på 500 anmodninger pr. server = 20 servere + 1

Der er stadig plads til forbedring.

1000 anmodninger pr. sekund

SRE: Ydelsesanalyse. En opsætning ved hjælp af en simpel webserver på Go

Fremragende kørsel! Applikationen viser, at den har behandlet 1000 anmodninger pr. sekund, men grænsen for svartid blev overskredet i henhold til SLO. Dette kan ses på p99-linjen i den øverste højre graf. Selvom p100-linjen er meget højere, er de reelle forsinkelser over maksimum på 60ms. Lad os dykke ned i profileringen for at finde ud af, hvad applikationen rent faktisk gør.

Profilering

Til profileringen indstiller vi belastningen til 1000 anmodninger pr. sekund, og derefter bruger vi pprof til at indsamle data for at finde ud af, hvor applikationen bruger CPU-tid. Dette kan gøres ved at aktivere HTTP endpoint pprof, hvorefter resultaterne gemmes under belastning ved hjælp af curl:

$ curl http://localhost:8080/debug/pprof/profile?seconds=29 > cpu.1000_reqs_sec_no_optimizations.prof

Resultaterne kan vises som:

$ go tool pprof -http=:12345 cpu.1000_reqs_sec_no_optimizations.prof

SRE: Ydelsesanalyse. En opsætning ved hjælp af en simpel webserver på Go

Grafen viser, hvor meget processor tid applikationen bruger. Beskrivelsen fra Brendan Gregg:

På X-aksen er stakprofilen sorteret i alfabetisk rækkefølge (dette er ikke tid), Y-aksen viser stakdybden, tællende fra nul i [top]. Hver rektangel er et stakramme. Jo bredere rammen er, jo oftere optræder den i stakke. Det, der er øverst, kører på CPU, og de nedenunder er under elementerne. Farverne har generelt ingen betydning og vælges tilfældigt for at differentiere rammen.

Analyse - hypotese

Til konfigurationen vil vi fokusere på at forsøge at finde unødvendige forbrug af processortid. Vi vil lede efter de største kilder til unødvendigt forbrug og fjerne dem. Og da profilering ganske nøjagtigt viser, hvor applikationen bruger sin processortid, kan det blive nødvendigt at gøre dette flere gange, samt ændre kildekoden til applikationen, genstarte testene og observere, at ydeevnen nærmer sig det ønskede.

I henhold til anbefalingerne fra Brendan Gregg vil vi læse grafen ovenfra og ned. Hver linje viser et stakramme (funktionsopkald). Den første linje er indgangspunktet for programmet, forælder til alle andre opkald (med andre ord, alle andre opkald vil have den i deres stak). Den næste linje er allerede forskellig:

SRE: Ydelsesanalyse. En opsætning ved hjælp af en simpel webserver på Go

Hvis du holder musen over funktionsnavnet på grafen, vises den samlede tid, den har været i stakken under fejlfinding. Funktionen HTTPServe har været der 65% af tiden, og de andre runtime-funktioner, runtime.mcall, mstart og gc, tog den resterende tid. Interessant nok: 5% af den samlede tid er brugt på DNS-forespørgsler:

SRE: Ydelsesanalyse. En opsætning ved hjælp af en simpel webserver på Go

De adresser, som programmet søger, tilhører Postgresql. Klik på FindByAge:

SRE: Ydelsesanalyse. En opsætning ved hjælp af en simpel webserver på Go

Morsomt, programmet viser, at der grundlæggende er tre hovedkilder, der tilføjer forsinkelser: åbning/lukning af forbindelser, datatræk og forbindelse til databasen. Grafen viser, at DNS-forespørgsler, åbning og lukning af forbindelser tager omkring 13% af den samlede køretid.

Hypotese: Genbrug af forbindelser via en pulje bør reducere tiden for en enkelt HTTP-anmodning, hvilket muliggør højere båndbredde og lavere forsinkelser..

Applikationskonfiguration — eksperiment.

Vi opdaterer kildekoden og forsøger at fjerne forbindelsen til PostgreSQL for hver anmodning. Første version — brugen af forbindelsespuljen på applikationsniveau. I dette eksperiment vil vi configurere forbindelsespuljen ved hjælp af SQL-driveren til Go:

db, err := sql.Open("postgres", dbConnectionString)
db.SetMaxOpenConns(8)

if err != nil {
   return nil, err
}

Udførelse, overvågning, analyse.

Efter genstart af testen med 1000 anmodninger pr. sekund ses det, at p99 forsinkelserne er normaliseret med SLO på 60ms!

Hvad med omkostningerne?

10000 anmodninger pr. sekund / på 1000 anmodninger per server = 10 servere + 1

Lad os gøre det endnu bedre!

2000 anmodninger pr. sekund.

SRE: Ydelsesanalyse. En opsætning ved hjælp af en simpel webserver på Go

Fordoble belastningen viser det samme, den øverste venstre graf demonstrerer, at applikationen kan håndtere 2000 anmodninger pr. sekund, p100 under 60ms, p99 opfylder SLO.

Set fra en omkostningsvinkel:

10000 anmodninger pr. sekund / på 2000 anmodninger per server = 5 servere + 1

3000 anmodninger pr. sekund.

SRE: Ydelsesanalyse. En opsætning ved hjælp af en simpel webserver på Go

Her kan applikationen håndtere 3000 anmodninger med p99 forsinkelse under 60ms. SLO overholdes, og omkostningerne er forstået som:

10000 anmodninger pr. sekund / på 3000 anmodninger per server = 4 servere + 1 (forfatteren har afrundet opad, oversætterens bemærkning)

Lad os forsøge en runde analyse til.

Analyse - hypotese

Vi samler og viser resultaterne fra fejlfindingen af applikationen ved 3000 anmodninger pr. sekund:

SRE: Ydelsesanalyse. En opsætning ved hjælp af en simpel webserver på Go

Der bruges stadig 6% af tiden på at etablere forbindelser. Konfigurationen af puljen har forbedret ydeevnen, men det er stadig klart, at applikationen fortsætter med at oprette nye forbindelser til databasen.

Hypotese: Forbindelser, trods tilstedeværelsen af en pulje, bliver stadig afvist og ryddet op, så applikationen skal geninstallere dem. At sætte antallet af ventende forbindelser til puljens størrelse bør hjælpe med forsinkelsen ved at minimere den tid, applikationen bruger på at oprette forbindelse..

Applikationskonfiguration — eksperiment.

Vi forsøger at indstille MaxIdleConns til puljens størrelse (også beskrevet her):

db, err := sql.Open("postgres", dbConnectionString)
db.SetMaxOpenConns(8)
db.SetMaxIdleConns(8)
if err != nil {
   return nil, err
}

Udførelse, overvågning, analyse.

3000 anmodninger pr. sekund.

SRE: Ydelsesanalyse. En opsætning ved hjælp af en simpel webserver på Go

p99 under 60ms med betydeligt lavere p100!

SRE: Ydelsesanalyse. En opsætning ved hjælp af en simpel webserver på Go

Flame-grafen viser, at oprettelse af forbindelse ikke længere er mærkbar! Vi kontrollerer nærmere. pg(*conn).query — vi bemærker heller ikke oprettelsen af forbindelse her.

SRE: Ydelsesanalyse. En opsætning ved hjælp af en simpel webserver på Go

Konklusion

Ydelseanalyse er afgørende for at forstå, om kunde forventninger og ikke-funktionelle krav er tilfredsstillet. Analysen gennem at sammenligne observationerne med kundernes forventninger kan hjælpe med at fastlægge, hvad der er acceptabelt, og hvad der ikke er. Go tilbyder effektive værktøjer, der er indbygget i standardbiblioteket, som gør analysen enkel og tilgængelig.

Kilde: habr.com

Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere 🔥 Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere | ProHoster