SRE: Analiza wydajności. Metoda konfiguracji przy użyciu prostego serwera WWW w Go

Analiza i dostrajanie wydajności to potężne narzędzie do sprawdzania zgodności wydajności dla klientów.

Analizę wydajności można wykorzystać do sprawdzenia wąskich gardeł w programie, stosując naukowe podejście do testowania eksperymentów dostrajających. W tym artykule zdefiniowano ogólne podejście do analizy i dostrajania wydajności na przykładzie serwera WWW Go.

Go jest tutaj szczególnie dobry, ponieważ ma narzędzia do profilowania pprof w standardowej bibliotece.

SRE: Analiza wydajności. Metoda konfiguracji przy użyciu prostego serwera WWW w Go

strategia

Utwórzmy listę podsumowującą dla naszej analizy strukturalnej. Postaramy się wykorzystać pewne dane do podejmowania decyzji, zamiast wprowadzać zmiany w oparciu o intuicję lub domysły. Aby to zrobić, zrobimy to:

  • Określamy granice optymalizacji (wymagania);
  • Obliczamy obciążenie transakcyjne systemu;
  • Wykonujemy test (tworzymy dane);
  • Obserwujemy;
  • Analizujemy – czy wszystkie wymagania zostały spełnione?
  • Ustalamy to naukowo, stawiamy hipotezę;
  • Przeprowadzamy eksperyment, aby sprawdzić tę hipotezę.

SRE: Analiza wydajności. Metoda konfiguracji przy użyciu prostego serwera WWW w Go

Prosta architektura serwera HTTP

W tym artykule użyjemy małego serwera HTTP w Golang. Można znaleźć cały kod z tego artykułu tutaj.

Analizowana aplikacja to serwer HTTP, który odpytuje Postgresql przy każdym żądaniu. Dodatkowo istnieją Prometheus, node_exporter i Grafana do gromadzenia i wyświetlania wskaźników aplikacji i systemu.

SRE: Analiza wydajności. Metoda konfiguracji przy użyciu prostego serwera WWW w Go

Dla uproszczenia uważamy, że w przypadku skalowania poziomego (i upraszczania obliczeń) każda usługa i baza danych są wdrażane razem:

SRE: Analiza wydajności. Metoda konfiguracji przy użyciu prostego serwera WWW w Go

Definiowanie celów

Na tym etapie ustalamy cel. Co próbujemy analizować? Po czym poznajemy, że nadszedł czas końca? W tym artykule wyobrazimy sobie, że mamy klientów i że nasza usługa będzie przetwarzać 10 000 żądań na sekundę.

В Książka Google SRE Szczegółowo omówiono metody selekcji i modelowania. Zróbmy to samo i zbudujmy modele:

  • Opóźnienie: 99% żądań powinno zostać zrealizowanych w czasie krótszym niż 60 ms;
  • Koszt: Usługa powinna pochłonąć minimalną kwotę, jaką naszym zdaniem jest rozsądnie możliwa. Aby to zrobić, maksymalizujemy przepustowość;
  • Planowanie wydajności: wymaga zrozumienia i udokumentowania liczby wystąpień aplikacji, które będą musiały zostać uruchomione, w tym ogólnej funkcjonalności skalowania, oraz liczby wystąpień potrzebnych do spełnienia wymagań dotyczących początkowego obciążenia i udostępniania redundancja n+1.

Opóźnienie może wymagać optymalizacji oprócz analizy, ale przepustowość niewątpliwie wymaga analizy. W przypadku korzystania z procesu SRE SLO żądanie opóźnienia pochodzi od klienta lub firmy reprezentowanej przez właściciela produktu. A nasz serwis wywiąże się z tego obowiązku od samego początku, bez żadnych ustawień!

Konfigurowanie środowiska testowego

Za pomocą środowiska testowego będziemy mogli umieścić mierzone obciążenie w naszym systemie. Do analizy zostaną wygenerowane dane dotyczące wydajności serwisu internetowego.

Obciążenie transakcyjne

To środowisko wykorzystuje wegetować aby utworzyć niestandardową częstotliwość żądań HTTP do momentu zatrzymania:

$ 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

Obserwacja

Obciążenie transakcyjne zostanie zastosowane w czasie wykonywania. Oprócz wskaźników aplikacji (liczba żądań, opóźnienie odpowiedzi) i systemu operacyjnego (pamięć, procesor, IOPS) zostanie przeprowadzone profilowanie aplikacji, aby zrozumieć, gdzie występują problemy i w jaki sposób zużywany jest czas procesora.

Profilowy

Profilowanie to rodzaj pomiaru, który pozwala zobaczyć, gdzie zużywa się czas procesora, gdy aplikacja jest uruchomiona. Pozwala dokładnie określić gdzie i ile czasu procesora jest przeznaczane:

SRE: Analiza wydajności. Metoda konfiguracji przy użyciu prostego serwera WWW w Go

Dane te można wykorzystać podczas analizy, aby uzyskać wgląd w zmarnowany czas procesora i wykonywaną niepotrzebną pracę. Go (pprof) może generować profile i wizualizować je w postaci wykresów płomienia przy użyciu standardowego zestawu narzędzi. Opowiem o przewodniku dotyczącym ich użytkowania i konfiguracji w dalszej części artykułu.

Wykonanie, obserwacja, analiza.

Zróbmy eksperyment. Będziemy wykonywać, obserwować i analizować, aż będziemy zadowoleni z wykonania. Wybierzmy dowolnie małą wartość obciążenia i zastosujmy ją do uzyskania wyników pierwszych obserwacji. W każdym kolejnym kroku będziemy zwiększać obciążenie o określony współczynnik skalowania, wybrany z pewną wariacją. Każde uruchomienie testu obciążenia jest wykonywane z dostosowaną liczbą żądań: make load-test LOAD_TEST_RATE=X.

50 żądań na sekundę

SRE: Analiza wydajności. Metoda konfiguracji przy użyciu prostego serwera WWW w Go

Zwróć uwagę na dwa górne wykresy. Lewy górny róg pokazuje, że nasza aplikacja przetwarza 50 żądań na sekundę (tak myśli), a prawy górny pokazuje czas trwania każdego żądania. Oba parametry pomagają nam sprawdzić i przeanalizować, czy mieścimy się w naszych granicach wydajności, czy nie. Czerwona linia na wykresie Opóźnienie żądania HTTP pokazuje SLO przy 60 ms. Linia pokazuje, że mamy znacznie poniżej maksymalnego czasu reakcji.

Spójrzmy na stronę kosztową:

10000 50 żądań na sekundę / 200 żądań na serwer = 1 serwerów + XNUMX

Wciąż możemy poprawić ten wynik.

500 żądań na sekundę

Bardziej interesujące rzeczy zaczynają się dziać, gdy obciążenie osiąga 500 żądań na sekundę:

SRE: Analiza wydajności. Metoda konfiguracji przy użyciu prostego serwera WWW w Go

Ponownie na lewym górnym wykresie widać, że aplikacja rejestruje normalne obciążenie. Jeżeli tak nie jest, oznacza to problem na serwerze, na którym działa aplikacja. Wykres opóźnienia odpowiedzi znajduje się w prawym górnym rogu i pokazuje, że 500 żądań na sekundę spowodowało opóźnienie odpowiedzi wynoszące 25–40 ms. 99. percentyl nadal dobrze pasuje do wybranego powyżej SLO 60 ms.

Pod względem kosztów:

10000 500 żądań na sekundę / 20 żądań na serwer = 1 serwerów + XNUMX

Wszystko można jeszcze poprawić.

1000 żądań na sekundę

SRE: Analiza wydajności. Metoda konfiguracji przy użyciu prostego serwera WWW w Go

Świetne uruchomienie! Aplikacja pokazuje, że przetworzyła 1000 żądań na sekundę, ale limit opóźnień został naruszony przez SLO. Można to zobaczyć w linii p99 na prawym górnym wykresie. Pomimo tego, że linia p100 jest znacznie wyższa, rzeczywiste opóźnienia są większe niż maksymalne 60ms. Zagłębmy się w profilowanie, aby dowiedzieć się, co właściwie robi aplikacja.

Profilowy

Do profilowania ustawiamy obciążenie na 1000 żądań na sekundę, a następnie używamy pprof do przechwytywania danych i sprawdzania, gdzie aplikacja wykorzystuje czas procesora. Można to zrobić poprzez aktywację punktu końcowego HTTP pprof, a następnie pod obciążeniem zapisz wyniki za pomocą curl:

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

Wyniki można wyświetlić w następujący sposób:

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

SRE: Analiza wydajności. Metoda konfiguracji przy użyciu prostego serwera WWW w Go

Wykres pokazuje, gdzie i ile aplikacja wykorzystuje czas procesora. Z opisu z Brendana Gregga:

Oś X to populacja profili stosu, posortowana alfabetycznie (to nie jest czas), oś Y pokazuje głębokość stosu, licząc od zera na [górze]. Każdy prostokąt jest ramką stosu. Im szersza ramka, tym częściej występuje w stosach. To, co znajduje się na górze, działa na procesorze, a to, co znajduje się poniżej, to elementy potomne. Kolory zwykle nic nie znaczą, są po prostu wybierane losowo, aby różnicować ramki.

Analiza - hipoteza

W przypadku strojenia skupimy się na próbie znalezienia zmarnowanego czasu procesora. Będziemy szukać największych źródeł bezużytecznych wydatków i je usuwać. Cóż, biorąc pod uwagę, że profilowanie bardzo dokładnie ujawnia, gdzie dokładnie aplikacja spędza czas procesora, być może będziesz musiał wykonać to kilka razy, a także zmienić kod źródłowy aplikacji, ponownie uruchomić testy i sprawdzić, czy wydajność zbliża się do docelowej.

Kierując się zaleceniami Brendana Gregga, wykres będziemy czytać od góry do dołu. Każda linia wyświetla ramkę stosu (wywołanie funkcji). Pierwsza linia to punkt wejścia do programu, rodzic wszystkich pozostałych wywołań (innymi słowy, wszystkie inne wywołania będą miały to na swoim stosie). Następna linia jest już inna:

SRE: Analiza wydajności. Metoda konfiguracji przy użyciu prostego serwera WWW w Go

Jeśli najedziesz kursorem na nazwę funkcji na wykresie, wyświetlony zostanie całkowity czas jej przebywania na stosie podczas debugowania. Funkcja HTTPServe była dostępna przez 65% czasu, inne funkcje wykonawcze runtime.mcall, mstart и gc, zajmował resztę czasu. Ciekawostka: 5% całkowitego czasu spędza się na zapytaniach DNS:

SRE: Analiza wydajności. Metoda konfiguracji przy użyciu prostego serwera WWW w Go

Adresy, których szuka program, należą do Postgresql. Kliknij FindByAge:

SRE: Analiza wydajności. Metoda konfiguracji przy użyciu prostego serwera WWW w Go

Co ciekawe, program pokazuje, że w zasadzie istnieją trzy główne źródła opóźnień: otwieranie i zamykanie połączeń, żądanie danych oraz łączenie się z bazą danych. Z wykresu wynika, że ​​żądania DNS, otwieranie i zamykanie połączeń zajmują około 13% całkowitego czasu wykonania.

Hipoteza: Ponowne wykorzystanie połączeń przy użyciu puli powinno skrócić czas pojedynczego żądania HTTP, umożliwiając wyższą przepustowość i mniejsze opóźnienia.

Konfiguracja aplikacji – eksperyment

Aktualizujemy kod źródłowy, przy każdym żądaniu staramy się usunąć połączenie z Postgresql. Pierwszą opcją jest użycie pula połączeń na poziomie aplikacji. W tym eksperymencie my ustalmy to łączenie połączeń przy użyciu sterownika sql dla go:

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

if err != nil {
   return nil, err
}

Wykonanie, obserwacja, analiza

Po ponownym uruchomieniu testu z 1000 żądaniami na sekundę jasne jest, że poziomy opóźnień p99 wróciły do ​​normy przy SLO wynoszącym 60 ms!

Jaki jest koszt?

10000 1000 żądań na sekundę / 10 żądań na serwer = 1 serwerów + XNUMX

Zróbmy to jeszcze lepiej!

2000 żądań na sekundę

SRE: Analiza wydajności. Metoda konfiguracji przy użyciu prostego serwera WWW w Go

Podwojenie obciążenia pokazuje to samo, lewy górny wykres pokazuje, że aplikacji udaje się przetworzyć 2000 żądań na sekundę, p100 jest mniejsze niż 60 ms, p99 spełnia SLO.

Pod względem kosztów:

10000 2000 żądań na sekundę / 5 żądań na serwer = 1 serwerów + XNUMX

3000 żądań na sekundę

SRE: Analiza wydajności. Metoda konfiguracji przy użyciu prostego serwera WWW w Go

W tym przypadku aplikacja może przetworzyć 3000 żądań z opóźnieniem p99 mniejszym niż 60 ms. SLO nie jest naruszane, a koszt jest akceptowany w następujący sposób:

10000 3000 żądań na sekundę / 4 żądań na serwer = 1 serwery + XNUMX (autor zaokrąglił w górę, około. tłumacz)

Spróbujmy kolejnej rundy analizy.

Analiza - hipoteza

Zbieramy i wyświetlamy wyniki debugowania aplikacji z szybkością 3000 żądań na sekundę:

SRE: Analiza wydajności. Metoda konfiguracji przy użyciu prostego serwera WWW w Go

Nadal 6% czasu poświęca się na nawiązywanie połączeń. Skonfigurowanie puli poprawiło wydajność, ale nadal widać, że aplikacja w dalszym ciągu pracuje nad tworzeniem nowych połączeń z bazą danych.

Hipoteza: Połączenia, pomimo obecności puli, nadal są zrywane i czyszczone, więc aplikacja musi je zresetować. Ustawienie liczby oczekujących połączeń na rozmiar puli powinno pomóc w zmniejszeniu opóźnień, minimalizując czas, jaki aplikacja spędza na tworzeniu połączenia.

Konfiguracja aplikacji – eksperyment

Próbuję zainstalować MaxIdleConns równy rozmiarowi basenu (również opisany tutaj):

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

Wykonanie, obserwacja, analiza

3000 żądań na sekundę

SRE: Analiza wydajności. Metoda konfiguracji przy użyciu prostego serwera WWW w Go

p99 wynosi mniej niż 60 ms przy znacznie mniejszym p100!

SRE: Analiza wydajności. Metoda konfiguracji przy użyciu prostego serwera WWW w Go

Sprawdzanie wykresu płomienia pokazuje, że połączenie nie jest już zauważalne! Sprawdźmy bardziej szczegółowo pg(*conn).query — my też nie zauważamy, żeby tutaj nawiązano połączenie.

SRE: Analiza wydajności. Metoda konfiguracji przy użyciu prostego serwera WWW w Go

wniosek

Analiza wydajności ma kluczowe znaczenie dla zrozumienia, że ​​oczekiwania klientów i wymagania pozafunkcjonalne są spełnione. Analiza polegająca na porównaniu obserwacji z oczekiwaniami klienta może pomóc w określeniu, co jest akceptowalne, a co nie. Go zapewnia potężne narzędzia wbudowane w standardową bibliotekę, dzięki którym analiza jest prosta i dostępna.

Źródło: www.habr.com

Dodaj komentarz