Dostrajanie wydajności — wiele usług zaplecza

Azure Kubernetes Service (AKS)
Azure Cosmos DB

W tym artykule opisano sposób, w jaki zespół programistyczny używał metryk do znajdowania wąskich gardeł i poprawiania wydajności systemu rozproszonego. Artykuł jest oparty na rzeczywistym testowaniu obciążenia, które zostało wykonana dla przykładowej aplikacji. Aplikacja pochodzi z planu bazowego Azure Kubernetes Service (AKS) dla mikrousług wraz z projektem testu obciążeniowego programu Visual Studio używanym do generowania wyników.

Ten artykuł jest częścią serii. Przeczytaj pierwszą część tutaj.

Scenariusz: wywołaj wiele usług zaplecza, aby pobrać informacje, a następnie zagregować wyniki.

Ten scenariusz obejmuje aplikację dostarczania dronów. Klienci mogą wysyłać zapytania do interfejsu API REST, aby uzyskać najnowsze informacje o fakturze. Faktura zawiera podsumowanie dostaw, pakietów i całkowitego wykorzystania dronów przez klienta. Ta aplikacja używa architektury mikrousług działających w usłudze AKS, a informacje potrzebne do faktury są rozłożone na kilka mikrousług.

Zamiast klienta wywołującego każdą usługę bezpośrednio, aplikacja implementuje wzorzec agregacji bramy . Korzystając z tego wzorca, klient wysyła pojedyncze żądanie do usługi bramy. Brama z kolei wywołuje usługi zaplecza równolegle, a następnie agreguje wyniki w jeden ładunek odpowiedzi.

Diagram przedstawiający wzorzec agregacji bramy

Test 1. Wydajność punktu odniesienia

Aby ustanowić punkt odniesienia, zespół programistyczny rozpoczął test obciążenia krokowego, co zwiększa obciążenie od jednego symulowanego użytkownika do 40 użytkowników w łącznym czasie trwania 8 minut. Poniższy wykres, pobrany z programu Visual Studio, pokazuje wyniki. Fioletowa linia pokazuje obciążenie użytkownika, a pomarańczowa linia pokazuje przepływność (średnie żądania na sekundę).

Wykres wyników testu obciążeniowego programu Visual Studio

Czerwona linia u dołu wykresu pokazuje, że do klienta nie zostały zwrócone żadne błędy, co jest zachęcające. Jednak średnia przepływność osiąga około połowy testu, a następnie spada dla pozostałej części, nawet gdy obciążenie nadal rośnie. Oznacza to, że zaplecze nie jest w stanie nadążyć. Wzorzec widoczny tutaj jest typowy, gdy system zaczyna osiągać limity zasobów — po osiągnięciu maksymalnej przepływności faktycznie spada znacznie. Rywalizacja o zasoby, błędy przejściowe lub wzrost liczby wyjątków może przyczynić się do tego wzorca.

Zapoznajmy się z danymi monitorowania, aby dowiedzieć się, co dzieje się w systemie. Następny wykres jest pobierany z usługi Application Insights. Przedstawia on średni czas trwania wywołań HTTP z bramy do usług zaplecza.

Wykres czasów trwania wywołań HTTP

Ten wykres pokazuje, że jedna operacja, w szczególności , GetDroneUtilizationtrwa średnio znacznie dłużej — o kolejność wielkości. Brama wykonuje te wywołania równolegle, więc najwolniejsza operacja określa, jak długo trwa ukończenie całego żądania.

Oczywiście następnym krokiem jest zapoznanie się z operacją GetDroneUtilization i wyszukiwanie wszelkich wąskich gardeł. Jedną z możliwości jest wyczerpanie zasobów. Być może ta konkretna usługa zaplecza zabraknie procesora CPU lub pamięci. W przypadku klastra usługi AKS te informacje są dostępne w Azure Portal za pośrednictwem funkcji szczegółowych informacji o kontenerze usługi Azure Monitor. Na poniższych wykresach przedstawiono wykorzystanie zasobów na poziomie klastra:

Wykres wykorzystania węzła usługi AKS

Na tym zrzucie ekranu są wyświetlane zarówno wartości średnie, jak i maksymalne. Ważne jest, aby przyjrzeć się więcej niż tylko średniej, ponieważ średnia może ukryć skoki danych. W tym miejscu średnie wykorzystanie procesora CPU pozostaje poniżej 50%, ale istnieje kilka skoków do 80%. Jest to blisko pojemności, ale nadal w granicach tolerancji. Coś innego powoduje wąskie gardło.

Następny wykres ujawnia prawdziwego winowajcę. Na tym wykresie przedstawiono kody odpowiedzi HTTP z bazy danych zaplecza usługi dostarczania, co w tym przypadku jest usługą Azure Cosmos DB. Niebieska linia reprezentuje kody powodzenia (HTTP 2xx), podczas gdy zielona linia reprezentuje błędy HTTP 429. Kod powrotny HTTP 429 oznacza, że usługa Azure Cosmos DB tymczasowo ogranicza żądania, ponieważ obiekt wywołujący zużywa więcej jednostek zasobów (RU) niż aprowizacji.

Wykres żądań ograniczonych

Aby uzyskać dalsze szczegółowe informacje, zespół programistyczny użył usługi Application Insights do wyświetlania kompleksowej telemetrii dla reprezentatywnej próbki żądań. Oto jedno wystąpienie:

Zrzut ekranu przedstawiający widok transakcji kompleksowej

Ten widok przedstawia wywołania związane z pojedynczym żądaniem klienta wraz z informacjami o chronometrażu i kodami odpowiedzi. Wywołania najwyższego poziomu pochodzą z bramy do usług zaplecza. Wywołanie metody jest GetDroneUtilization rozwinięte w celu wyświetlania wywołań zależności zewnętrznych — w tym przypadku do usługi Azure Cosmos DB. Wywołanie w kolorze czerwonym zwróciło błąd HTTP 429.

Zwróć uwagę na dużą lukę między błędem HTTP 429 a następnym wywołaniem. Gdy biblioteka kliencka usługi Azure Cosmos DB otrzyma błąd HTTP 429, automatycznie cofnie się i czeka na ponowną próbę wykonania operacji. Ten widok pokazuje, że w ciągu 672 ms ta operacja zajęła większość czasu oczekiwania na ponowienie próby w usłudze Azure Cosmos DB.

Oto kolejny interesujący wykres dla tej analizy. Pokazuje użycie jednostek RU na partycję fizyczną w porównaniu z aprowizowaną jednostkami RU na partycję fizyczną:

Wykres użycia jednostek RU na partycję

Aby zrozumieć ten wykres, musisz zrozumieć, jak usługa Azure Cosmos DB zarządza partycjami. Kolekcje w usłudze Azure Cosmos DB mogą mieć klucz partycji. Każda możliwa wartość klucza definiuje partycję logiczną danych w kolekcji. Usługa Azure Cosmos DB dystrybuuje te partycje logiczne na co najmniej jednej partycji fizycznej . Zarządzanie partycjami fizycznymi jest obsługiwane automatycznie przez usługę Azure Cosmos DB. W miarę przechowywania większej ilości danych usługa Azure Cosmos DB może przenosić partycje logiczne do nowych partycji fizycznych, aby rozłożyć obciążenie na partycje fizyczne.

Na potrzeby tego testu obciążeniowego kolekcja usługi Azure Cosmos DB została aprowizowana przy użyciu 900 jednostek RU. Wykres przedstawia 100 RU na partycję fizyczną, co oznacza łącznie dziewięć partycji fizycznych. Chociaż usługa Azure Cosmos DB automatycznie obsługuje fragmentowanie partycji fizycznych, wiedza o liczbie partycji może zapewnić wgląd w wydajność. Zespół deweloperów będzie później używać tych informacji, ponieważ będą nadal optymalizować. Jeśli niebieska linia przekracza fioletową linię poziomą, użycie jednostek RU przekroczyło aprowizowaną liczbę jednostek RU. Jest to punkt, w którym usługa Azure Cosmos DB zacznie ograniczać wywołania.

Test 2. Zwiększanie liczby jednostek zasobów

W przypadku drugiego testu obciążeniowego zespół przeskalował kolekcję usługi Azure Cosmos DB z 900 RU do 2500 RU. Przepływność wzrosła z 19 żądań/sekund do 23 żądań/sekund, a średnie opóźnienie spadło z 669 ms do 569 ms.

Metric Test 1 Test 2
Przepływność (req/s) 19 23
Średnie opóźnienie (ms) 669 569
Żądania zakończone powodzeniem 9,8 K 11 K

Nie są to ogromne zyski, ale patrząc na wykres w czasie pokazuje bardziej kompletny obraz:

Wykres wyników testu obciążeniowego programu Visual Studio przedstawiający bardziej spójną przepływność.

Podczas gdy poprzedni test wykazał początkowy wzrost po gwałtownym spadku, ten test pokazuje bardziej spójną przepływność. Maksymalna przepływność nie jest jednak znacznie wyższa.

Wszystkie żądania do usługi Azure Cosmos DB zwróciły stan 2xx, a błędy HTTP 429 zostały wycofane:

Wykres wywołań usługi Azure Cosmos DB

Wykres użycia jednostek RU w porównaniu z aprowizowaną jednostkami RU pokazuje, że istnieje mnóstwo miejsca na głowę. Istnieje około 275 jednostek RU na partycję fizyczną, a test obciążenia osiągnął około 100 jednostek RU zużywanych na sekundę.

Wykres użycia jednostek RU w porównaniu z aprowizowaną jednostkami RU pokazujący, że jest mnóstwo miejsca na głowę.

Kolejną interesującą metryczką jest liczba wywołań do usługi Azure Cosmos DB na pomyślną operację:

Metric Test 1 Test 2
Wywołania na operację 11 9

Przy założeniu, że liczba wywołań nie powinna być zgodna z rzeczywistym planem zapytania. W takim przypadku operacja obejmuje zapytanie obejmujące wiele partycji, które trafia do wszystkich dziewięciu partycji fizycznych. Wyższa wartość w pierwszym teście obciążeniowym odzwierciedla liczbę wywołań, które zwróciły błąd 429.

Ta metryka została obliczona przez uruchomienie niestandardowego zapytania usługi Log Analytics:

let start=datetime("2020-06-18T20:59:00.000Z");
let end=datetime("2020-07-24T21:10:00.000Z");
let operationNameToEval="GET DroneDeliveries/GetDroneUtilization";
let dependencyType="Azure DocumentDB";
let dataset=requests
| where timestamp > start and timestamp < end
| where success == true
| where name == operationNameToEval;
dataset
| project reqOk=itemCount
| summarize
    SuccessRequests=sum(reqOk),
    TotalNumberOfDepCalls=(toscalar(dependencies
    | where timestamp > start and timestamp < end
    | where type == dependencyType
    | summarize sum(itemCount)))
| project
    OperationName=operationNameToEval,
    DependencyName=dependencyType,
    SuccessRequests,
    AverageNumberOfDepCallsPerOperation=(TotalNumberOfDepCalls/SuccessRequests)

Podsumowując, drugi test obciążeniowy pokazuje poprawę. GetDroneUtilization Jednak operacja nadal trwa o rzędu wielkości dłużej niż najwolniejsza operacja. Spojrzenie na kompleksowe transakcje pomaga wyjaśnić, dlaczego:

Zrzut ekranu przedstawiający drugi test obciążeniowy przedstawiający poprawę.

Jak wspomniano wcześniej, GetDroneUtilization operacja obejmuje zapytanie obejmujące wiele partycji do usługi Azure Cosmos DB. Oznacza to, że klient usługi Azure Cosmos DB musi wymyślić zapytanie do każdej partycji fizycznej i zebrać wyniki. W widoku kompleksowej transakcji te zapytania są wykonywane szeregowo. Operacja trwa tak długo, jak suma wszystkich zapytań — i ten problem będzie się pogarszać tylko w miarę wzrostu rozmiaru danych i dodawania większej liczby partycji fizycznych.

Test 3. Zapytania równoległe

Na podstawie poprzednich wyników oczywistym sposobem zmniejszenia opóźnienia jest równoległe wystawianie zapytań. Zestaw SDK klienta usługi Azure Cosmos DB ma ustawienie, które kontroluje maksymalny stopień równoległości.

Wartość Opis
0 Brak równoległości (wartość domyślna)
> 0 Maksymalna liczba wywołań równoległych
-1 Zestaw SDK klienta wybiera optymalny stopień równoległości

W przypadku trzeciego testu obciążeniowego to ustawienie zostało zmienione z zakresu od 0 do -1. Poniższa tabela zawiera podsumowanie wyników:

Metric Test 1 Test 2 Test 3
Przepływność (req/s) 19 23 42
Średnie opóźnienie (ms) 669 569 215
Żądania zakończone powodzeniem 9,8 K 11 K 20 K
Żądania ograniczone 2,72 K 0 0

Na wykresie testu obciążeniowego nie tylko ogólna przepływność jest znacznie wyższa (pomarańczowa linia), ale przepływność utrzymuje również tempo z obciążeniem (fioletowa linia).

Wykres wyników testu obciążeniowego programu Visual Studio przedstawiający większą ogólną przepływność, która utrzymuje tempo przy obciążeniu.

Możemy sprawdzić, czy klient usługi Azure Cosmos DB wykonuje zapytania równolegle, sprawdzając widok kompleksowej transakcji:

Zrzut ekranu przedstawiający widok kompleksowej transakcji pokazujący, że klient usługi Azure Cosmos DB wykonuje zapytania równolegle.

Co ciekawe, efekt uboczny zwiększenia przepływności polega na tym, że liczba jednostek RU zużywanych na sekundę również zwiększa się. Mimo że usługa Azure Cosmos DB nie ograniczała żadnych żądań podczas tego testu, użycie było zbliżone do aprowizowanego limitu jednostek RU:

Wykres użycia jednostek RU zbliżony do aprowizowanego limitu jednostek RU.

Ten graf może być sygnałem do dalszego skalowania bazy danych w poziomie. Okazuje się jednak, że możemy zamiast tego zoptymalizować zapytanie.

Krok 4. Optymalizowanie zapytania

Poprzedni test obciążeniowy wykazał lepszą wydajność pod względem opóźnienia i przepływności. Średnie opóźnienie żądań zostało zmniejszone o 68%, a przepływność wzrosła o 220%. Jednak zapytanie obejmujące wiele partycji jest problemem.

Problem z zapytaniami między partycjami polega na tym, że płacisz za jednostkę RU na każdej partycji. Jeśli zapytanie jest uruchamiane tylko od czasu do czasu — powiedzmy raz na godzinę — może nie mieć znaczenia. Jednak za każdym razem, gdy widzisz duże obciążenie odczytu, które obejmuje zapytanie obejmujące wiele partycji, należy sprawdzić, czy zapytanie można zoptymalizować, uwzględniając klucz partycji. (Może być konieczne przeprojektowanie kolekcji w celu użycia innego klucza partycji).

Oto zapytanie dotyczące tego konkretnego scenariusza:

SELECT * FROM c
WHERE c.ownerId = <ownerIdValue> and
      c.year = <yearValue> and
      c.month = <monthValue>

To zapytanie wybiera rekordy zgodne z określonym identyfikatorem właściciela i miesiącem/rokiem. W oryginalnym projekcie żadna z tych właściwości nie jest kluczem partycji. Wymaga to od klienta wypychania zapytania do każdej partycji fizycznej i zbierania wyników. Aby poprawić wydajność zapytań, zespół deweloperów zmienił projekt tak, aby identyfikator właściciela był kluczem partycji dla kolekcji. W ten sposób zapytanie może dotyczyć określonej partycji fizycznej. (Usługa Azure Cosmos DB obsługuje to automatycznie; nie trzeba zarządzać mapowaniem między wartościami klucza partycji i partycjami fizycznymi).

Po przełączeniu kolekcji na nowy klucz partycji nastąpiła dramatyczna poprawa zużycia jednostek RU, co przekłada się bezpośrednio na niższe koszty.

Metric Test 1 Test 2 Test 3 Test 4
Liczba jednostek RU na operację 29 29 29 3.4
Wywołania na operację 11 9 10 1

Widok kompleksowej transakcji pokazuje, że zgodnie z przewidywaniami zapytanie odczytuje tylko jedną partycję fizyczną:

Zrzut ekranu przedstawiający widok transakcji kompleksowej pokazujący, że zapytanie odczytuje tylko jedną partycję fizyczną.

Test obciążeniowy pokazuje zwiększoną przepływność i opóźnienie:

Metric Test 1 Test 2 Test 3 Test 4
Przepływność (req/s) 19 23 42 59
Średnie opóźnienie (ms) 669 569 215 176
Żądania zakończone powodzeniem 9,8 K 11 K 20 K 29 K
Żądania ograniczone 2,72 K 0 0 0

Konsekwencją poprawy wydajności jest to, że wykorzystanie procesora CPU węzła staje się bardzo wysokie:

Wykres przedstawiający wysokie wykorzystanie procesora CPU węzła.

Pod koniec testu obciążeniowego średni procesor osiągnął około 90%, a maksymalny procesor osiągnął 100%. Ta metryka wskazuje, że procesor CPU jest kolejnym wąskim gardłem w systemie. Jeśli potrzebna jest większa przepływność, następnym krokiem może być skalowanie usługi dostarczania do większej liczby wystąpień.

Podsumowanie

W tym scenariuszu zidentyfikowano następujące wąskie gardła:

  • Żądania ograniczania przepustowości usługi Azure Cosmos DB z powodu niewystarczającej liczby aprowizowania jednostek RU.
  • Duże opóźnienie spowodowane wykonywaniem zapytań dotyczących wielu partycji bazy danych w szeregowych.
  • Nieefektywne zapytanie obejmujące wiele partycji, ponieważ kwerenda nie zawierała klucza partycji.

Ponadto wykorzystanie procesora CPU zostało zidentyfikowane jako potencjalne wąskie gardło na większą skalę. Aby zdiagnozować te problemy, zespół programistyczny przejrzał:

  • Opóźnienie i przepływność z testu obciążeniowego.
  • Błędy usługi Azure Cosmos DB i użycie jednostek RU.
  • Widok kompleksowej transakcji w usłudze Application Insights.
  • Wykorzystanie procesora CPU i pamięci w szczegółowych informacji o kontenerze usługi Azure Monitor.

Następne kroki

Przegląd antywzorzeców wydajności