...

Optymalizacja powinowactwa procesów serwera i świadomość NUMA w hostingu

Zwiększam wydajność serwera poprzez Przynależność do procesu i świadomość NUMA w ukierunkowany sposób, a tym samym optymalnie organizować wątki, rdzenie i pamięć względem siebie. Pozwala mi to zmniejszyć opóźnienia, zwiększyć przepustowość i osiągnąć spójne czasy odpowiedzi w środowiskach hostingowych z wieloma aplikacjami.

Punkty centralne

Zanim wprowadzę jakiekolwiek konkretne ustawienia, wyjaśniam moje cele, wzorce obciążenia i istniejącą topologię sprzętową. Analizuję, które wątki wymagają szczególnie dużo pamięci i które procesy wymagają krótkich czasów reakcji. Rozważam, ile rdzeni jest dostępnych na węzeł NUMA i ile jest lokalnej pamięci RAM. Planuję łączyć usługi węzeł po węźle, tak aby Lokalizacja procesora jest utrzymywana. Mierzę każdą zmianę za pomocą testów porównawczych i monitorowania, aby uniknąć fałszywych założeń.

  • AffinityPowiązanie procesów z grupami podstawowymi
  • NUMAZachowaj pamięć lokalną
  • TopologiaSkalowanie węzeł po węźle
  • MonitoringUczyń zdalny dostęp widocznym
  • HostingKontrola rozmieszczenia hiperwizora

Co oznacza Process Affinity na serwerze?

Z Przynależność do procesu Określam, na których rdzeniach procesora działa proces lub wątek, zamiast pozwalać systemowi operacyjnemu swobodnie decydować. Dzięki temu zawartość pamięci podręcznej jest spójna, co zmniejsza liczbę pominięć pamięci podręcznej i przełączeń kontekstu. Przypinam wątki tak, aby efektywnie wykorzystywały swoje pamięci podręczne L1/L2/L3 i nie przeskakiwały między rdzeniami. Poprawia to przewidywalność opóźnień przy dużym obciążeniu i zapewnia równomierne wykorzystanie zarezerwowanych rdzeni. Praktycznym wprowadzeniem jest ten przewodnik po Zasobność procesora w hostingu, ponieważ używam go do porównywania typowych wariantów przypinania.

Zrozumienie NUMA: dostęp lokalny i zdalny

NUMA dzieli pamięć roboczą na węzły, z których każdy jest ściśle powiązany z określonymi gniazdami procesora. Wątek uzyskuje dostęp do lokalnej pamięci RAM szybciej niż do pamięci zdalnej na innych węzłach. Ta asymetria ma znaczący wpływ na rzeczywiste obciążenia, zwłaszcza w przypadku wielu rdzeni i dużej ilości pamięci RAM. Dlatego przypisuję wątki i ich dostęp do pamięci do wspólnego węzła, aby zmniejszyć opóźnienia i zwiększyć przepustowość. Jeśli chcesz zagłębić się w topologię, zapoznaj się z praktycznymi wskazówkami na stronie Węzły NUMA w serwerze a następnie mierzy efekty w codziennym życiu.

Świadomość NUMA w systemie operacyjnym i aplikacji

Aktywuję Świadomość NUMA w systemie operacyjnym, hiperwizorze i aplikacji, aby pamięć była przydzielana lokalnie. Tam, gdzie to możliwe, utrzymuję wątki instancji na rdzeniach tego samego węzła NUMA, zamiast dystrybuować je między węzłami. Wolę tworzyć duże sterty lub bufory w lokalnej pamięci RAM, aby drogie zdalne dostępy pozostały rzadkie. Jeśli aplikacja ma kilku pracowników, układam ich węzeł po węźle w pule, aby uniknąć zakłóceń. Tworzy to wyraźną alokację procesora i pamięci, co zauważalnie skraca czas odpowiedzi.

Interakcja między Affinity i NUMA

Affinity bez planowania NUMA marnuje potencjał, jeśli pamięć znajduje się na odległych węzłach. Podobnie, obserwacja NUMA jest mało przydatna, jeśli planowanie często przenosi wątki. W związku z tym wiążę wątki z rdzeniami określonego węzła i zapewniam lokalną alokację pamięci równolegle. Jeśli skaluję aplikację, najpierw zapełniam węzeł przed włączeniem kolejnych węzłów. Takie sprzężenie polityki przypinania rdzeni i pamięci generuje stałe profile opóźnień pod obciążeniem.

Strojenie sprzętu i oprogramowania układowego (UEFI/BIOS)

Aby Affinity i NUMA działały, ustawiłem bazę w oprogramowaniu układowym na stabilną. Preferuję stałe tryby wydajności zamiast agresywnych opcji oszczędzania energii, aby zminimalizować wahania zegara i opóźnień. Ważne punkty, które sprawdzam:

  • Profil wydajności: maksymalna moc/wydajność zamiast zrównoważonej; ograniczenie niskich stanów C, jeśli opóźnienia są bardziej krytyczne niż wydajność.
  • Strategia Turbo/boost: Deterministyczny boost na żądanie w celu uniknięcia wahań liczby rdzeni P.
  • SMT/Hyper-Threading: Testowanie w zależności od obciążenia - w przypadku trudnych umów SLA dotyczących opóźnień często przypinam krytyczne wątki do fizycznych rdzeni i oddzielam rodzeństwo SMT.
  • Przeplatanie pamięci: Wyłączone dla optymalizacji NUMA, aby węzły pozostały wyraźnie oddzielone.
  • Kanały pamięci: Symetryczna konfiguracja gniazd DIMM na węzeł dla maksymalnej przepustowości.

Ścieżka konfiguracji: Analiza do przypięcia

Zaczynam od nagrania topologii, zazwyczaj od lscpu, numactl -hardware lub hwloc. Następnie definiuję wymaganą liczbę rdzeni dla każdej usługi i przypisuję je do węzła. Wdrażam przypinanie za pomocą zestawu zadań lub opcji systemd, aby przypisanie pozostało powtarzalne. Podczas testu dostosowuję rozmiar grup rdzeni, aż opóźnienie i przepustowość są w dobrym stosunku. Upewniam się, że żadne usługi intensywnie korzystające z procesora nie współdzielą tej samej puli rdzeni, a tym samym nie wypierają nawzajem swoich pamięci podręcznych.

W Linuksie lubię deklaratywnie ustawiać powinowactwo i politykę pamięci poprzez cgroups (v2): Definiuję cpuset.cpus i cpuset.mems w zależności od węzła i uruchamiam usługi z parametrami systemd, takimi jak CPUAffinity= i NUMAMask=. Utrzymuję oddzielne pule dla procesów wsadowych lub drugorzędnych, aby nie dostały się one do rdzeni warstwy krytycznej pod względem opóźnień. W przypadku powtarzających się zadań planuję dokładne okna startowe, w których rdzenie są wolne.

Powinowactwo przerwań i wejść/wyjść

Nie tylko wątki aplikacji wymagają lokalności - również Przerwania i ścieżki I/O, które organizuję w pobliżu węzła:

  • Sieć: Powiąż kolejki RX/TX karty sieciowej z rdzeniami tego samego węzła NUMA (skonfiguruj RSS/XPS), aby przetwarzanie pakietów i wątki aplikacji współdzieliły pamięć podręczną i lokalność pamięci RAM.
  • Pamięć masowa: przypnij kolejki NVMe i wątki IO na węzeł; sprawdź rozkład kolejek dla blk-mq, aby gorące woluminy nie krzyżowały się z węzłami.
  • irqbalance: Skonfiguruj specjalnie lub wyłącz dla krytycznych kolejek i ustaw ręcznie za pomocą smp_affinity.

Ukierunkowane wykorzystanie funkcji systemu operacyjnego

Celowo używam funkcji jądra dla ścisłych profili opóźnień:

  • isolcpus/nohz_full/rcu_nocbs: oddziela rdzenie od ogólnego harmonogramu, minimalizuje obciążenie tickiem i przenosi wywołania zwrotne RCU - idealne dla wątków o wysokiej wydajności.
  • Zasady harmonogramu: Używaj SCHED_FIFO/RR oszczędnie dla komponentów czasu rzeczywistego; w przeciwnym razie używaj CFS z bliskim powinowactwem.
  • Automatyczne równoważenie NUMA: często wyłączane w przypadku obciążeń ściśle przypiętych, aby jądro nie przesuwało pamięci.
  • Transparent Huge Pages: Przeważnie ustawione na madvise i używające jawnych Huge Pages dla naprawdę dużych stert, aby zmniejszyć liczbę pominięć TLB.

Polityka pamięci masowej uwzględniająca NUMA

Z numactl Wymuszam preferowaną alokację pamięci lokalnej lub korzystam z zasad, takich jak preferowane i interleave. Tam, gdzie to możliwe, utrzymuję duże struktury w pamięci, takie jak pule buforów baz danych w obrębie węzła. Jeśli zapotrzebowanie na pamięć wzrasta, obserwuję wzrost zdalnych dostępów i reaguję poprzez segmentację lub sharding. Praktyczne spostrzeżenia na temat strojenia dostarczają mi wskazówek dotyczących Równoważenie NUMA, co następnie potwierdzam testami obciążeniowymi. Dzięki temu czas dostępu do pamięci jest niski i przewidywalny.

Techniki przechowywania danych: Ogromne strony, sterty i zbieranie śmieci

Zarządzanie pamięcią często determinuje opóźnienia P99. Używam ogromnych stron, gdzie dominują duże, długo żyjące sterty (np. bufory DB, sterty JVM). Zmniejsza to liczbę pominięć TLB i spacerów po stronach. W przypadku obciążeń JVM zwracam uwagę na rozmiar sterty na węzeł i aktywuję optymalizację NUMA, aby wątki GC i sterty pozostały lokalne. W przypadku .NET i Go planuję GC i pule goroutine, aby nie wypełniały rdzeni między węzłami w niekontrolowany sposób. W bazach danych dzielę duże pule buforów na segmenty lokalne dla węzłów lub uruchamiam wiele mniejszych instancji na węzeł.

Praktyczny hosting: typowe obciążenia

Bazy danych, pamięci podręczne i duże serwery aplikacji reagują wrażliwie na Lokalizacja procesora i opóźnienia pamięci. Rozproszona maszyna wirtualna na kilku węzłach NUMA zwiększa ścieżki obliczeniowe i pamięciowe oraz spowalnia zapytania lub wywołania API. Dlatego umieszczam maszyny wirtualne tak, aby ich jednostki vCPU były przypisane do fizycznego węzła, a pamięć pozostawała tam. Pule kontenerów otrzymują spójne zestawy CPU, dzięki czemu pracownicy nie przeskakują między węzłami. Ta ostrożność opłaca się szczególnie w przypadku handlu elektronicznego i usług API o wysokiej równoległości.

Drobnoziarniste strategie aplikacji

Na poziomie aplikacji odłączam węzły, aby zachować lokalność:

  • Pule robocze: Jedna pula na węzeł NUMA, każda z lokalną kolejką w celu uniknięcia komunikacji między węzłami.
  • Sharding: Utrzymuj dane i sesje w węzłach lokalnych; wybierz hashowanie, aby gorące fragmenty nie przekraczały wielu węzłów.
  • Pamięci podręczne: Replikowane zamiast scentralizowanych; czytelnicy wolą kopie lokalne w węzłach.
  • Przypinanie wątków w środowiskach uruchomieniowych: W przypadku stosów sieciowych (np. Netty) i klientów DB, wiąż pracowników ze stałymi rdzeniami, obserwuj bliskość IRQ.

Monitorowanie i rozwiązywanie problemów

Rozsądne monitorowanie pokazuje więcej niż ogólne wykorzystanie mocy, ponieważ NUMA-Efekty są ukryte w wartościach szczegółowych węzła. Monitoruję obciążenie procesora na rdzeń i węzeł, wykorzystanie pamięci na węzeł i szybkość zdalnego dostępu. Jeśli poszczególne rdzenie są przepełnione, podczas gdy inne pozostają nieużywane, oznacza to słabą konfigurację powinowactwa. Jeśli pamięć RAM jednego węzła jest pełna, podczas gdy inny ma rezerwę, muszę dostosować politykę pamięci lub rozmieszczenie. Używam tych sygnałów do obiektywnego dokumentowania wąskich gardeł i wyprowadzania kolejnych zmian.

Metryki Uwaga/Objaw Typowa przyczyna Szybkie działanie
Procesor na rdzeń Niektóre rdzenie są stale wysokie Nieprawidłowe przypięcie Redystrybucja grup podstawowych
Pamięć RAM na węzeł Węzeł w limicie Pamięć nie lokalna set numactl preferred
Stawka zdalna Wysoki dostęp zdalny Maszyna wirtualna/kontener za pośrednictwem węzłów Zestaw vCPU/CPU w pakiecie
Przełączniki kontekstowe Nieregularne opóźnienia Wędrówka wątku Pin Affinity twardszy

Anty-wzorce i typowe przeszkody

Unikam globalnych limitów CPU niezależnie od NUMA, ponieważ alokują one rdzenie między węzłami. Również „jedna duża maszyna wirtualna“ ze zbyt dużą liczbą vCPU rzadko skaluje się liniowo - wiele instancji lokalnych węzłów jest lepszych. Przezroczyste ogromne strony w trybie always czasami powodują szczyty błędów stron; madvise plus ukierunkowane ogromne strony są bardziej przewidywalne. irqbalance działający w sposób niekontrolowany osłabia lokalność we / wy. I: Zbyt mocne przypinanie bez rdzeni buforujących może utrudnić konserwację i obciążenie boczne - zawsze planuję kilka wolnych rdzeni na węzeł.

Mierzalność efektów wydajności

Mierzę efekty Affinity i NUMA zmieniają się zawsze z powtarzalnymi benchmarkami. Porównania "przed" i "po" z identycznym zestawem danych w przejrzysty sposób pokazują ulepszenia. Łączę testy syntetyczne z realistycznymi profilami obciążenia, aby optymalizacje przynosiły efekty w codziennym użytkowaniu. Kluczowe wyniki, takie jak opóźnienia P95 i P99, są często bardziej znaczące niż wartości średnie. Pozwala mi to weryfikować decyzje i rozpoznawać efekty uboczne na wczesnym etapie.

Wirtualizacja i kontenery

W konfiguracjach hiperwizora używam vNUMA, aby maszyna wirtualna-gość rozumiała fizyczną topologię. Pakuję vCPU maszyny wirtualnej do fizycznie pasującego węzła, aby zminimalizować zdalny dostęp. W przypadku kontenerów definiuję żądania i limity procesora, aby zestawy procesorów pozostały spójne, a menedżer topologii przestrzegał lokalizacji węzła. Duże maszyny wirtualne z wieloma jednostkami vCPU rozmieszczam w węzłach tylko wtedy, gdy aplikacja pozwala na wewnętrzną segmentację. Każde rozmieszczenie oceniam na podstawie opóźnień, przepustowości i wykorzystania na węzeł.

Orkiestracja: Cgroups, Kubernetes i inne.

W kontenerach polegam na klasach gwarantowanych lub burstable ze stabilnymi zestawami CPU i przydziałem mems. Menedżer topologii w trybie „single-numa-node“ pomaga utrzymać lokalność węzłów. W przypadku długo działających części czasu rzeczywistego używam menedżera CPU w trybie „statycznym“, aby zachować wyłączność rdzeni. Planuję HugePages jako żądania/limity i grupuję pody według roli obciążenia, aby węzły nie były heterogenicznie przeciążone. Ważne: należy prawidłowo utrzymywać etykiety węzłów, aby reguły umieszczania nie naruszały lokalności w sposób niezamierzony.

Rola dostawcy usług hostingowych

Dobry dostawca zapewnia przejrzystość Topologia NUMA, opcje powinowactwa i wgląd w metryki węzłów. Upewniam się, że hiperwizor i orkiestracja poważnie traktują świadomość NUMA i że rozmieszczenie vCPU pozostaje pod kontrolą. Ważne jest również monitorowanie, które zapewnia limity procesora, pamięci RAM i zdalne na węzeł. Pozwala mi to samodzielnie decydować o tym, jak rygorystycznie przypinam i jak ustawiam zasady dotyczące pamięci. Taka kontrola sprawia, że wymagające obciążenia są niezawodne i przewidywalne.

Model operacyjny: bezpieczne wprowadzanie zmian

Zasady pinowania i NUMA wprowadzam iteracyjnie: najpierw na węźle, z jasno określonymi krokami wycofywania. Dokumentuję topologię, przypisania i parametry jądra, aby zapewnić powtarzalność. W przypadku wydań używam ruchu kanaryjskiego, monitoruję P95/P99, przełączniki kontekstowe i stawki zdalne przez co najmniej jedną fazę pełnego obciążenia, a dopiero potem wdrażam je szerzej. Dzięki temu ulepszenia są stabilne, a ryzyko możliwe do kontrolowania.

Najlepsze praktyki, kompaktowo stosowane

Każdą optymalizację rozpoczynam od dokładnego Analiza topologii i udokumentować przypisanie rdzenia i węzła. Następnie dzielę obciążenia tak, aby baza danych, pamięć podręczna i serwer aplikacji otrzymały oddzielne zasoby węzła. Przypinam krytyczne procesy i nadaję priorytet pamięci lokalnej przed dostrojeniem rozmiaru grupy. Każdemu dostrojeniu towarzyszą testy porównawcze i metryki węzłów, aby wyraźnie zobaczyć efekty. W przypadku wzrostu planuję węzeł po węźle i utrzymuję szczupłe instancje zamiast wysadzać monolityczną gigantyczną instancję.

Podsumowanie i kolejne kroki

Z ukierunkowanymi Przynależność do procesu i prawdziwą świadomość NUMA, znacznie przyspieszam obciążenia na tym samym sprzęcie. Wyraźne rozmieszczenie, lokalna alokacja pamięci i spójne pomiary wyników mają kluczowe znaczenie. Łączenie maszyn wirtualnych i kontenerów blisko węzła zmniejsza opóźnienia i zwiększa przepustowość. Zalecam rozpoczęcie projektu pilotażowego na hoście, przetestowanie powinowactwa i polityki pamięci oraz przyjęcie najlepszych ustawień. W ten sposób wydajność rośnie krok po kroku bez konieczności zakupu nowych serwerów.

Artykuły bieżące