Jak w
Któregoś dnia obudził mnie niezadowolony e-mail z powodu dużych opóźnień w firmie Alvin, którą planowaliśmy uruchomić w najbliższej przyszłości. W szczególności klient doświadczył opóźnienia na poziomie 99. percentyla w zakresie 50 ms, czyli znacznie powyżej naszego budżetu opóźnień. Było to zaskakujące, ponieważ intensywnie testowałem usługę, szczególnie pod kątem opóźnień, które są częstą skargą.
Zanim umieściłem Alvina w testach, przeprowadziłem wiele eksperymentów z 40 tys. zapytań na sekundę (QPS), a wszystkie wykazały opóźnienia mniejsze niż 10 ms. Byłem gotowy oświadczyć, że nie zgadzam się z ich wynikami. Ale patrząc ponownie na list, zauważyłem coś nowego: nie przetestowałem dokładnie warunków, o których wspominali, ich QPS było znacznie niższe niż moje. Testowałem przy 40 tys. QPS, ale oni tylko przy 1 tys. Aby ich uspokoić, przeprowadziłem kolejny eksperyment, tym razem z niższym QPS.
Ponieważ piszę o tym na blogu, prawdopodobnie już zorientowałeś się, że ich liczby były prawidłowe. Wielokrotnie testowałem mojego wirtualnego klienta z tym samym rezultatem: mała liczba żądań nie tylko zwiększa opóźnienie, ale zwiększa liczbę żądań z opóźnieniem większym niż 10 ms. Innymi słowy, jeśli przy 40k QPS około 50 żądań na sekundę przekraczało 50 ms, to przy 1k QPS było 100 żądań powyżej 50 ms na sekundę. Paradoks!
Zawężanie poszukiwań
W przypadku problemu opóźnień w systemie rozproszonym składającym się z wielu komponentów pierwszym krokiem jest utworzenie krótkiej listy podejrzanych. Zagłębmy się nieco w architekturę Alvina:
Dobrym punktem wyjścia jest lista ukończonych przejść we/wy (połączenia sieciowe/wyszukiwanie dysku itp.). Spróbujmy dowiedzieć się, gdzie jest opóźnienie. Oprócz oczywistych operacji we/wy z klientem Alvin wykonuje dodatkowy krok: uzyskuje dostęp do magazynu danych. Jednak ta pamięć działa w tym samym klastrze co Alvin, więc opóźnienie powinno być tam mniejsze niż w przypadku klienta. A więc lista podejrzanych:
- Połączenie sieciowe od klienta do Alvina.
- Połączenie sieciowe od Alvina do magazynu danych.
- Wyszukaj na dysku w magazynie danych.
- Połączenie sieciowe z hurtowni danych do Alvina.
- Połączenie sieciowe od Alvina do klienta.
Spróbujmy przekreślić kilka punktów.
Przechowywanie danych nie ma z tym nic wspólnego
Pierwszą rzeczą, którą zrobiłem, było przekonwertowanie Alvina na serwer ping-ping, który nie przetwarza żądań. Po otrzymaniu żądania zwraca pustą odpowiedź. Jeśli opóźnienie maleje, błąd w implementacji Alvina lub hurtowni danych nie jest niczym niezwykłym. W pierwszym eksperymencie otrzymujemy następujący wykres:
Jak widać, nie ma poprawy podczas korzystania z serwera ping-ping. Oznacza to, że hurtownia danych nie zwiększa opóźnień, a lista podejrzanych zostaje skrócona o połowę:
- Połączenie sieciowe od klienta do Alvina.
- Połączenie sieciowe od Alvina do klienta.
Świetnie! Lista szybko się kurczy. Myślałem, że już prawie odkryłem przyczynę.
gRPC
Nadszedł czas, aby przedstawić Wam nowego gracza: gRPC
dobrze zoptymalizowany i szeroko stosowany, użyłem go po raz pierwszy w systemie tej wielkości i spodziewałem się, że moja implementacja będzie co najmniej nieoptymalna.
dostępność gRPC
na stosie zrodziło nowe pytanie: może to moja implementacja, albo ja gRPC
powoduje problem z opóźnieniami? Dodanie nowego podejrzanego do listy:
- Klient dzwoni do biblioteki
gRPC
- biblioteka
gRPC
wykonuje połączenie sieciowe z biblioteką na klienciegRPC
na serwerze - biblioteka
gRPC
kontaktuje się z Alvinem (brak operacji w przypadku serwera ping-pongowego)
Aby dać ci wyobrażenie o tym, jak wygląda kod, moja implementacja klient/Alvin nie różni się zbytnio od implementacji klient-serwer
Uwaga: Powyższa lista jest nieco uproszczona, ponieważ
gRPC
umożliwia wykorzystanie własnego (szablonu?) modelu wątków, w którym przeplata się stos wykonawczygRPC
i wdrożenie użytkownika. Dla uproszczenia będziemy trzymać się tego modelu.
Profilowanie wszystko naprawi
Po przekreśleniu magazynów danych myślałem, że już prawie skończyłem: „Teraz to proste! Zastosujmy profil i dowiedzmy się, gdzie występuje opóźnienie.” I
Wziąłem cztery profile: z wysokim QPS (niskie opóźnienie) i z serwerem ping-pongowym z niskim QPS (duże opóźnienie), zarówno po stronie klienta, jak i po stronie serwera. I na wszelki wypadek wziąłem też przykładowy profil procesora. Porównując profile, zwykle szukam nietypowego stosu wywołań. Na przykład wadą związaną z dużym opóźnieniem jest znacznie więcej przełączników kontekstu (10 razy lub więcej). Ale w moim przypadku liczba przełączeń kontekstu była prawie taka sama. Ku mojemu przerażeniu nie było tam nic istotnego.
Dodatkowe debugowanie
Byłem zdesperowany. Nie wiedziałem, jakich innych narzędzi mógłbym użyć, więc mój następny plan polegał zasadniczo na powtórzeniu eksperymentów z różnymi odmianami, zamiast jasnej diagnozy problemu.
Co jeśli
Od samego początku obawiałem się specyficznego opóźnienia wynoszącego 50 ms. To bardzo ważny czas. Zdecydowałem, że wytnę fragmenty kodu, dopóki nie będę mógł dokładnie ustalić, która część powoduje ten błąd. Potem przyszedł eksperyment, który zadziałał.
Jak zwykle z perspektywy czasu wydaje się, że wszystko było oczywiste. Umieściłem klienta na tej samej maszynie co Alvin i wysłałem żądanie do localhost
. I wzrost opóźnień zniknął!
Coś było nie tak z siecią.
Nauka umiejętności inżyniera sieci
Muszę przyznać: moja wiedza na temat technologii sieciowych jest straszna, zwłaszcza biorąc pod uwagę fakt, że pracuję z nimi na co dzień. Ale głównym podejrzanym była sieć i musiałem nauczyć się ją debugować.
Na szczęście Internet kocha tych, którzy chcą się uczyć. Kombinacja ping i tracert wydawała się wystarczająco dobrym początkiem do debugowania problemów z transportem sieciowym.
Po pierwsze uruchomiłem
Potem próbowałem
Zatem to nie mój kod, implementacja gRPC ani sieć spowodowały opóźnienie. Zacząłem się martwić, że nigdy tego nie zrozumiem.
Teraz na jakim systemie operacyjnym jesteśmy
gRPC
szeroko stosowany w systemie Linux, ale egzotyczny w systemie Windows. Postanowiłem przeprowadzić eksperyment, który zadziałał: stworzyłem maszynę wirtualną z Linuksem, skompilowałem Alvina dla Linuksa i wdrożyłem go.
I oto co się stało: Linuxowy serwer ping-pongowy nie miał takich samych opóźnień jak podobny host Windows, chociaż źródło danych nie było inne. Okazuje się, że problem tkwi w implementacji gRPC dla Windows.
Algorytm Nagle’a
Przez cały ten czas myślałem, że brakuje mi flagi gRPC
. Teraz rozumiem, co to naprawdę jest gRPC
Brak flagi systemu Windows. Znalazłem wewnętrzną bibliotekę RPC, co do której miałem pewność, że będzie dobrze działać dla wszystkich ustawionych flag
Prawie Gotowe: zacząłem usuwać dodane flagi pojedynczo, aż regresja powróciła, aby móc określić przyczynę. To było niesławne
gRPC
ta flaga została ustawiona w implementacji systemu Linux dla gniazd TCP, ale nie w systemie Windows. jestem tym
wniosek
Większe opóźnienia przy niskim QPS były spowodowane optymalizacją systemu operacyjnego. Z perspektywy czasu profilowanie nie wykryło opóźnienia, ponieważ zostało wykonane w trybie jądra, a nie w
Jeśli chodzi o eksperyment z hostem lokalnym, prawdopodobnie nie dotknął on rzeczywistego kodu sieciowego, a algorytm Nagle'a nie działał, więc problemy z opóźnieniami zniknęły, gdy klient skontaktował się z Alvinem za pośrednictwem hosta lokalnego.
Następnym razem, gdy zauważysz wzrost opóźnienia w miarę zmniejszania się liczby żądań na sekundę, algorytm Nagle'a powinien znaleźć się na Twojej liście podejrzanych!
Źródło: www.habr.com