Poza P95: Pomiar opóźnienia, którego faktycznie doświadczają twoi użytkownicy
Zdrowy P95 może znajdować się obok wolnego produktu. Jak czas oczekiwania w kolejce i rozprzestrzenianie się zadań ukrywają się przed panelami kontrolnymi, oraz jak możliwość obserwacji każdego kroku eliminuje wzajemne oskarżenia dotyczące opóźnień.
Klient donosi, że produkt działa powoli. Ktoś otwiera panel sterowania opóźnieniami i wszystko jest w normie: P95 mieści się w założonym zakresie, wskaźnik błędów jest stabilny, wydajność CPU jest wysoka, a liczba połączeń z bazą danych znacznie poniżej ustalonego limitu. Kuszącym wnioskiem jest to, że skarga musi być błędna, ale właśnie ten instynkt, polegający na ufaniu wykresom zamiast osobie, która po prostu czekała, stanowi prawdziwy problem.
Poniższe rozdziały wyjaśniają, dlaczego przyzwoity wskaźnik P95 i powolna praca produktu mogą istnieć jednocześnie. Dowiesz się, gdzie opóźnienia ukrywają się przed metrykami na poziomie usługi (kolejki, próby ponowne, rozprzestrzenianie zapytań, blokujące decyzje), dlaczego „ogon” statystyczny ma większe znaczenie, niż sugeruje jego rozmiar, oraz jak prosty nawyk mierzenia czasu trwania każdego kroku żądania pozwala przekształcić niejasne skargi w konkretne dowody. Pod koniec powinieneś być w stanie krytycznie ocenić stwierdzenie „P95 jest w porządku”, zamiast je albo akceptować, albo odrzucać.
Czym jest P95 i czego nie mówi
Warto być precyzyjnym, zanim zaczniemy krytykować tę miarę. P95 to wartość, na lub poniżej której zakończyło się 95 procent pomierzonych żądań. Jest to naprawdę przydatna liczba, stanowiąca znaczne ulepszenie w porównaniu z średnią latencją. W przypadku średniej problemy w produkcji znikają bez śladu: jeśli dziewięćdziesiąt dwa żądania są realizowane w 50 ms, a jedno zajmuje pięć sekund, średnia wynosi około 100 ms – liczba, o której nikt by nie pisał. Procentyl przynajmniej skupia naszą uwagę na krańcach rozkładu.
Zatem sam procentyl nie jest problemem. Problemem jest rola, jaką zespoły mu przypisują po jego przeczytaniu. Stwierdzenie „P95 jest w porządku” często służy jako certyfikat niewinności, który kończy dyskusję. Taki postępek po cichu zapomina o czymś podstawowym: nikt używający twojego produktu nie doświadcza procentylu – ludzie doświadczają oczekiwania. Naciskają przycisk i zastanawiają się, czy nacisk został zarejestrowany. Widzą stronę, która wygląda na załadowaną, ale nie reaguje. Obserwują ikonkę spinająca się, podczas gdy kilka usług za nią pokazuje zupełnie normalne wartości.
API może mieć doskonałe wartości P95, podczas gdy produkt zbudowany na jego bazie sprawia wrażenie powolnego. Utrzymanie obu tych faktów jednocześnie jest punktem wyjścia dla wszystkiego, co następuje.
Podczas każdej prośby działają trzy „zegary”
Większość rozmów na temat opóźnień zakłada istnienie jednego „zegara”. W praktyce jest ich co najmniej trzy, a mierzą one różne rzeczy.
- Zegar serwera. Do twojej usługi przychodzi żądanie, wychodzi odpowiedź – wtedy zatrzymuje się licznik. To właśnie pokazują większość paneli sterowania backendu.
- Zegar zależności. W trakcie obsługi tego żądania czas jest wykorzystywany na autoryzację, oczekiwanie na połączenie z bazą danych, wewnętrzne wywołania HTTP, brak dostępności pamięci cache, próby ponowne, kolejki, sprawdzanie flag funkcjonalnych, korzystanie z API firm trzecich oraz wszystkie komponenty, które „zwykle działają szybko”.
- Zegar użytkownika. Użytkownik kliknął, potem czekał, aż ekran stanie się użyteczny. To jedyny przedział czasu, który jest dla niego istotny.
Zespoły zwykle optymalizują pierwszy czas działania i następnie opisują produkt jako szybki. Poniższy diagram przedstawia je obok siebie: usługa pokazuje czyste 180 ms, podczas gdy łańcuch zależności obejmuje ponowną próbę trwającą 600 ms, a osoba przy klawiaturze wciąż pyta, dlaczego strona się ładuje.
SERVER CLOCK
request in ------------------------------> response out
"API P95: 180ms"
DEPENDENCY CLOCK
request in ---> auth ---> db ---> service A ---> retry ---> service B ---> response out
40ms 70ms 120ms 600ms 90ms
HUMAN CLOCK
click ----------------------------------------------------------------> usable screen
"Why is this still loading?"
Nic w czasie działania serwera nie jest błędne. Po prostu jest on niekompletny. Ocena szybkości produktu na jego podstawie jest jak mierzenie temperatury kogoś i stwierdzanie, że wszystkie organy są zdrowe: czasami właśnie ta wartość jest kluczowa, a czasami umyka ona rzeczywistej chorobie.
Koniec punktu końcowego to drzwi, podróż to budynek
Korzystny model mentalny: koniec punktu końcowego w tle to pojedyncze drzwi, natomiast podróż użytkownika to przechadzka po całym budynku. Tablice kontrolne przede wszystkim mierzą liczbę drzwi – wnika żądanie, wychodzi odpowiedź, aktualizuje się wykres i wszyscy się uspokajają.
Użytkownicy jednak nie są tam po to, by podziwiać drzwi. Chcą dotrzeć do celu: otworzyć ekran główny, wysłać płatność, złożyć zamówienie, pobrać wykaz, sfinalizować jakieś rzeczywiste zadanie bez poczucia, że produkt waha się przed nimi. To zadanie rzadko odpowiada jednemu wywołaniu w tle. Otwieranie panelu sterowania w typowym aplikacji jednostronicowej przypomina bardziej schemat poniżej, w którym aplikacja najpierw żąda informacji o profilu, uprawnieniach, kontach, ofertach, powiadomieniach i limitach, zanim pojawi się cokolwiek przydatnego.
User Clicks "Open Dashboard"
|
v
+----------------------+
| Frontend Shell |
+----------------------+
|
+------> GET /profile
|
+------> GET /permissions
|
+------> GET /accounts
|
+------> GET /offers
|
+------> GET /notifications
|
+------> GET /limits
|
v
Screen Finally Becomes Useful
Każdy z tych sześciu punktów końcowych może wydawać się sprawny indywidualnie, podczas gdy cała ścieżka działania wydaje się nieprawidłowa. Usługa może spełniać swoje warunki SLA i mimo to przyczyniać się do wolnego działania produktu. Zespół może osiągnąć własne cele, a jednak pogarszać jakość użytkowania. Gdy wywołania są realizowane sekwencyjnie, ich opóźnienia się sumują; gdy działają równolegle, to najwolniejsze wywołanie określa tempo, a ograniczenia połączeń przeglądarki dla poszczególnych domen lub zależności między wywołaniami mogą sparaliżować procesy, które uważano za równoczesne.
Dlatego stwierdzenie „nasza API jest szybka” to początek dochodzenia, a nie odpowiedź. Kolejne pytania są nieprzyjemne, ale konieczne: szybka w porównaniu z czym, szybka dla kogo i szybka mierzona od kiedy? W obrębie granic waszej usługi, czy od chwili kliknięcia przez użytkownika?
Pozostałe pięć procent to nie błąd zaokrąglenia
Nazwa „P95” sprawia wrażenie, że problem jest niewielki. Pięć procent wydaje się akceptowalną ceną działania na dużą skalę, czymś, co spokojny zespół ds. ochrony środowiska może zaakceptować.
Jednak gdy weźmiemy pod uwagę rzeczywisty wolumen, sytuacja się zmienia. System obsługujący milion zapytań dziennie przy progu P95 ma pięćdziesiąt tysięcy zapytań przekraczających ten próg każdego dnia. Każde z nich to moment, w którym ktoś może ponownie kliknąć, odświeżyć stronę lub porzucić proces i obwinić produkt.
Nawet takie ujęcie jest zbyt uproszczone, ponieważ wolne zapytania nie są rozkładane równomiernie między użytkowników:
- Jeden nieszczęsny użytkownik może wielokrotnie trafić na wolną ścieżkę obsługi.
- Jeden klient korporacyjny może być przymuszony do korzystania z przeciążonego elementu systemu.
- Jeden region może mieć problemy, podczas gdy ogólna sytuacja na świecie pozostaje akceptowalna.
- Proces o wysokiej wartości może być wolny, podczas gdy tanie, częste wywołania utrzymują dobry wynik procentowy.
Na panelu kontrolnym widać rozkład. Użytkownik dotknięty problemem doświadcza czegoś osobistego. Z perspektywy systemu niewielka część żądań była wolna; z perspektywy użytkownika produkt konkretnie marnował jego czas.
Dlatego skargi użytkowników zasługują na potraktowanie jako dane. Ludzie nie zgłaszają problemów w procentach, lecz poprzez historie: „Zawsze się zawiesza po naciśnięciu przycisku wysłania”. „Strona się pokazuje, ale dane przychodzą późno”. „Muszę załadować stronę dwa razy”. „Funkcjonuje, ale działa powoli”. Żadna z tych informacji nie jest niejasna. Każda to raport o wydajności napisany prostym językiem, a wspólnym błędem jest przekształcanie go w niewłaściwą miarę i wnioskowanie, że nic nie jest nie tak.
Dwa usługi, jeden P95 – bardzo różne ryzyko
Jeden procentyl nic nie mówi o kształcie rozkładu wokół niego. Rozważmy dwa usługi o podobnym P95. Pierwsza jest zwyczajna: większość żądań ma umiarkowaną prędkość, te wolniejsze są tylko nieco wolniejsze i nie występują nagłe spadki. Druga jest szybka przez większość czasu, ale od czasu do czasu spada do poziomu kilku sekund.
SERVICE A
Most Requests 120ms - 260ms
Slow Requests 300ms - 380ms
Disaster Requests Rare
SERVICE B
Most Requests 40ms - 80ms
Slow Requests 300ms
Disaster Requests 8s - 12s
Oba mogą pokazywać mniej więcej ten sam P95. Tylko usługa B ma „pułapkę”, która znajduje się całkowicie poza 95. procentylem, poza zasięgiem jego pomiarów. Analiza wartości P99, P99,9 oraz maksymalnych wartości, a jeszcze lepiej histogramu lub mapy ciepła opóźnień, ujawnia tę różnicę.
Procentyl ukrywa również to, gdzie koncentruje się problem. Nie poinformuje on o tym, czy wolne żądania pochodzą od jednego użytkownika, jednego punktu końcowego, jednej partycji bazy danych, jednej zależności poniżej w łańcuchu, jednego regionu, jednej ścieżki ponownej próby, jednego przeciążonego puli wątków, czy też od funkcji używanej tylko podczas ważnych demonstracji. Rozbicie opóźnienia według tych wymiarów przekształca liczbę w diagnozę.
Zasada praktyczna: panel z danymi procentylowymi powinien uruchomić rozmowę, a nie ją zakończyć. Pytanie brzmi nie tylko „jaki jest nasz P95?”, ale także „jakiego rodzaju problem kryje się za tą liczbą?”
Najgorsze opóźnienia występują pomiędzy zespołami
Część problemów z opóźnieniami jest niemal łatwa do rozwiązania, ponieważ przyczyna jest oczywista. Powolna zapytanie otrzymuje indeks, brakująca pamięć cache jest ostrożnie dodana, zbyt duży obciążenie danych zostaje skrócone, a nieefektywny pętla jest przepisany. Wszyscy to rozumieją.
Najtrudniejsze problemy pojawiają się w lukach pomiędzy zespołami. Zespół backendu donosi, że API funkcjonuje prawidłowo. Frontend informuje, że czeka na odpowiedź od backendu. Platforma twierdzi, że brama dostępu działa bez zarzutu. Zespół zajmujący się bazą danych podaje, że baza danych jest w porządku. Zespół produktowy donosi, że użytkownicy nadal są niezadowoleni. Każdy z nich mówi prawdę, co właśnie sprawia, że sytuacja jest tak frustrująca.
W takich przypadkach system nie jest wolny w żadnym oczywistym miejscu – wolność występuje w połączeniach pomiędzy poszczególnymi elementami:
- w przenoszeniu danych pomiędzy usługami;
- w próbie ponownego wykonania zapytania, która podwaja czas trwania operacji;
- w drugim żądaniu, którego w ogóle nie powinno być wysyłane;
P95 nie wprowadziło nikogo w błąd, będąc fałszywe – wprowadziło błąd, będąc pojedynczym wskaźnikiem. Izolowany metryk pozwala każdej zespołowi udowadniać swoją niewinność, podczas gdy użytkownik nadal czeka.
Czas spędzony w kolejce to nadal opóźnienie
Istnieje bardziej subtelny problem z mierzeniem: timerzy na poziomie aplikacji często uruchamiają się zbyt późno. Zanim twoje narzędzie obsługi lub kontroler zaczną działać, żądanie może już spędzić czas w oczekiwaniu przy balanserze obciążeń, na połączeniu, na dostęp do wątku lub procesora, albo w kolejce po inne zadania, ponieważ proces był technicznie uruchomiony, ale w praktyce przeciążony. W Node.js ten sam efekt objawia się jako opóźnienie w pętli zdarzeń: zajęta pętla oznacza, że funkcje zwrotne, w tym twoje narzędzie obsługi żądania, uruchamiają się później niż w momencie, gdy sokiet otrzymał dane.
Następnie twój timer się uruchamia, kod wykonywa się szybko, a metryka rejestruje szybkie żądanie. Użytkownik nie przejmuje się tym, gdzie dokładnie znajdowała się granica twojego zegara. Czekanie przed uruchomieniem timera nadal się liczy. Podobnie jak czekanie podczas prób ponownych, oczekiwanie na wysłanie przez przeglądarkę kolejnego wymaganego wywołania oraz czekanie, aż nieaktywna przycisk stanie się klikalna.
To jest pułapka granic pomiarów. Zespoły mierzą to, co ich usługa może zaobserwować, a następnie domyślnie traktują wszystko poza tym zakresem jako nieistniejące. Opóźnienie, którego nie można zobaczyć, nie jest wyimaginowane; to opóźnienie, za które nikt nie odpowiada, a takie opóźnienia zazwyczaj są najdroższe, ponieważ nikt nie jest odpowiedzialny za jego zmniejszenie. Czas trwania zapytań w balanserze obciążeń, metryki oczekiwania w puli połączeń, opóźnienia w pętli zdarzeń oraz czasy działania przeglądarki to typowe sposoby na ponowne ujawnienie tych interwałów.
Nazewnictwo poszczególnych etapów zapytania
To, co zwykle rozstrzyga te spory, to nie kolejna karta sterująca, nowy dostawca ani większy budżet na monitorowanie. Chodzi o to, by uniemożliwić ukrycie wolnych części procesu, nadając każdej z nich uczciwą nazwę.
Podany poniżej fragment to obsługa w Spring Boot w Javie, ale ten wzorzec można bezpośrednio zastosować w Express, Fastify lub NestJS. Dla żądania GET /home uruchamiany jest obiekt LatencyTrace, który jest powiązany z nagłówkiem X-Request-Id przysylanym w żądaniu. Każdy istotny krok – ładowanie użytkownika, pobieranie uprawnień, wywoływanie usług związanych z kontami i ofertami oraz mapowanie odpowiedzi – jest otoczony instrukcją trace.time(...) wraz z opisowym etykietą. W bloku finally instrukcja trace.logIfTotalCrosses(Duration.ofMillis(700)) wyświetla szczegółowy raport tylko wtedy, gdy całkowity czas obsługi żądania przekracza 700 ms; dzięki temu log pozostaje cichy przy normalnym ruchu, a szczegółowy przy wolnym ruchu. Dokładna implementacja jest mniej ważna niż nawyk, który się poprzez nią kształtuje.
@GetMapping("/home")
public HomeResponse home(@RequestHeader("X-Request-Id") String requestId) {
LatencyTrace trace = LatencyTrace.start("GET /home", requestId);
try {
User user = trace.time("db.user", () ->
userRepository.findActiveUser()
);
Permissions permissions = trace.time("auth.permissions", () ->
permissionClient.fetch(user.id())
);
List<Account> accounts = trace.time("accounts.http", () ->
accountClient.fetchAccounts(user.id())
);
List<Offer> offers = trace.time("offers.http", () ->
offerClient.fetchEligibleOffers(user.id())
);
return trace.time("response.mapping", () ->
HomeResponse.from(user, permissions, accounts, offers)
);
} finally {
trace.logIfTotalCrosses(Duration.ofMillis(700));
}
}
Warto zwrócić uwagę na dwa szczegóły. Po pierwsze, blok finally gwarantuje zapisanie informacji o błędzie nawet wtedy, gdy jeden z kroków wywoła błąd, co jest dokładnie tym momentem, gdy tego najbardziej potrzebujemy. Po drugie, kroki są wykonywane jeden po drugim, więc całkowity czas to mniej więcej suma ich czasów trwania; jeśli zaimplementujesz równoległe wywołania niezależnych funkcji, takich jak te dotyczące kont i ofert, system nadal powinien mierzyć czas każdego z nich, aby można było stwierdzić, który z nich determinuje ogólny czas wykonywania.
Porównaj to, co daje zwykły rejestr latencji, z tym, co dostarcza ten system. Zwykła wersja to pojedynczy wiersz z podanym łącznym czasem:
GET /home took 1180ms
Jest dokładna, ale praktycznie bezużyteczna, ponieważ nie mówi nic o tym, gdzie poszło 1 180 ms. Wersja oparta na poziomie poszczególnych kroków odpowiada na to natychmiast:
GET /home took 1180ms
db.user 42ms
auth.permissions 81ms
accounts.http 156ms
offers.http 823ms
response.mapping 28ms
Gdy taki wynik pojawia się przed grupą, ton dyskusji ulega zmianie. Nikt nie musi zgadywać ani bronić swojego rozwiązania, a stwierdzenie „działa u mnie” przestaje być istotnym wkładem. Oferty stanowią około 70 procent zapytań, więc rozmowa przechodzi od opinii do dowodów. To jest prawdziwa korzyść z dobrego instrumentarium: nie więcej wykresów, ale mniej argumentów.
W kodzie Node.js można osiągnąć ten sam efekt, umieszczając performance.now() wokół każdej wywołania oczekującego na wynik oraz używając uporządkowanego narzędzia do logowania, albo w sposób bardziej systematyczny poprzez elementy OpenTelemetry, które przenoszą te same nazwy kroków między różnymi usługami, dzięki czemu analiza pozostaje dokładna nawet wtedy, gdy wolny krok znajduje się w innej usłudze. Aby zapoznać się z bardziej szczegółowym omówieniem łączenia logów, metryk i śladów, zobacz budowanie obserwowalnych API w Node.js.
Dlaczego czyste testy wydajności mogą uporządkować chaotyczny system
To właśnie w tym miejscu raporty o wydajności zazwyczaj przedstawiają tabelę testów. Istnieje dobry powód, by tego tutaj nie robić. Testy wydajności są cenne, gdy są uczciwe, ale wymyślony test w tym celu byłby jedynie kolejną mylącą liczbą ubraną w formalny wygląd.
Problem nie polega na tym, że inżynierowie nie mogą przeprowadzać kontrolowanych testów. Chodzi o to, że takie testy mają tendencję do eliminowania właśnie tych problemów, które szkodzą użytkownikom:
- Benchmark testuje jeden punkt końcowy; rzeczywista akcja użytkownika uruchamia wiele z nich.
- Benchmark działa przy wykorzystaniu już załadowanych pamięci cache; użytkownicy pojawiają się po wdrożeniach, usuwaniach i przy wykorzystaniu mniej efektywnych ścieżek kodu.
- Benchmark mierzy udane odpowiedzi; użytkownicy muszą radzić sobie z częściowymi błędami, ponawianymi próbami, przekroczeniami czasu reakcji oraz wyłączonymi ekranami.
- Benchmark zakłada stabilne zależności; zależności w środowisku produkcyjnym są sprawne aż do dnia, w którym przestają nim być.
- Benchmark izoluje jeden serwis; to, z czym mają do czynienia użytkownicy, składa się z wielu serwisów w połączeniu z przeglądarkami, sieciami, kolejkami, umowami API oraz decyzjami zespołów, które mogą już ze sobą nie komunikować się.
Benchmark opisuje, jak szybki może być kontrolowany ścieżka działania. Produkcja natomiast pokazuje, na jak wiarygodny jest cały system. Są to różne miary, a jedna nie zastępuje drugiej.
Szybki kod nie gwarantuje szybkiego produktu
Wielu inżynierów backendu odczuwa głębokie zadowolenie po lokalnej optymalizacji. Uda się zmniejszyć czas wykonywania funkcji z 20 ms do 8 ms, usunąć niepotrzebną alokację pamięci, przeprojektować pętlę lub dostosować zapytanie – różnica wydaje się znacząca. Tymczasem produkt nadal czeka na odległą usługę, która na każde dziesiąte żądanie potrzebuje 900 ms. Ekran nadal długo zajmuje czas, zanim stanie się użyteczny, przycisk nadal się zamyka, a zgłoszenia obsługi klienta wciąż opisują problemy jako „powolne”.
To jak polerowanie mosiądzu, podczas gdy podłoga się zapada. Prace lokalne są atrakcyjne, ponieważ znajdują się w pełni pod twoją kontrolą. Opóźnienia od początku do końca są bardziej skomplikowane, ponieważ ich poprawa wymaga udziału innych zespołów, starych umów, wyborów produktowych oraz architektury, której nikt nie chce ponownie analizować. Użytkownicy jednak nie nagradzają lokalnej elegancji – nagradzają zakończone działania. Szybka funkcja jest miła, szybki endpoint jest lepszy, a szybka ścieżka to to, co naprawdę ma znaczenie. Zanim optymalizujesz jakikolwiek pojedynczy komponent, warto sprawdzić, czy znajduje się on na kluczowej ścieżce procesu, którym ktoś się interesuje.
Gdy najwolniejszą zależnością jest decyzja
Nie każdy problem z opóźnieniami ma charakter techniczny. Często są to decyzje, które utrwaliły się w architekturze:
- Frontend wywołuje sześć API, podczas gdy wystarczyłaby jedna zintegrowana odpowiedź.
Najwolniejszą częścią systemu jest często wybór produktu, który nigdy nie został zmierzony, luka w zarządzaniu własnością lub spotkanie sprzed dawna, którego rezultatem stała się trwała opóźniona reakcja. Wiele z tych problemów ma dobrze znane rozwiązania, takie jak backend dla frontendu, który agreguje żądania, opóźnione ładowanie niekluczowych elementów interfejsu, przeniesienie prób ponownych i analiz poza krytyczną ścieżkę oraz projekty odpowiedzi dostosowane do rozmiaru ekranu. Każde z tych rozwiązań wymaga jednak, aby ktoś miał uprawnienia do zmiany podjętej decyzji.
Dlatego praca nad wydajnością nabiera charakteru politycznego, gdy już zastosowano proste rozwiązania. Pozostała opóźniona reakcja staje się kwestią odpowiedzialności. Kto ma prawo uznać daną zależność za obowiązkową? Kto odpowiada za cały proces? Kto może usunąć dane żądanie? Kto jest odpowiedzialny, gdy wszystkie usługi działają poprawnie, a doświadczenie użytkownika nadal jest kiepskie? P95 nie potrafi odpowiedzieć na żadne z tych pytań i nigdy tak nie miało być.
Zielone panele kontrolne mogą zmniejszyć ciekawość zespołów
Mniej przyjemna obserwacja: zielony panel kontrolny może sprawić, że zespół przestanie zadawać pytania. Nie zawsze, ale na tyle często, by to miało znaczenie. Gdy wykres staje się czerwony, wszyscy zaczynają badać przyczynę. Gdy wykres jest zielony, a użytkownik skarży się, rozmowa zmierza w kierunku usprawiedliwiania tej skargi. Być może ich łącze było słabe, być może przeglądarka działała wolno, być może chodziło o pojedynczy przypadek, obsługa przesadziła, frontend zachował się nietypowo, klient kliknął zbyt wcześnie albo skarga ma charakter emocjonalny, a nie techniczny.
Tak właśnie zespoły tracą zaufanie – nie z powodu braku danych, lecz dlatego, że używają danych jako wymówki, by nie słuchać. Użytkownik nie ma twojego panelu kontrolnego; ma twój produkt. Jeśli dla niego produkt wydaje się wolny, to jest dowód. To nie jest pełny ani doskonały dowód, ale to jednak dowód.
Zdrowa kultura inżynieryjna traktuje wykres jako jeden ze źródeł informacji, a użytkownika jako kolejne, przy czym produkcja stanowi scenę do zbadania. Kultura, która traktuje wykresy jako prawdę, a użytkowników jako szum, powoduje wypuszczanie wolnych produktów, mimo że uważa się je za szybkie.
Zacznij od tego, co próbował zrobić użytkownik
Dyskusje na temat wydajności zwykle koncentrują się wokół poszczególnych komponentów: który serwis, który punkt końcowy, która zapytanie jest wolne. To dobre pytania, ale rzadko są najlepszym pierwszym pytaniem. Lepszym punktem wyjścia jest: co próbował osiągnąć użytkownik? Gdy już na to odpowiesz, zazwyczaj sama wynika właściwa miara:
- W przypadku otwierania panelu sterowania zmierz czas od momentu próby otwarcia do chwili, gdy panel jest faktycznie gotowy do użycia.
- Przy wysyłaniu formularza zmierz czas od kliknięcia do momentu, gdy pojawia się widoczna potwierdzenie.
- W przypadku pobierania raportu zmierz czas od kliknięcia do chwili, gdy plik staje się dostępny.
To brzmi oczywiste, ale wiele systemów nie jest tak instrumentowanych. Są one mierzone wzdłuż granic zespołów, ponieważ granice te są łatwe do instrumentacji i pozwalają na stworzenie uporządkowanych paneli kontrolnych. Ścieżki użytkownika są trudniejsze do pomiaru i dają mniej korzystne wyniki, a właśnie z tych mniej korzystnych wyników wynikają ulepszenia produktu. Po stronie klienta interfejsy API przeglądarki, takie jak Performance i User Timing, umożliwiają wyraźne oznaczenie momentów „kliknięcia przez użytkownika” oraz „dostępności ekranu”, a powiązanie tych oznaczeń z identyfikatorem żądania łączy ludzki czas z śladami działania serwera. Jeśli problemem okazuje się wolny proces renderowania, a nie sieć, artykuł na temat diagnozowania problemów wydajności React poza czasem odpowiedzi API omawia tę stronę problemu.
Traktuj wskaźniki z ostrożną rezerwą
To wszystko nie jest argumentem za usunięciem P95 z twoich paneli kontrolnych. Ono tam należy i ma znaczenie. Po prostu nie powinno być ufaane samodzielnie. Dobry procentyl powinien wzbudzić twoją ciekawość, skierowując uwagę na żądania znajdujące się poza progiem:
- Kto dokładnie należy do tych powolnych pięciu procent i czy to zawsze ci sami użytkownicy?
- Do jakich ścieżek użytkowników należą te żądania?
- Czy jedna z zależności jest odpowiedzialna za większość problemów?
- Czy określony region ma gorszą wydajność, podczas gdy ogólny obraz wygląda dobrze?
- Czy konkretny użytkownik stale ma najgorsze warunki?
- Czy próby ponownych wysyłek potajemnie przekształcają błędy w długie czekanie?
- Ile czasu żądania spędzają w kolejce, zanim w ogóle rozpocznie się timer aplikacji?
Prawidłowa relacja z metryką to ani uwielbienie, ani odrzucenie – to pełne szacunku podejrzenie. Metryka jest świadkiem, a świadkowie mogą pominąć ważne informacje.
Główne wnioski
Niebezpiecznym scenariuszem nie jest metryka, która jest wyraźnie błędna – taką łatwo byłoby zauważyć. Chodzi o metrykę, która tak pewnie przedstawia małą prawdę, że wszyscy przestają szukać tej większej. Następnym razem, gdy ktoś powie, że P95 jest w porządku, nie kłóć się o tę liczbę; zapytaj, co zostało pominęte. Czy uwzględniła czas oczekiwania w kolejce, próby ponowne, czekanie frontendu, połączenie z podmiotem trzecim, to, że użytkownik kliknął ponownie, bo nic się nie wydarzyło, oraz moment, w którym ekran stał się faktycznie użyteczny do wykonywania zadania, którego chciał dokonać klient?
- P95 przewyższa średnie wartości, ale ukrywa kształt rozkładu, stopień ekstremalności wartości oraz osoby, które są tym dotknięte; należy je połączyć z wyższymi percentylami, histogramami oraz podziałem na poszczególnych użytkowników lub regiony.
- Należy odróżnić zegary serwera, zależności oraz ludzi i upewnić się, że każdy z nich ma swojego odpowiedzialnego.
- Proces korzystania przez użytkownika zazwyczaj składa się z wielu kroków; należy mierzyć cały proces, a nie tylko jego końcowe punkty.
- Czas trwania działania aplikacji często nie uwzględnia czasu oczekiwania w kolejce, czasu łączenia się oraz opóźnień w pętli zdarzeń; należy te elementy mierzyć wyraźnie.
- Mierzenie czasu trwania każdego określonego kroku żądania oraz rejestrowanie szczegółów w momencie przekroczenia określonego progu zastępuje oskarżenia dowodami.
- Część opóźnień jest wynikiem decyzji dotyczących produktu i odpowiedzialności za jego funkcjonowanie, a ich naprawa wymaga osoby upoważnionej do dokonania zmian.
- Należy traktować skargi użytkowników jak raporty dotyczące wydajności, a tablice kontrolne jako jeden z wielu źródeł informacji.
Literatura pokrewna
- Dlaczego wzory regex z backtrackingiem mogą cicho zatrzymać twój serwer — Dowiedz się, jak chciwe dopasowywanie i katastrofalne backtracking mogą przekształcić pozornie poprawny wzór regex w problem blokujący CPU i powodujący awarie w produkcji, oraz jak rozpoznać to ryzyko.
- Anty-wzory backendu: 7 drożnych błędów i ich praktyczne rozwiązania — Naucz się, jak wykrywać i naprawiać siedem powszechnych błędów w inżynierii backendu, od przeciążonych kontrolerów po nierozwiązane błędy, zanim spowodują one problemy w produkcji.