...

Server NUMA Locality och CPU-Memory Affinity för maximal hostingprestanda

Server NUMA Lokalitet och CPU-minnesaffinitet avgör hur nära trådar arbetar med sitt RAM-minne och hur konstanta latenserna förblir i värdstackar. Jag kommer att visa dig på ett praktiskt sätt hur du kan uppnå mätbart mer genomströmning med topologiigenkänning, affinitetsstrategier och I/O-vägar nära noden och Fördröjning märkbart lägre.

Centrala punkter

För snabb orientering sammanfattar jag de viktigaste budskapen innan jag förklarar stegen i detalj och backar upp dem med exempel, så att du omedelbart kan se var du ska börja för att Lokalitet och Affinity på ett lönsamt sätt. Jag betonar tydliga relationer mellan trådar, minne och I/O så att du kan härleda prioriteringar på ett rent sätt och Beslut uppfylla. Jag identifierar också scenarier där Interleave är meningsfullt utan att späda ut dina kritiska vägar och visar hur du kan visa verkliga framsteg via övervakning och Fel undviks. För virtualiserade miljöer ger jag tips om placering av vCPU:er och vRAM så att gästsystem inte glider över flera noder och Fjärrkontroll-tillgångarna exploderar. Slutligen kommer jag att översätta resultaten till en kort färdplan så att du kan gå vidare på ett strukturerat sätt och ta varje steg i rätt riktning. mätbar säker.

  • Lokalitet först: håll trådar nära ditt eget RAM, undvik fjärrkontroll.
  • Affinitet fixa: Bind ihop kärnor och minne enligt policy.
  • Topologi läsa: Noder, kärnor, PCIe-enheter per sockel.
  • I/O-vägar paket: Koppla ihop NIC, NVMe och app i samma nod.
  • mässor istället för att gissa: P95/ P99, fjärråtkomst och genomströmningsspårning.

Förståelse för NUMA-topologin

Innan jag flyttar arbetsbelastningar läser jag Topologi av servern: Hur många NUMA-noder finns det, hur många kärnor och hur mycket RAM är anslutna till varje nod. Jag tittar också på vilka PCIe-enheter - t.ex. NIC eller NVMe SSD - som är anslutna till vilken socket, eftersom detta avgör avbrottsvägar och minnesåtkomst, och hur mycket RAM som är anslutet till varje nod. Fördröjning karakteriserad. En nod ger lokal minnesåtkomst med kort avstånd; allt därutöver kostar tid och bandbredd. Ju större maskinen är med flera socklar, desto mer påverkar fjärråtkomst svarstiderna och slukar bandbredd. Genomströmning. För en förståelig introduktion till hårdvarulogik tycker jag att en kompakt NUMA-noder i en överblick, att medvetet överväga nodgränser och undvika felaktiga fördelningar.

I praktiken börjar jag med en kort topologiinventering och dokumenterar den så att jag senare kan härleda affinitetsbeslut på ett begripligt sätt. Användbara kommandon:

#-kärnor och NUMA-tilldelning
lscpu -e=CPU,Kärna,Sockel,Nod

Översikt över # NUMA-hårdvara
numactl --hårdvara

# Tilldela PCIe-enheter till deras NUMA-nod
lspci -nn | grep -E "Ethernet|Non-Volatile"
for d in /sys/bus/pci/devices/*; do echo -n "$d: "; cat $d/numa_node; done

Det viktiga är att du PCIe rotkomplex och enhetsplatser till socklarna. Två portar i samma NIC kan tilldelas olika noder; detta påverkar var RX/TX-köer och IRQ:er hamnar bäst. Samma sak gäller för NVMe: moderna styrenheter har flera köer som du bör binda till kärnor nära noden så att DMA inte utlöser några nodhopp.

Använda CPU-minnesaffinitet korrekt

Med CPU-Memory Affinity kopplar jag processer till kärnområden och genomdriver lokal minnesallokering så långt det är möjligt så att Trådar inte ständigt når över kanten på noden. I Linux definierar jag CPU:er via systemd eller cgroups och kombinerar detta med minnespolicyer så att RAM företrädesvis skapas på samma nod och Fjärrkontroll förblir minimerad. Kritiska tjänster - API-frontends, minnescacher, databaser - gynnas omedelbart eftersom minneskontrollerns väntetider minskar och cacheträffarna blir mer frekventa. För hårda pinning-gränser kan dock begränsa schemaläggningen, så jag backar upp varje justering med benchmarks och övervakar P95/P99-värden för märkbara effekter på Användare-upplevelse. En kompakt introduktion till Affinity in hosting hjälper dig att komma igång: Affinitet och NUMA-medvetenhet tillhandahålla de nödvändiga verktygen för ren placering.

Den avgörande faktorn är Principen om första kontaktenMinne skapas på den nod som skriver till sidan först. Initialisera därför stora heaps eller buffertar på målkärnorna i den nod där tjänsten senare kommer att köras - helst med CPU- och minnespolicyn redan inställd (t.ex. via systemd unit eller numactl). Om du startar cold på nod 0 och sedan flyttar trådarna till nod 1 förblir majoriteten av sidorna avlägsna. För högar med stora runtimes är det värt att använda „pre-touch“ under bootstrap så att sidorna roterar lokalt och sedan förblir varma.

NUMA-medvetenhet i värdstacken

Ett NUMA-medvetet operativsystem, en lämplig hypervisor och applikationer med trådpinnning utvecklar sin fulla potential tillsammans. Potentiell. Operativsystemet gynnar lokal placering om det finns lediga resurser i noden, medan hypervisorn allokerar virtuella datorer på ett sådant sätt att vCPU:er och vRAM inte glider isär och Lokalitet upprätthålls. I applikationen separerar jag arbetspooler per nod och håller köerna lokala istället för att driva globala pooler kors och tvärs. Jag organiserar databasprocesser, cache-demons och webbserverinstanser nod för nod så att hotpaths förblir korta och Jitter minskar. Detta ökar konsekvensen och förutsägbarheten under belastning, vilket direkt påverkar förutsägbarheten för SLA:er i euro och sparar dyr överprovisionering.

På Ingress-nivå tar jag hand om Affinitet till noder av sessionerna, till exempel genom sticky routing eller konsekvent hashing (t.ex. på klient-IP eller sessionstoken), så att förfrågningar hamnar tillbaka hos „deras“ nodlokala arbetare och cache. För statliga tjänster planerar jag repliker per nod och balanserar läsåtkomst lokalt; jag utjämnar skrivvägar via asynkron replikering eller batchning för att undvika ping-pong mellan noderna.

Schemalägga tjänster nod för nod

Jag grupperar lagren i en stack på ett sådant sätt att varje lager har en tydlig nodreferens och Stigar håll dig kort. En klassisk separation: webb/API per nod, app-arbetare bredvid, plus den lokala cachen; databasen sitter också nära noden om RAM-fotavtrycket passar in och IO-vägen är inte avbruten. Jag flyttar rapporteringsjobb, säkerhetskopior eller batcharbetare till mindre kritiska noder så att interaktiva förfrågningar inte påverkas. Jag undviker stora monolitinstanser eftersom de ofta korsar nodgränser och därför genererar fjärrbelastning, vilket Prestanda suddig. Mindre, replikerade instanser per nod ger ofta bättre genomströmning i vardagen, eftersom de respekterar NUMA-reglerna och jämnar ut toppar.

För kapacitetsplanering beräknar jag Headroom separat för varje nod: CPU-buffert för bursts, RAM-buffert mot OOM och separata marginaler för sidcache. På så sätt förhindrar jag att kärnan oavsiktligt växlar på distans. Jag definierar tydliga växlingsvägar för failover: om en nod misslyckas kan ersättningsinstanser köra cross-node, men jag begränsar deras samtidighet tills den ursprungliga noden återställs - detta håller den totala latensen stabil.

Inställning av CPU-affinitet: Metoder och fallgropar

För kärnallokering använder jag systemd med CPUAffinity eller cgroups med cpuset.cpus, så att tjänsterna har fasta Centrala områden få. Vid pinning är jag uppmärksam på hyper-threading-par, eftersom två logiska trådar i en fysisk enhet delar resurser och kan sakta ner varandra om jag kombinerar dem olyckligt, och Tips skapa. Latensvägar - TLS-terminering, API-ingång, cache-läsare - får exklusiva kärnor, medan loggar, komprimering eller säkerhetskopiering flyttas till andra pooler. Pooler som är för smala utan buffertar orsakar köer, så jag räknar med utrymme och kontrollerar kontextbyten, kötid och IRQ-distribution. Från observationen drar jag slutsatsen om jag ska öppna kärnorna bredare eller koncentrera dem ytterligare tills latensfördelningen faller av rent och P99-topparna blir tystare.

För ytterligare jitterreducering ställer jag in kernel-switchar som nohz_full och rcu_nocbs för kärnor med exklusiv latens, isolera dem från systemtjänster och placera avsiktligt endast IRQ:er på processorer som är avsedda för detta ändamål. Jag använder tjänsten „irqbalance“ med försiktighet: konfigurera den antingen specifikt eller avaktivera den om den motverkar din manuella IRQ-affinitet. Jag använder SCHED_FIFO/SCHED_RR sparsamt och endast med Be-gränser för att undvika prioritetsinversion eller svält.

Minnespolicyer och NUMA-masker

När det gäller minnespolicy skiljer jag mellan föredragen lokal allokering, interleave över flera noder och fasta NUMA-masker via cpuset.mems, så att RAM flöden till där trådarna faktiskt körs. För interaktiva tjänster brukar jag ställa in „preferred“, vilket innebär att systemet allokerar lokalt och bara avviker när det finns en brist, vilket är Fjärrkontroll-åtkomst är begränsad. Analys- eller streamingjobb kan ibland dra nytta av interleave eftersom bandbredden fördelas över noderna och trycket på en styrenhet minskar. Fasta masker ger kontroll, men kräver disciplin i kapacitetsplaneringen så att inga oönskade OOM-händelser i en nod går upp och ner. Tjänster störa. Följande tabell kategoriserar vanliga policyer i typiska scenarier och hjälper dig att fatta ett snabbt beslut.

Policy Effekt Typiska arbetsbelastningar Risk
Företrädesvis (lokal) RAM i första hand i den lokala noden, reservalternativ vid knapphet Webb/ API, cacher, OLTP-databaser Liten drift vid full belastning på andra noder
Interleave Jämn fördelning över utvalda noder Streaming, analys, stora skanningar Högre latenstid för enskilda åtkomster
Fast NUMA-mask Strikt bindning till definierade minnesnoder Strikt inkapslade tjänster, deterministiska tester Risk för OOM om budgeten är felaktigt planerad

Håll ett öga på systemomfattande switchar: zon_återvinning_läge påverkar om en nod aggressivt rensar upp sitt eget minne innan den allokerar på distans - ofta oönskat för latensvägar. Transparenta stora sidor (THP) kan utlösa sidmigrering eller generera stall; för latens-känsliga tjänster väljer jag vanligtvis „madvise“ och använder statiska hugepages där det är vettigt, så att TLB-träffar ökar och sidfelstoppar minskar.

Binda nätverks- och I/O-vägar nära noden

Jag anpassar NIC-köerna (RX/TX) så att deras IRQ:er pekar på kärnor i rätt nod och paketbehandlingen sker där App beräknar. Samma sak gäller för NVMe SSD-enheter eller RAID-styrenheter: I/O-trådar bör köras på den nod som enheten är ansluten till via PCIe, så att DMA-vägarna förblir korta och enheten kan användas mer effektivt. Flaskhalsar inte materialiseras. Under Linux justerar jag IRQ-affinitetsmasker och länkar dem till CPU-pooler för mina tjänster för att skapa en kontinuerlig väg. Med mikroutbrott från nätverket, till exempel många TLS-handskakningar, lönar sig denna närhet direkt eftersom kopieringsvägarna är kortare och CPU-cacherna förblir varma och Sammanhang mindre ofta. Detta resulterar i ett konsekvent dataflöde från paketet till applikationen till minnet, utan onödiga nodhopp.

Konkreta hävstänger i nätverksstacken: RSS för hårdvarudistribution till köer, RPS/RFS för mjukvarubaserad CPU-styrning och XPS för TX-val. Jag använder ethtool för att tilldela RX-köer till kärngrupper som körs i samma nod som dina arbetare. För lagring använder jag blk-mq-justering och kömappning per nod; NVMe-styrenheter erbjuder flera köer för inlämning/avslut, som jag skalar och affinitiserar ≤ antal kärnor per nod. Kontrollera regelbundet om avbrott (cat /proc/interrupts) avfyras där dina appkärnor finns - du kan känna igen drift genom att öka fjärrbyte trots en stabil belastning.

Strukturera applikationsarkitekturen i linje med NUMA

På appnivå skapar jag mina egna arbetspooler för varje NUMA-nod, håller köerna lokala och undviker globala låshotspots så att Trådar inte hoppa fram och tillbaka. Jag konfigurerade session och data sharding så att heta partitioner förblir där de begärande arbetarna körs och Tid inte går förlorad i trafiken mellan noderna. För cacher använder jag ofta repliker i stället för en central instans så att läsarna träffar nodlokala kopior. I Netty-, Tokio-, libuv- eller DB-klienter kopplar jag händelseslingor till fasta kärnor och uppmärksammar IRQ-närhet så att uppgiftsändringar förblir begränsade och Cacher träffa bättre. Denna layout minskar ping-pong-effekter och gör svarstiderna jämnare under dagen.

En underskattad hävstång är Allokator och körtidsalternativ: NUMA-aktiverade allokatorer (jemalloc/tcmalloc) minskar konflikter mellan trådar och håller sidorna närmare trådarnas hemkärnor. I JVM-stackar hjälper alternativ som NUMA-medvetenhet och pre-touch till med deterministiska felfaser; i .NET anpassar jag GC-trådar nära noder och uppmärksammar server-GC för att jämna ut stopptider. I Go dimensionerar jag GOMAXPROCS per nodpool och håller goroutine-schemaläggare borta från latenta kärnor som arbetar nära IRQ.

Förnuftig kontroll av NUMA:s autobalansering

Automatiska NUMA-balanseringsmekanismer i kärnan kan hjälpa till att jämna ut distribuerad belastning, men jag kontrollerar alltid om de kan stödja min Affinitet undermineras. I latenskritiska tjänster inaktiverar eller stryper jag automatisk flyttning om den drar ut trådar ur deras lokala minne och Tips genereras. För analysjobb eller bred batchbearbetning brukar jag låta balanseringen vara aktiverad eftersom den kan öka bandbredden utan att försämra interaktionen. En praktisk introduktion till balanseringsstrategier ger mig ytterligare utgångspunkter: Förstå NUMA-balansering visar när det automatiska systemet ska användas och när det ska tilldelas manuellt. I slutändan fattar jag ett databaserat beslut för varje serviceklass i stället för att blint anta en global standardinställning och Mål att missa.

När balanseringen är aktiverad övervakar jag migreringshastigheter, mindre och större feltoppar och CPU-användning per nod. Om sidor flyttas fram och tillbaka cykliskt motverkar jag detta med tätare pinning, pre-touch och smalare minnesmasker. I arbetsbelastningar med långa, sekventiella skanningar kan balansering dock harmonisera belastningen så länge inga interaktiva latensvägar påverkas.

Övervakning: mäta, jämföra, besluta

Utan mätning förblir tuning en gissningslek, så jag följer CPU-belastning per kärna och per nod, minnesanvändning per nod och andelen Fjärrkontroll-accesser. För användarupplevelsen räknas P95/P99-latenstider mycket mer än medelvärden, eftersom extremvärden karaktäriserar SLA-intrycket och Kostnader uppåt. Jag kör realistiska belastningsprofiler med kalla och varma cacheminnen eftersom de båda världarna visar olika flaskhalsar. Efter varje ändring dokumenterar jag inställningarna, testdatum och resultat så att jag säkert kan återkalla ändringar senare och Kunskap inte går förlorad. Om du också korrelerar appmätvärden - kölängder, omförsök, skräpuppsamling - med systemvärden kan du snabbare identifiera orsak och verkan.

Praktisk hjälp i analysen:

  • numastat (system- och processrelaterad) för lokal vs. Fjärrkontroll-Hit
  • /proc/interrupts och SoftIRQ-tid av CPU för IRQ-drift
  • perf-händelser och schemaläggningsstatistik för körködjup, kontextbyten, LLC-missar etc.
  • fio/iperf/wrk med nodspecifika arbetspooler för reproducerbara jämförelser

Utvärderingen görs per nod: Jag förväntar mig att latenshistogrammen ska ligga nära varandra. Om en nod rör sig uppåt letar jag först efter felaktigt fördelad IRQ-belastning, drift i sidcachen eller heaps som allokerades till fel nod under uppvärmningen.

NUMA i virtuella datorer och containrar

Vid virtualisering är placeringen av vCPU:er och vRAM på en delad nod viktig för att gästernas arbetsbelastningar inte ska splittras och Fördröjning drar upp. Jag dimensionerar RAM-minnet så att det ryms i den lokala noden och undviker stora virtuella datorer som sträcker sig över flera noder och Drift trigger. För containrar använder jag cpuset-controllers så att pod-grupper fungerar konsekvent på en nod och lagring skapas lokalt. Jag föredrar att placera I/O-tunga gäster på noden med en direkt lagringsanslutning för att hålla DMA-vägarna korta och IRQ-minska bruset. Det innebär att även virtualiseringsvärdar med hög densitet förblir förutsägbara och kan genomföra fler projekt på samma hårdvara.

Jag är uppmärksam på vNUMAExponering: Gästen bör se samma nodstruktur som hypervisorn fysiskt tillhandahåller. vCPU-pinning och vRAM-bindning hör ihop; Jag flyttar hot-adds under underhållsfönster om möjligt, eftersom annars nya sidor hamnar på distans. I Kubernetes ställer jag in QoS på „garanterad“, CPU-hanteraren på „statisk“ och topologimedveten placering så att pods inte flyttas mellan noder. För SR-IOV/VF:er tilldelar jag VF:er till lämplig fysisk nod och binder IRQ-köerna till CPU-uppsättningarna i de pods eller VM:er som de betjänar.

Målinriktad förberedelse av första touch, uppvärmning och höjder

Många prestandafel uppstår under StartHögen växer under uppvärmningsfasen där de första förfrågningarna landar - ofta centralt på en nod. Jag kör därför kontrollerade uppvärmningar för varje nod: Jag startar instanser med en inställd CPU-/minnesmask, kör riktade förbelastningsfrågor och initierar cacheminnen parallellt för varje nod. För JVM-tjänster aktiverar jag pre-touch av heapen; för databaser segmenterar jag buffertpooler nod för nod. Detta minskar efterföljande sidmigreringar och säkerställer att de första förfrågningarna inte slumpmässigt präglar minnesfördelningen.

Kernel/BIOS-tuning för konstanta latenstider

Under motorhuven justerar jag kraft- och avbrottspolicyn:

  • Ställ in CPU-guvernören på „prestanda“, begränsa djupa C-states, använd paket-C-states noggrant för att Jitter för att minska.
  • Reglera inte minnesfrekvensen; balanserade energiprofiler minimerar ofta Genomströmning under belastning.
  • Undvik spread spectrum/klockmodulering om konsekvens är viktigare än minimala energibesparingar.

På kärnnivå håller jag hushålls-CPU:er åtskilda från latenskärnor, minimerar timeravbrott på heta kärnor (nohz_full) och parkerar bakgrundsarbete (komprimering, Kswapd) företrädesvis på systemkärnor i en nod som inte kör latensvägar.

Felsökning och typiska anti-mönster

  • SymptomP99-latenscy hoppar efter distributioner. OrsakHeaps/Caches första kontakten på fel nod. LösningUppvärmning/pre-touch under målaffinitet, sedan öppen lastfördelare.
  • SymptomHög SoftIRQ-tid på „fel“ CPU:er. Orsakirqbalance fördelad över noder. LösningFixa IRQ-affinitet, ställ in RPS/RFS/XPS nodkompatibel.
  • SymptomOOM i en nod, trots att systemets RAM-minne är ledigt. OrsakStrikt NUMA-mask utan buffert. LösningKorrigera kapacitet eller använd „preferred“, upprätta varningar per nod.
  • SymptomOregelbunden genomströmning med NVMe. OrsakFelaktig kömappning, delade köer mellan noderna. Lösning: blk-mq/NVMe-köer per nod, I/O-trådar pinnade.

Checklista för övning

  • Spela in topologi: Noder, kärnor, RAM, PCIe-enheter per socket.
  • Rita serviceavsnitt: Vilka vägar är Fördröjning-kritisk, vilken sats?
  • Ställ in CPU/minnesaffinitet för varje klass; notera första kontakten vid start.
  • Bind IRQ/köer nära noden; kontrollera RSS/RPS/XPS- och NVMe-köer.
  • Övervakning på P95/P99, fjärråtkomst, körkö, IRQ-fördelning.
  • Kontrollera autobalansering specifikt; välj THP/zone_reclaim_mode på lämpligt sätt.
  • Håll vNUMA, vCPU-pinning och vRAM-bindning konsekventa i virtuella datorer/containrar.
  • Testa iterativt, dokumentera, rulla tillbaka vid avvikelser och finjustera.

Kort sammanfattning och avstämningsschema

Det ger den största avkastningen, Trådar och minne tillsammans, förkorta I/O-vägarna och distribuera dem endast försiktigt. Jag börjar med en topologianalys, planerar tjänster nod för nod, ställer in CPU- och minnesaffinitet, ansluter nätverk/lagring på lämpligt sätt och övervakar P95/P99-värden med fokus på Fjärrkontroll-åtkomst. Sedan justerar jag poolstorlekarna, IRQ-maskerna och policyerna tills latensnivåerna sjunker och genomströmningen ökar. För virtuella datorer och containrar kontrollerar jag placeringen separat eftersom hypervisorn har stort inflytande och Gränser fungerar annorlunda. Om du upprepar och dokumenterar den här processen kommer du att få mätbart bättre prestanda med Server NUMA Locality och CPU-Memory Affinity - ofta billigare än att uppgradera ytterligare maskinvara i euro.

Aktuella artiklar