...

Jak prawidłowo analizować statystyki NUMA w systemie Linux

Linux NUMA Statystyki pokazują mi, jak skutecznie procesy zarządzają pamięcią lokalną oraz gdzie zdalny dostęp powoduje wzrost opóźnień. Wyjaśniam, w jaki sposób celowo analizuję te dane, oceniam trendy w czasie i na tej podstawie opracowuję jasne kroki optymalizacyjne dla Wydajność derive.

Punkty centralne

  • Zrozumieć liczniki: numa_hit, numa_miss, numa_foreign, local_node, other_node, interleave_hit
  • Ocena kontekstu: profil obciążenia, topologia, typ obciążenia
  • Mierzenie trendów: Przed/po oraz w odstępach czasu
  • Sprawdzanie procesów: w całym systemie a w poszczególnych procesach
  • Zastosuj tuning: Affinity, Zasady, Umiejscowienie

Co naprawdę pokazują statystyki NUMA

Traktuję dane NUMA jako mapę dla Miejsce przechowywania oraz ścieżki danych. Wysoki numa_hit oznacza, że alokacje trafiły do żądanego węzła. Natomiast numa_miss sygnalizuje, że jądro musiało przenieść się na inny węzeł. Licznik numa_foreign wskazuje odpowiednik na węźle docelowym i uzupełnia obraz sytuacji. Dzięki local_node i other_node mogę stwierdzić, czy operacje dostępu pozostały lokalne, czy też wykorzystano pamięć zdalną.

Wartości te nie powinny być nigdy interpretowane w oderwaniu od kontekstu, ponieważ Obciążenia reagują bardzo różnie. Krótkie procesy powodują sporadyczne błędy, nie zmieniając jednak w zauważalny sposób ogólnej wydajności. Z kolei zasady interleave’u powodują celowo rozłożone alokacje, co powoduje wzrost wartości interleave_hit. Dlatego zawsze sprawdzam zamierzoną politykę i aktualne obciążenie. Dopiero wtedy decyduję, czy dana wartość wymaga podjęcia działań, czy też jest zgodna z założeniami projektu.

Dla mnie główna idea brzmi: Licznik dostarczają sygnałów, a nie ocen. Szukam wzorców w czasie, a nie pojedynczych wartości. W ten sposób rozpoznaję, czy zmiana w systemie powoduje przesunięcie lokalizacji. Dopiero na podstawie tego obrazu trendów oceniam, czy należy przenieść procesy, dostosować zasady lub ustawić przypisania procesora. Każda analiza NUMA rozpoczyna się zatem od jasno sformułowanego pytania i powtarzalnych punktów pomiarowych.

Interpretowanie liczników rdzeniowych w kontekście

Zawsze porównuję numa_hit i porównać ze sobą numa_miss, zamiast analizować wartości bezwzględne. Jeśli liczba błędów wzrasta, równolegle sprawdzam zmiany wartości numa_foreign na potencjalnym węźle docelowym. Jeśli oba wskaźniki są ze sobą zgodne, oznacza to rzeczywiste przeniesienie, a nie zwykły artefakt odczytu. local_node i other_node uzupełniają ten obraz o informacje dotyczące faktycznych dostępów. W ten sposób rozpoznaję, czy alokacja rozpoczęła się lokalnie, ale operacja później odczytywała dane z bardziej odległej pamięci.

Pojedynczy wysoki other_node Nie przeszkadza mi, gdy obciążenie jest celowo rozdzielane. Natomiast serwery WWW z dużą liczbą procesów roboczych zyskują na spójnej lokalizacji. Dlatego przyglądam się procesom, a nie tylko ogólnemu obrazowi sytuacji. Gdy tylko poszczególne usługi zaczynają się wyróżniać, analizuję ich rozmieszczenie. Dopiero gdy w całym systemie pojawiają się problemy, szukam przyczyn związanych z topologią lub obciążeniem.

Porównania w czasie: procedura pomiarowa, która się sprawdza

Odczytuję stany liczników na początku i na końcu Faza końcowa i obliczam różnicę. Pojedyncze wartości zacierają efekty, natomiast różnice wskazują na zmiany. Powtarzające się interwały, na przykład od 30 do 60 sekund, często wystarczają do rozpoznania trendów. Po wdrożeniach, aktualizacjach jądra lub zmianach sprzętowych ponownie porównuję te same interwały. Jeśli wówczas pojawi się więcej błędów lub wartość local_node ulegnie przesunięciu, oznacza to rzeczywistą zmianę.

Takie szeregi czasowe obejmują Błąd w rozmieszczeniu szybciej niż na podstawie pojedynczych migawek. Koreluję te linie z obciążeniem procesora, zmianami kontekstu i zajętością pamięci na węzeł. Dzięki temu widzę, czy wąskie gardła w pamięci RAM danego węzła prowadzą do działań zastępczych. Albo czy nowe procesy zaburzają równowagę w węzłach. Sam stosunek trafień do pomyłek przedstawiam zawsze jako wykres, a nie jako pojedynczą liczbę.

Sprawdź w całym systemie, a następnie przyjrzyj się bliżej procesom

Zacznę od ogólnego przeglądu sytuacji numastat i dopiero potem sprawdzam poszczególne procesy. Taka kolejność pozwala zaoszczędzić czas, ponieważ wiele efektów staje się widocznych globalnie. W widoku procesów korzystam z wyświetlania specyficznego dla danego procesu, aby wyodrębnić usługi, które rzucają się w oczy. Gdy tylko zidentyfikuję potencjalne przyczyny, dostosowuję Umieszczenie dotyczące obciążenia procesora i pamięci. Praktyczne wskazówki na ten temat zawarto w artykule poświęconym Afiniowość procesora i pamięci.

Zwłaszcza w przypadku usług Java, PHP-FPM lub baz danych często wystarczy czysta Affinity, aby znacznie zmniejszyć liczbę błędów. Orkiestracja kontenerów często maskuje te problemy, ponieważ harmonogramy rozdzielają zasoby bez uwzględnienia architektury NUMA. Dlatego sprawdzam przypisanie węzłów dla każdego poda lub maszyny wirtualnej. Gdy zestawy procesorów i przydział pamięci RAM są zgodne, wartość local_node wyraźnie wzrasta. Niektóre problemy rozwiązują się, gdy proces działa w pobliżu wymaganego zestawu danych.

Przegląd liczników NUMA (tabela)

Poniższą tabelę wypełniam podczas oceny nowego systemu, aby móc sprawdzić każdą Kluczowa liczba szybko ją klasyfikuję. Przedstawia ona znaczenie, typową interpretację i możliwe działania. Nie traktuję jej jako sztywnego schematu, lecz jako listę kontrolną. Kluczowe znaczenie ma porównanie z profilem obciążenia i topologią serwerów. Dopiero w tym kontekście podejmuję sensowną decyzję.

Licznik Znaczenie Interpretacja Podejście
numa_hit Alokacja w wybranym węźle Wysoka wartość jest pozytywna Utrzymanie pozycji
numa_miss Alokacja została przeniesiona na inne węzły Zwiększone ryzyko opóźnień Sprawdź zgodność/politykę
numa_foreign Alokacja obca w tym węźle Odpowiednik numa_miss Analiza węzłów docelowych
local_node Dostęp do pamięci lokalnej Im wyżej, tym taniej Proces bliżej pamięci RAM
other_node Dostęp do pamięci zdalnej To po prostu budzi obawy, choć nie jest zamierzone Sprawdź topologię/obciążenie
interleave_hit Wyniki dla rozkładu typu interleave Oczekiwane w przypadku polityki interleave Ocena równomierności

Dzięki temu Przegląd szybciej podejmuję decyzję, kiedy należy podjąć działania. Wzrost wartości numa_miss bez wyraźnej przyczyny powoduje rozpoczęcie analizy przyczyn. Jeśli wartości interleave_hit pozostają wysokie, sprawdzam, czy polityka jest aktywna zgodnie z zamierzeniami. Jeśli wartość other_node rośnie bez wzrostu obciążenia, sprawdzam, czy nie występują obciążenia wypierające. W ten sposób tabela staje się punktem wyjścia do podjęcia ukierunkowanych działań.

Zrozumienie i wykorzystanie topologii NUMA

Zanim zacznę tuningować, sprawdzam Topologia serwera: gniazda, rdzenie, kanały pamięci, ścieżki opóźnień. Jeśli proces działa na gnieździe 0, ale utrzymuje bloki danych na gnieździe 1, wydłuża się czas dostępu. Obniża to przepustowość i powoduje wahania czasów odpowiedzi. Usługi wymagające dużej ilości pamięci szczególnie odczuwają każdy niepotrzebny dystans. Dlatego procesy intensywnie wykorzystujące dane przypisuję do węzłów z wystarczającą ilością wolnej pamięci RAM.

Asymetryczne Połączenia wzmacniają te efekty, na przykład gdy węzeł wykorzystuje mniej kanałów. W takich przypadkach celowo przenoszę buforowane dane, zamiast rozdzielać proces. Konfiguruję hosty maszyn wirtualnych i kontenerów w taki sposób, aby każda instancja miała spójne przypisanie do węzła. W ten sposób ograniczam zdalny ruch danych bez konieczności stosowania limitów przepustowości. Ograniczenia wyznacza fizyka maszyny, a ja się ich trzymam.

Właściwe zrozumienie pojęć „interleave” i „balancing”

Zasady przeplatania celowo rozdzielają pamięć między węzłami, tak aby Przepustowość wzrasta na proces lub maleją punkty newralgiczne. W tej konfiguracji wysokie wartości interleave_hit są pożądane. Sprawdzam wtedy przede wszystkim równomierność, a nie bezwzględną lokalność. AutoNUMA lub równoważenie NUMA mogą pomóc, ale nie w każdej sytuacji.

W zależności od sytuacji decyduję, czy automatyczne Równoważenie pozostaje aktywna. W przypadku spójnych, długotrwałych usług raczej stosuję stałe powiązania. W przypadku zmiennych obciążeń AutoNUMA może reagować w odpowiedni sposób. Dobry przegląd korzyści i zagrożeń znajduje się w artykule Równoważenie NUMA. Dopiero gdy cel i ramy są jasno określone, wybieram odpowiedni tryb.

Wzorce obciążenia: bazy danych, maszyny wirtualne, usługi internetowe

Bazy danych są wrażliwe na Opóźnienie między procesorem a pamięcią RAM. Dlatego utrzymuję instancję, pamięć buforową oraz aktywne fragmenty na tym samym węźle. Maszyny wirtualne korzystają z jasno zdefiniowanych zestawów procesorów oraz pamięci RAM węzła, dzięki czemu systemy operacyjne gości widzą spójne ścieżki. Usługi internetowe z dużą liczbą pracowników działają najlepiej, gdy grupy pracowników pozostają przypisane do jednego węzła. W przypadku strategii pamięciowej stosuję, w zależności od sytuacji, ukierunkowane Zasady dotyczące pamięci NUMA.

Natomiast zadania analityczne i duże skanowania wykonuję częściowo rozdzielone . W tym przypadku interleave często zapewnia lepszą przepustowość niż ścisła lokalność. Ważne jest, aby obiektywnie przyjrzeć się wzorcom operacji wejścia/wyjścia (IO) danego obciążenia. Zapisy dominują w inny sposób niż odczyty, a dostępy losowe różnią się od sekwencyjnych. Wybieram strategię, która pasuje do wzorca dostępu, a nie tę, która brzmi dobrze w podręczniku.

Praktyczna procedura pomiarowa i narzędzia

Na początek wystarczy mi numastat oraz widok procesu. Rejestruję stany liczników wraz z datą, numerem PID i wskaźnikami obciążenia. Ważne jest, aby pomiary przeprowadzać w identycznych przedziałach czasowych. Pozwala to na dokładne odzwierciedlenie różnic przed i po zmianie. W okresach produkcyjnych odnotowuję te różnice i koreluję je z datami wydania aktualizacji.

W przypadku widocznych Usługi Dodatkowo sprawdzam obciążenie procesora i widok węzłów za pomocą narzędzi takich jak lscpu, numactl oraz widok perf dotyczący obciążenia zdalnego. W każdym cyklu pomiarowym dokumentuję wybraną politykę. Po wprowadzeniu zmiany ponownie wykonuję pomiary. Dopiero gdy linie trendu ustabilizują się, oceniam efekt jako udany. Ślepe przełączanie często prowadzi do pozornych popraw.

Jak uniknąć częstych błędów w interpretacji

Wysoki interleave_hit nie jest błędem, jeśli funkcja Interleave jest celowo aktywna. Podobnie pojedynczy błąd przy długim czasie działania jest nieistotny. Zawsze sprawdzam gęstość i rozkład w całym przedziale czasowym, a nie tylko wartości szczytowe. Niektórzy interpretują wartość „other_node” jako ogólnie negatywną i pomijają charakter obciążenia. Dlatego najpierw analizuję cel projektowy, a dopiero potem oceniam dane liczbowe.

Kolejny błąd: Widok ogólny No cóż, w sumie wszystko w porządku. Często wartości odstające występują tylko w niektórych PID-ach. Albo harmonogram kontenerów rozdziela pody między węzłami, mimo że sensowniejsze byłoby utworzenie lokalnej grupy. Takie zjawiska dostrzegam dopiero wtedy, gdy dokonuję pomiarów dla poszczególnych procesów. Bez takiej szczegółowości analiza pozostaje niekompletna.

Skuteczne zabiegi tuningowe

Zaczynam od Umieszczenie: Procesy na węzłach, na których znajdują się lub powinny znajdować się dane. Następnie ustawiam powinowactwo procesora (CPU affinity), aby wątki nie przeskakiwały między gniazdami. Kolejnym krokiem jest przypisanie pamięci, aby jądro przydzielało ją w żądanym miejscu. W przypadku zmiennych obciążeń sprawdzam zasady i, jeśli to odpowiednie, włączam AutoNUMA.

Następnie zadbam o Spójność W cyklu życia: ponowne uruchomienia, wdrożenia i skalowanie nie mogą przypadkowo zmieniać powiązań węzłów. Dokumentuję powiązania w kodzie, aby zachować ich powtarzalność. Następnie ponownie dokonuję pomiarów, oceniam różnice i podejmuję decyzję o dopracowaniu. Każda zmiana zasługuje na jasny dowód w postaci pomiarów.

Przykład z praktyki: Od porażki do sukcesu

Załóżmy, że pewna Baza danych pod obciążeniem wykazuje wyższą wartość numa_miss i rosnącą wartość other_node. Opóźnienia zapytań ulegają większym wahaniom. Najpierw sprawdzam przypisanie procesów i stwierdzam, że po wdrożeniu usługa działa na węźle A, ale pamięć podręczna została przydzielona do węzła B. Po stałym przypisaniu procesora i pamięci do węzła B stosunek ten ulega odwróceniu: wskaźnik numa_hit rośnie, a liczba nieudanych trafień maleje. Czasy odpowiedzi stają się bardziej stabilne, a obciążenie procesora nieznacznie spada, ponieważ eliminowane są dostępy zdalne.

Równolegle sprawdzam Polityka. Funkcja Interleave była przypadkowo włączona i rozdzielała przydziały pamięci. Po przełączeniu na preferowany węzeł pamięć podręczna pozostaje zamknięta lokalnie. Po godzinie pomiarów różnice potwierdzają poprawę. Dopiero wtedy uznaję to dostrojenie za sukces. Bez tej dodatkowej weryfikacji chwilowy odczyt mógłby wprowadzić w błąd.

Wskaźniki i wartości orientacyjne w praktyce

Przy podejmowaniu decyzji korzystam z niezawodnych Szanse zamiast pojedynczych wartości surowych. Dla każdego procesu obliczam wskaźnik alokacji local_alloc = numa_hit / (numa_hit + numa_miss). Dodatkowo analizuję Wskaźnik dostępności local_access = local_node / (local_node + other_node). Oba wskaźniki razem pokazują, czy pamięć pozostaje wykorzystywana lokalnie również po alokacji. Jako orientacyjne wartości przyjmuję: w przypadku usług, dla których opóźnienie ma kluczowe znaczenie, dążę do tego, by dostęp zdalny wynosił poniżej 5–10 %. W przypadku obciążeń analitycznych związanych z przepustowością dopuszczam 20–30 %, o ile wzrasta przepustowość. Decydujące znaczenie ma Stabilność w czasie. Wolę wartość, która pozostaje stabilna pod obciążeniem, niż krótki skok z idealnymi wynikami. Dokumentuję te przedziały docelowe dla każdego przeglądu, aby późniejsze pomiary można było jednoznacznie zaklasyfikować.

Cgroups, kontenery i pułapki związane z harmonogramowaniem

W środowiskach kontenerowych najpierw sprawdzam zestaw procesorów‑Przypisanie: zestawy procesorów i cpuset.mems muszą obejmować tę samą przestrzeń węzłów, w przeciwnym razie nieuchronnie pojawią się błędy. Dbam o to, aby pody ze stałymi żądaniami procesora nie były rozdzielane na kilka węzłów NUMA oraz aby harmonogram nie rozdzielał procesów roboczych tej samej aplikacji między różne węzły. W przypadku typów typu burst ograniczam maksymalną liczbę wątków na pod tak, aby mieściły się one w obrębie jednego węzła. Dokumentuję Domena NUMA na wdrożenie i wymagam spójnych replik (jedna grupa robotów na węzeł, a nie połówki grup rozłożone na dwa węzły). Jeśli zbyt wąsko obliczę wielkość pamięci na każdy pod, narzucę sobie niechcianą presję: niewielki zapas na każdy węzeł zapobiega sytuacji, w której jądro musi przedwcześnie przełączać się na inne węzły. Jeśli kontenery są często restartowane, zwracam uwagę na deterministyczne przypisanie, aby Zimny start nie dostać przez przypadek gorszej lokalizacji.

Spójne dostosowanie maszyn wirtualnych i vNUMA

W przypadku maszyn wirtualnych skupiam swoją uwagę na vNUMA: Topologia wirtualna musi odpowiadać topologii fizycznej. Rozdzielam procesory vCPU w taki sposób, aby każdy węzeł vNUMA znajdował się dokładnie na jednym fizycznym węźle NUMA. Po stronie hosta przypisuję wątki QEMU/hypervisora do tej domeny i upewniam się, że przydzielona pamięć RAM jest w całości udostępniana z tego węzła. Baloniarstwo A w przypadku maszyn wirtualnych, dla których opóźnienia mają kluczowe znaczenie, z rezerwą akceptuję overcommit; agresywne ballooning może wyprzeć zestawy „hot” z węzła i spowodować wzrost liczby dostępów zdalnych. W przypadku migracji na żywo po przeniesieniu ponownie weryfikuję przypisania – niektóre środowiska tracą przy tym precyzyjnie skonfigurowane przypisania procesora i pamięci. Dopiero gdy wyrównanie vNUMA jest prawidłowe, oceniam numastat w całym systemie: w przeciwnym razie naprawiam objawy, a nie przyczynę.

THP, Hugepages i migracja stron

Przejrzyste ogromne strony (THP) mogą zarówno pomagać, jak i przeszkadzać. Większe strony zmniejszają liczbę błędów TLB i poprawiają przepustowość, jednak jeśli jądro dopiero późno wdroży obsługę Hugepages zawalił się lub przeniesione, mogą pojawić się nieodpowiednie Odległości dalekie powstają. Trzymam się dwóch zasad: po pierwsze, Polityka wyraźnie zdefiniować (np. preferowany węzeł) i w miarę możliwości od samego początku przydzielać duże zasoby lokalnie. Po drugie, w przypadku obciążeń z ustalonymi, dużymi pamięciami podręcznymi, w miarę możliwości ustawiam statyczne strony typu hugepage (hugetlb), które wyraźnie rezerwuję na jednym węźle. Zmniejsza to fragmentację i ruchy kompensacyjne. Jeśli na wykresach czasowych zauważę, że po dłuższym czasie działania other_node– wzrasta udział, sprawdzam, czy Migracja stron czy następuje kompresja i czy ustawienia THP pasują do danego wzorca. Dla mnie ważne jest, aby nie wyłączać ani nie włączać tej funkcji globalnie – podejmuję decyzję dla każdej usługi z osobna i mierzę wpływ na lokalność oraz opóźnienie.

Jak prawidłowo interpretować wskaźniki „Reclaim”, „Swap” i „Memory‑Pressure”

Jeśli wyniki rosną bez widocznej zmiany pozycji, szukam ciśnienie w zbiorniku na węzeł. Pełne węzły zmuszają jądro do odzyskiwania pamięci i kompakcji, częściowo wywołanych przez kswapd na innym węźle – powoduje to skutki uboczne w licznikach. Sprawdzam dla każdego węzła ilość wolnej pamięci oraz obciążenie pamięci podręcznej stron. Włączone Zamiana może wypierać hot sety i spowodować gwałtowny wzrost opóźnień; w przypadku szczególnie wrażliwych usług wyłączam swap lub ściśle go ograniczam. W logach i widokach perf szukam szczytów odzyskiwania pamięci podczas szczytów obciążenia. Celem jest zapewnienie wystarczającej ilości wolnej, lokalne Utrzymywanie pamięci RAM w węźle docelowym, aby alokacje nie ulegały przesunięciu. Jeśli w tym celu konieczne jest zmniejszenie rozmiaru pamięci podręcznej, nadaję priorytet zestawowi roboczemu usługi względem ogólnej pamięci podręcznej stron.

Praktyczne polecenia i analiza wyników

W widoku procesu korzystam z numastat -p i dodaję: cat /proc//numa_maps, aby wyświetlić przydziały według obszaru (anonimowe, oparte na plikach) oraz według węzła. numactl --hardware podaje mi macierze opóźnień i rozmiary węzłów, lscpu --extended pokazuje przypisanie procesora do węzłów. W przypadku operacji dostępu do pamięci, w których nacisk kładzie się na ścieżki zdalne, ustawiam pamięć perf w celu weryfikacji wzorców obciążenia. Dane delta gromadzę w sposób powtarzalny, np.:

Procedura pomiaru

  • t0: numastat (ogółem) oraz numastat -p w celu utworzenia kopii zapasowej najlepszych PID-ów
  • 30–60 s pracy pod obciążeniem, identyczna faza obciążenia
  • t1: ponowne odczytanie numastatu, obliczenie wartości delta dla każdego licznika
  • Rejestrowanie wartości procesora równoległego, zmian kontekstu oraz pamięci węzła

Następnie obliczam Szanse i zaznaczam, które procesy znacznie odbiegają od trendów ogólnosystemowych. W przypadku utrzymującej się niepewności powtarzam pomiar co najmniej trzy razy. Jedynie spójne odchylenia uznaję za wiarygodne. W celu ciągłego monitorowania mapuję liczniki na szeregi czasowe i łączę je z metadanymi wersji – w ten sposób rozpoznaję Punkty regresji natychmiast.

Lista kontrolna dotycząca systematycznego dostrajania NUMA

  • Określenie celu: opóźnienie a przepustowość, obciążenie stałe a zmienne
  • Analiza topologii: węzły, opóźnienia, wolne zasoby pamięci RAM na węzeł
  • Pomiar wartości bazowych: numastat ogółem i w podziale na procesy, obliczenie wskaźników
  • Korekta przypisania: powinowactwo procesora, przypisanie pamięci, zasady
  • Konfiguracja kontenerów/maszyn wirtualnych: cpuset.cpus = węzeł, cpuset.mems odpowiednio; prawidłowe mapowanie vNUMA
  • Świadomy wybór THP/Hugepages, z uwzględnieniem fragmentacji
  • Zmniejszenie obciążenia pamięci: sprawdzić rezerwę pamięci dla każdego węzła oraz strategię wymiany
  • Pomiar kontrolny: porównanie wartości delta, zapewnienie stabilności w czasie
  • Dokumentacja: powiązania jako kod, informacje o wydaniu w kontekście NUMA

Krótkie podsumowanie

Analizuję dane NUMA poprzez Sygnały W tym kontekście warto zapoznać się z: parami liczników, przebiegami czasowymi i widokiem procesu. Kluczowe wartości, takie jak numa_hit, numa_miss, numa_foreign, local_node, other_node oraz interleave_hit, wskazują mi lokalność, ruchy zastępcze i strategie dystrybucji. Podejmuję decyzje w oparciu o topologię i obciążenie, a nie na podstawie sztywnych wartości granicznych. Optymalizacja zaczyna się od rozmieszczenia, powinowactwa, odpowiedniej polityki i przejrzystej procedury pomiarowej. W ten sposób zapewniam stałą Wydajność, ponieważ procesor i pamięć RAM są dostosowane do aplikacji, a operacje na odległych zasobach zdarzają się rzadko.

Artykuły bieżące