Poznaj praktyczne metody Brendana Gregga (USE, RED, flame graphs) do badania opóźnień i wąskich gardeł w produkcji za pomocą danych, a nie zgadywania.

Brendan Gregg jest jednym z najbardziej wpływowych autorytetów w obszarze wydajności systemów, zwłaszcza w środowisku Linuksa. Napisał powszechnie używane książki, stworzył praktyczne narzędzia i—co ważniejsze—upowszechnił jasne metody badania rzeczywistych problemów produkcyjnych. Zespoły stosują jego podejście, ponieważ działa w stresujących sytuacjach: gdy opóźnienia rosną i wszyscy chcą odpowiedzi, potrzebujesz sposobu, by przejść z „może to X” do „to na pewno Y” przy minimalnym zamieszaniu.
Metodyka wydajności to nie pojedyncze narzędzie ani sprytne polecenie. To powtarzalny sposób badania: lista kontrolna tego, co sprawdzić najpierw, jak interpretować obserwacje i jak zdecydować o kolejnych krokach.
To powtarzalność redukuje zgadywanie. Zamiast polegać na osobie z największą intuicją (albo z najsilniejszą opinią), postępujesz według spójnego procesu, który:
Wiele dochodzeń dotyczących opóźnień idzie źle w pierwszych pięciu minutach. Ludzie od razu przechodzą do napraw: „dodaj CPU”, „zrestartuj usługę”, „zwiększ cache”, „dostrój GC”, „pewnie to sieć”. Czasem te działania pomagają—częściej ukrywają sygnał, marnują czas lub wprowadzają nowe ryzyko.
Metody Gregga zachęcają do odroczenia „rozwiązań”, dopóki nie będziesz mógł odpowiedzieć na prostsze pytania: Co jest nasycone? Co zwraca błędy? Co stało się wolniejsze—przepustowość, kolejkowanie czy pojedyncze operacje?
Ten przewodnik pomaga zawęzić zakres, zmierzyć właściwe sygnały i potwierdzić wąskie gardło przed optymalizacją. Celem jest uporządkowany workflow do badania opóźnień i profilowania w produkcji, tak aby wyniki nie zależały od szczęścia.
Opóźnienie jest objawem: użytkownicy czekają dłużej na zakończenie pracy. Przyczyna zwykle leży gdzie indziej—zawartość CPU, oczekiwania na dysk lub sieć, konflikt o blokady, garbage collection, kolejkowanie lub opóźnienia zależności zdalnych. Sam pomiar opóźnienia mówi tylko, że problem istnieje, nie skąd pochodzi.
Te trzy sygnały są ze sobą powiązane:
Przed strojem uchwyć wszystkie trzy sygnały dla tego samego okna czasowego. W przeciwnym razie możesz „naprawić” opóźnienie przez odrzucenie pracy lub szybsze zwracanie błędów.
Średnia ukrywa skoki, które pamiętają użytkownicy. Usługa z medianą 50 ms może mieć częste przestoje po 2 s.
Śledź percentyle:
Obserwuj też kształt rozkładu: stabilne p50 przy rosnącym p99 często wskazuje na przerywane przestoje (np. konflikt o blokadę, problemy I/O, pauzy stop-the-world) zamiast ogólnego spowolnienia.
Budżet opóźnienia to proste rozliczenie: „jeśli żądanie musi zakończyć się w 300 ms, gdzie może pójść ten czas?” Podziel go na kubełki takie jak:
Ten budżet pomaga w pierwszym zadaniu pomiarowym: zidentyfikuj, który kubełek urósł podczas skoku, a potem badaj tę część zamiast stroić na ślepo.
Prace nad opóźnieniami idą na bok, gdy „problem” opisuje się jako system jest wolny. Metody Gregga zaczynają wcześniej: wymuś sprowadzenie problemu do konkretnego, testowalnego pytania.
Zapisz dwie zdania zanim dotkniesz narzędzi:
To zapobiega optymalizowaniu niewłaściwej warstwy—np. CPU hosta—gdy problem dotyczy jednego endpointu lub jednej zależności.
Wybierz okno odpowiadające zgłoszeniu i—jeśli możliwe—includuj okres „dobry” do porównania.
Zakreśl śledztwo jawnie:
Precyzja tutaj przyspiesza późniejsze kroki (USE, RED, profilowanie), bo będziesz wiedzieć, które dane powinny się zmienić, jeśli hipoteza jest prawidłowa.
Zanotuj deploye, zmiany konfiguracji, przemieszczenia ruchu i zdarzenia infra—but nie zakładaj przyczynowości. Formułuj je jako „Jeśli X, to oczekiwalibyśmy Y”, by móc szybko potwierdzić lub obalić.
Mały log zapobiega dublowaniu pracy między członkami zespołu i ułatwia przekazanie sprawy.
Time | Question | Scope | Data checked | Result | Next step
Nawet pięć wierszy jak powyżej może zmienić stresujący incydent w proces powtarzalny.
Metoda USE (Utilization, Saturation, Errors) to szybka lista Gregga do przeskanowania „czterech dużych” zasobów—CPU, pamięć, dysk (storage) i sieć—by przestać zgadywać i zawęzić problem.
Zamiast patrzeć na dziesiątki wykresów, zadawaj te same trzy pytania dla każdego zasobu:
Stosowane konsekwentnie, to szybkie inwentaryzowanie miejsc, gdzie istnieje „ciśnienie”.
Dla CPU wykorzystanie to % użycia CPU, nasycenie objawia się kolejką uruchamiania (run-queue) lub wątkami czekającymi na CPU, a błędy mogą obejmować throttling (w kontenerach) lub źle działające przerwania.
Dla pamięci wykorzystanie to używana pamięć, nasycenie pojawia się jako stronicowanie lub częsty garbage collection, a błędy to awarie alokacji lub OOM.
Dla dysku wykorzystanie to czas zajęcia urządzenia, nasycenie to głębokość kolejki i czasy oczekiwania odczytów/zapisów, a błędy to błędy I/O i timeouty.
Dla sieci wykorzystanie to przepustowość, nasycenie to dropy/kolejki/opóźnienia, a błędy to retransmiti, resety lub utrata pakietów.
Podczas zgłoszeń o wolnym działaniu często najbardziej mówią sygnały nasycenia: kolejki, czas oczekiwania i konflikt zwykle korelują z opóźnieniem bardziej niż surowe wykorzystanie.
Metryki na poziomie usługi (latencja żądań, współczynnik błędów) mówią o wpływie. USE pokazuje gdzie patrzeć dalej, identyfikując zasób pod obciążeniem.
Praktyczna pętla:
Metoda RED utrzymuje Cię przy doświadczeniu użytkownika zanim zanurkujesz w wykresy hosta.
RED zapobiega gonitwie za „interesującymi” metrykami systemowymi, które nie wpływają na użytkowników. Wymusza krótszą pętlę: który endpoint jest wolny, dla których użytkowników i od kiedy? Jeśli Duration rośnie tylko dla jednej trasy, podczas gdy CPU jest stabilne, masz już ostrzejszy punkt startowy.
Dobra praktyka: trzymaj RED rozbite po usłudze i najważniejszych endpointach (lub kluczowych metodach RPC). To ułatwia odróżnienie szerokiego pogorszenia od lokalnej regresji.
RED mówi gdzie boli. USE pomaga przetestować który zasób jest za to odpowiedzialny.
Przykłady:
Utrzymuj prosty układ:
Jeśli chcesz spójnego workflow incydentu, połącz ten widok z inwentaryzacją USE w /blog/use-method-overview, aby łatwiej przejść od „użytkownicy to czują” do „ten zasób jest ograniczeniem”.
Dochody wydajności mogą eksplodować do dziesiątek wykresów i hipotez w minutę. Myślenie Gregga to utrzymanie wąskości: Twoim zadaniem nie jest „zbierać więcej danych”, lecz zadać kolejne pytanie, które najszybciej wyeliminuje niepewność.
Większość problemów z opóźnieniami wynika z jednego dominującego kosztu (lub niewielkiej pary): jedna gorąca blokada, jedna wolna zależność, jeden przeciążony dysk, wzorzec pauz GC. Priorytetyzacja to polowanie na ten dominujący koszt najpierw, bo ścięcie 5% w pięciu miejscach rzadko poprawia widoczne przez użytkownika opóźnienia.
Praktyczny test: „Co może wyjaśnić większość zmiany opóźnienia, którą widzimy?” Jeśli hipoteza tłumaczy tylko mały fragment, jest niższym priorytetem.
Użyj podejścia z góry gdy odpowiadasz na „czy użytkownicy są dotknięci?” Zacznij od endpointów (sygnały w stylu RED): opóźnienie, przepustowość, błędy. To pomaga unikać optymalizacji czegoś, co nie jest na ścieżce krytycznej.
Użyj podejścia z dołu gdy host jest ewidentnie chory (objawy USE): saturacja CPU, narastająca pamięć, I/O wait. Jeśli node jest „zaparkowany”, tracisz czas patrząc tylko na percentyle endpointów bez zrozumienia ograniczenia.
Gdy alarm wskoczy, wybierz gałąź i trzymaj się jej, dopóki nie potwierdzisz lub nie obalisz:
Ogranicz się do małego zestawu sygnałów startowych, potem drąż tylko gdy coś się zmienia. Jeśli potrzebujesz checklisty, połącz kroki z runbookiem, np. /blog/performance-incident-workflow, by każdy nowy wykres miał cel: odpowiedzieć na konkretne pytanie.
Profilowanie produkcyjne może wydawać się ryzykowne, bo dotyka systemu live—jednak często to najszybszy sposób, by debatę zastąpić dowodem. Logi i dashboardy mówią co jest wolne. Profilowanie mówi gdzie idzie czas: które funkcje są gorące, które wątki czekają i jakie ścieżki kodu dominują podczas incydentu.
Profilowanie to narzędzie do „budżetu czasu”. Zamiast debatować „baza danych vs GC”, otrzymujesz dowód typu „45% prób CPU to parsowanie JSON” lub „większość żądań blokuje się na mutexie”. To zawęża następny krok do jednego–dwóch konkretnych poprawek.
Każdy odpowiada na inne pytanie. Wysokie opóźnienia przy niskim CPU często wskazują na off-CPU lub oczekiwania na blokady, nie na gorące punkty CPU.
Wiele zespołów zaczyna od on-demand, a potem przechodzi na always-on, gdy ufa bezpieczeństwu i widzi powtarzalne problemy.
Profilowanie bezpieczne w produkcji to kontrola kosztu. Preferuj sampling (nie śledzenie każdego zdarzenia), trzymaj okna przechwycenia krótkie (np. 10–30 s) i najpierw zmierz narzut na kanarku. Jeśli nie jesteś pewien, zacznij od niskiej częstotliwości próbkowania i zwiększaj tylko, gdy sygnał jest zbyt zaszumiony.
Flame graphs wizualizują, gdzie próbki czasu trafiły podczas okna profilowania. Każdy „blok” to funkcja (lub ramka stosu), a każdy stos pokazuje, jak wykonanie dotarło do tej funkcji. Są świetne do szybkiego wykrywania wzorców—ale nie mówią automatycznie „tu jest błąd”.
Flame graph zwykle reprezentuje próbki on-CPU: czas, gdy program rzeczywiście wykonywał się na rdzeniu CPU. Może uwypuklić fragmenty kodu intensywnie używające CPU, nieefektywne parsowanie, nadmierną serializację lub hotspoty.
Nie pokazuje bezpośrednio oczekiwań na dysk, sieć, opóźnień schedulera ani blokad mutexów (to jest off-CPU i potrzebuje innego profilu). Nie dowodzi też kauzalności dla widocznego przez użytkownika opóźnienia, dopóki nie powiążesz go z zakresem i symptomem.
Najszerszy blok kusi, by go winić, ale zapytaj: czy to hotspot, który możesz zmienić, czy tylko „czas spędzony w malloc, GC lub logowaniu”, ponieważ prawdziwy problem jest powyżej? Uważaj też na brakujący kontekst (JIT, inlining, symbole), który może sprawić, że ramka wygląda jak winowajca, podczas gdy jest tylko pośrednikiem.
Traktuj flame graph jako odpowiedź na sformułowane pytanie: który endpoint, które okno czasowe, które hosty i co się zmieniło. Porównuj „przed vs po” (lub „zdrowe vs zdegradowane”) flame graphs dla tej samej ścieżki żądania, by uniknąć szumów profilowania.
Gdy latencja rośnie, wiele zespołów patrzy najpierw na % CPU. To zrozumiałe—ale często mylące. Usługa może mieć „tylko 20% CPU” i nadal być bolesnie wolna, jeśli wątki spędzają większość życia nie wykonując pracy.
% CPU odpowiada „jak zajęty jest procesor?”. Nie odpowiada „gdzie poszedł mój czas żądania?”. Żądania mogą stać, gdy wątki czekają, są zablokowane lub zaparkowane przez scheduler.
Kluczowa idea: czas rzeczywisty żądania zawiera zarówno pracę on-CPU, jak i czas oczekiwania off-CPU.
Off-CPU kryje się zwykle za zależnościami i konfliktem:
Kilka sygnałów często koreluje z off-CPU:
Te objawy mówią „czekamy”, ale nie na co dokładnie.
Profil off-CPU przypisuje czas do powodu, dla którego nie działałeś: zablokowany w syscallach, czekanie na blokadę, spanie lub deschedulacja. To potężne w pracy nad latencją, bo zamienia ogólne spowolnienia w mierzalne i działania kategorie: „zablokowany na mutexie X”, „czeka na read() z dysku” lub „utknął w connect() do upstreamu”. Gdy możesz nazwać oczekiwanie, możesz je zmierzyć, potwierdzić i naprawić.
Prace nad wydajnością często zawodzą w tym samym momencie: ktoś zauważa podejrzaną metrykę, ogłasza ją „problemem” i zaczyna stroić. Metody Gregga zachęcają do zwolnienia i udowodnienia, co ogranicza system, zanim cokolwiek zmienisz.
Wąskie gardło to zasób lub komponent, który aktualnie ogranicza przepustowość lub napędza opóźnienie. Jeśli go złagodzisz, użytkownicy zauważą poprawę.
Hot spot to miejsce, gdzie spędza się czas (np. funkcja widoczna często w profilu). Hot spoty mogą być prawdziwymi wąskimi gardłami—albo po prostu pracą nie mającą wpływu na ścieżkę krytyczną.
Szum to wszystko, co wygląda znacząco, a nie jest: zadania w tle, jednorazowe skoki, artefakty próbkowania, efekty cache'owania czy „top talkerzy”, którzy nie korelują z problemem widocznym dla użytkownika.
Zacznij od czystego snapshotu przed: symptom widoczny dla użytkownika (latencja lub błąd) oraz wiodące sygnały kandydatów (saturacja CPU, głębokość kolejek, I/O dysku, kontencja blokad itp.). Potem zastosuj kontrolowaną zmianę, która powinna wpływać tylko na podejrzaną przyczynę.
Przykłady testów przyczynowości:
Korelacja to wskazówka, nie werdykt. Jeśli „CPU rośnie, gdy latencja rośnie”, zweryfikuj przez zmianę dostępności CPU lub zmniejszenie pracy CPU i obserwuj, czy latencja podąża za tym.
Zapisz: co zmierzono, dokładną wprowadzoną zmianę, wyniki przed/po i obserwowaną poprawę. To zmienia jednorazowe zwycięstwo w odtwarzalny playbook na następny incydent—i zapobiega późniejszemu przepisywaniu historii przez „intuicję”.
Incydenty wydajnościowe są pilne, a to dokładnie moment, gdy pojawia się zgadywanie. Lekki, powtarzalny workflow pomaga przejść od „coś jest wolne” do „wiemy, co się zmieniło” bez niepotrzebnego zamieszania.
Wykryj: alarmuj na podstawie widocznej dla użytkownika latencji i błędów, nie tylko CPU. Alarmuj, gdy p95/p99 przekracza próg przez utrzymane okno.
Triage: natychmiast odpowiedz na trzy pytania: co jest wolne, kiedy to się zaczęło i kogo dotyczy? Jeśli nie możesz określić zakresu (usługa, endpoint, region, kohorta), nie jesteś gotów do optymalizacji.
Mierz: zbierz dowody zawężające wąskie gardło. Preferuj przechwycenia o ograniczonym czasie (np. 60–180 s), by móc porównać „źle” vs „dobrze”.
Napraw: zmień jedną rzecz naraz, potem zmierz te same sygnały, by potwierdzić poprawę i wyeliminować efekt placebo.
Utrzymuj wspólny dashboard, którego wszyscy używają podczas incydentów. Niech będzie nudny i spójny:
Celem nie jest wykreślenie wszystkiego; chodzi o skrócenie czasu do pierwszego faktu.
Zainstrumentuj endpointy, które mają największe znaczenie (checkout, logowanie, wyszukiwanie), a nie każdy endpoint. Dla każdego ustal oczekiwane p95, maksymalny współczynnik błędów i kluczową zależność (DB, cache, zewnętrzne API).
Przed następnym outage'em uzgodnij zestaw do przechwytywania:
Udokumentuj to w krótkim runbooku (np. /runbooks/latency), w tym kto może uruchamiać przechwycenia i gdzie przechowywać artefakty.
Metodyka Gregga to zasadniczo kontrolowana zmiana i szybka weryfikacja. Jeśli Twój zespół buduje serwisy używając Koder.ai (platforma chat-driven do generowania i iterowania aplikacji webowych, backendowych i mobilnych), dwie funkcje dobrze pasują do tego podejścia:
Nawet jeśli nie generujesz nowego kodu podczas incydentu, te nawyki—małe diffy, mierzalne wyniki i szybka odwracalność—to te same nawyki, które Gregg promuje.
Jest 10:15 i dashboard pokazuje p99 API rosnące z ~120 ms do ~900 ms podczas szczytu. Współczynnik błędów jest stabilny, ale klienci zgłaszają „opóźnienia”.
Zacznij od widoku usługi: Rate, Errors, Duration.
Dzielenie Duration po endpointach pokazuje, że jedna trasa dominuje p99: POST /checkout. Rate wzrósł 2×, błędy są normalne, ale Duration rośnie głównie przy wzroście współbieżności. To wskazuje na kolejkowanie lub kontencję, a nie całkowitą awarię.
Sprawdź teraz, czy opóźnienie to czas obliczeń czy oczekiwania: porównaj „czas handlera” aplikacji vs całkowity czas żądania (lub upstream vs downstream spans, jeśli masz tracing). Czas handlera jest niski, a całkowity wysoki—żądania czekają.
Zrób inwentaryzację prawdopodobnych wąskich gardeł: Utilization, Saturation, Errors dla CPU, pamięci, dysku i sieci.
Wykorzystanie CPU to tylko ~35%, ale run queue i konteksty przełączeń rosną. Dysk i sieć wyglądają stabilnie. Ta rozbieżność (niski % CPU, duże oczekiwanie) to klasyczny sygnał: wątki nie palą CPU—są zablokowane.
Przechwyciłeś profil off-CPU podczas skoku i znajdujesz dużo czasu w mutexie wokół współdzielonego cache'a „promotion validation”.
Zamieniasz globalny lock na per-key lock (lub ścieżkę odczytu bez blokady), wdrażasz i obserwujesz, jak p99 wraca do normy przy utrzymanym wysokim Rate.
Lista kontrolna po incydencie: