Dobierz rozmiar puli wątków aplikacji na podstawie tego, ile żądania pracują, a ile czekają.
Każdy wątek sięgający do bazy trzyma połączenie, czekając, więc ta pula wyznacza dolną granicę twojej puli połączeń.
Dlaczego właściwa pula wątków jest mniejsza, niż myślisz — dopóki nagle nie jest
Pula wątków ma jedno zadanie: utrzymać CPU zajęte, nie pozwalając, by praca piętrzyła się wewnątrz procesu. Rozmiar, który to osiąga, zależy niemal wyłącznie od jednej proporcji — ile żądanie czeka na coś innego wobec tego, ile faktycznie liczy. To narzędzie stosuje standardowy wzór na tę proporcję, sprawdza go prawem Little'a i mówi, kiedy wynik oznacza, że potrzebujesz innego modelu współbieżności, a nie większej liczby.
Jak to działa
- Stosuje klasyczny wzór: rdzenie × docelowe wykorzystanie × (1 + czekanie ÷ praca).
- Prawem Little'a wylicza współbieżność, jaką naprawdę implikuje twoja docelowa przepustowość.
- Podaje przepustowość, jaką zalecana pula utrzyma, żebyś zobaczył, czy pokrywa twój cel.
- Ostrzega, gdy arytmetyka wyszła poza to, co model wątek-na-żądanie może sensownie udźwignąć.
wątki = rdzenie x wykorzystanie x (1 + czas_czekania / czas_pracy) prawo Little'a współbieżność = tempo_napływu x opóźnienie sufit przepustowości maks_rps = wątki / (czas_pracy + czas_czekania)
Przykład z liczbami
Usługa na 8 rdzeniach, cel 80% CPU, gdzie żądanie liczy 20 ms i czeka 80 ms na bazę, przy szczycie 300 żądań na sekundę.
- czekanie ÷ praca = 80 ÷ 20 = 4
- wątki = 8 × 0,8 × (1 + 4) = 32
- opóźnienie = 20 + 80 = 100 ms
- prawo Little'a: 300 żądań/s × 0,1 s = 30 równoczesnych żądań
- 32 wątki ≥ 30, więc pula pokrywa cel
- sufit: 32 ÷ 100 ms = 320 żądań na sekundę
Trzydzieści dwa wątki na ośmiu rdzeniach. Pula jest czterokrotnością liczby rdzeni nie dlatego, że wątki są darmowe, lecz dlatego, że każdy jest bezczynny przez 80% czasu — trzyma miejsce w kolejce do bazy, a nie używa CPU. Zmień czekanie na zero, a ten sam wzór zwróci 6, co jest właściwą odpowiedzią dla pracy, która nigdy się nie blokuje.
Jak czytać wynik
- Wzór jest tak dobry, jak dwa czasy, które mu podasz. Czas pracy to czas CPU, a nie czas zegarowy, i łatwo je pomylić — zegarowy zawiera czekanie, czyli tę drugą zmienną. Profiler je rozdziela, stoper nie.
- Każdy wątek odpytujący bazę trzyma połączenie, czekając. To czyni pulę wątków dolną granicą puli połączeń i jest to typowy powód wyczerpania limitu połączeń: nikt nie dobrał obu razem.
- Powyżej kilkuset wątków problemem staje się sam model. Każdy wątek systemowy to około megabajt stosu i miejsce w harmonogramie, więc pula 1300 spędza więcej czasu na przełączaniu kontekstu niż na pracy. Przy takiej proporcji odpowiedzią jest nieblokujące I/O albo wątki wirtualne — dlatego narzędzie to mówi, zamiast wypisać liczbę i zostawić cię z nią.
- Kolejka przed pulą to nie dodatkowa przepustowość. Jeśli napływ przewyższa to, co pula zdąży obsłużyć, kolejka rośnie bez ograniczeń, a z nią opóźnienie — żądania i tak padną, tylko później i po zużyciu pamięci. Ogranicz kolejkę i odrzucaj nadmiar, zamiast udawać, że głębokość to przepustowość.
- Cele wykorzystania powyżej mniej więcej 80% przestają być bezpieczne. Teoria kolejek jest bezlitosna blisko nasycenia: gdy wykorzystanie zbliża się do 100%, czas oczekiwania rośnie bez granic, więc ostatnie 20% rdzenia kosztuje znacznie więcej opóźnienia, niż zwraca przepustowości.
Częste pytania
- Skąd bierze się wzór (1 + czekanie/praca)?
- To standardowy wynik na utrzymanie N rdzeni zajętymi, gdy zadanie zajmuje rdzeń tylko przez część swojego życia; spopularyzował go Brian Goetz w Java Concurrency in Practice. Bez czekania redukuje się do rdzenie × wykorzystanie, co jest poprawne dla pracy związanej z CPU; proporcja po prostu uwzględnia wątki zaparkowane na I/O w danej chwili.
- Czy pula wątków powinna odpowiadać puli połączeń?
- Pula połączeń powinna być co najmniej tak duża jak liczba wątków mogących jednocześnie być w wywołaniu bazy, ale nie większa, niż limit bazy zniesie w skali całej floty. Gdy jedno kłóci się z drugim, rozwiązaniem jest mniej wątków albo więcej instancji aplikacji — a nie większa pula połączeń, która tylko przenosi rywalizację do bazy.
- Czy wątki wirtualne czynią to nieaktualnym?
- Zmieniają to, która liczba ma znaczenie. Wątki wirtualne są na tyle tanie, że możesz mieć jeden na żądanie bez dobierania puli, więc proporcja czekania przestaje cię ograniczać. Zostaje limit niżej: pula połączeń bazy, limit tempa w usłudze zewnętrznej, samo CPU. Te wciąż trzeba dobrać, a ten rachunek dalej mówi, gdzie się zacinają.
- Jak zmierzyć czas pracy i czas czekania?
- Czas pracy weź z profilowania CPU albo z różnicy między czasem zegarowym a sumą oprzyrządowanych wywołań zewnętrznych. Czas czekania weź ze span-ów, które twoje śledzenie już emituje dla wywołań bazy i HTTP. Jeśli nie masz żadnego, zacznij od świadomego oszacowania i sprawdź prognozę: jeśli pula utrzymuje znacznie mniej, niż mówi wzór, twoje oszacowanie czekania było za niskie.