Skip to main content

Dobierz rozmiar puli wątków aplikacji na podstawie tego, ile żądania pracują, a ile czekają.

8
80%
20 ms
80 ms
300
Zalecany rozmiar puli
32
8 × 80% × (1 + 80/20)
Współbieżność wymagana przez twój cel
30
300 × 100 ms
Przepustowość, którą ta pula utrzyma
320/s
32 ÷ 100 ms
Przyjęte opóźnienie żądania
100 ms
20 + 80

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ę.

  1. czekanie ÷ praca = 80 ÷ 20 = 4
  2. wątki = 8 × 0,8 × (1 + 4) = 32
  3. opóźnienie = 20 + 80 = 100 ms
  4. prawo Little'a: 300 żądań/s × 0,1 s = 30 równoczesnych żądań
  5. 32 wątki ≥ 30, więc pula pokrywa cel
  6. 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.