Server NUMA La localizzazione e l'affinità di memoria della CPU determinano la vicinanza dei thread alla loro RAM e la costanza delle latenze negli stack di hosting. Vi mostrerò in modo pratico come sia possibile ottenere un throughput misurabilmente più elevato con il riconoscimento della topologia, le strategie di affinità e i percorsi di I/O vicini al nodo e Latenza notevolmente inferiore.
Punti centrali
Per un rapido orientamento, riassumo i messaggi chiave prima di spiegare i passaggi in dettaglio e sostenerli con esempi, in modo che possiate vedere immediatamente da dove iniziare per Località e Affinity con profitto. Sottolineo le relazioni chiare tra thread, memoria e I/O, in modo da poter ricavare le priorità in modo pulito e Decisioni incontrare. Identifico anche gli scenari in cui l'Interleave ha senso senza diluire i vostri percorsi critici e mostro come potete dimostrare i progressi reali attraverso il monitoraggio e la verifica dei risultati. Errore sono evitati. Per gli ambienti virtualizzati, fornisco suggerimenti sul posizionamento delle vCPU e della vRAM, in modo che i sistemi guest non scivolino su più nodi e che non si verifichino problemi di sicurezza. Remoto-Gli accessi esplodono. Infine, tradurrò i risultati in una breve tabella di marcia, in modo che possiate procedere in modo strutturato e fare ogni passo nella giusta direzione. misurabile sicuro.
- Località primo: mantenete i thread vicino alla vostra RAM, evitate quelli remoti.
- Affinità correggere: Legare core e memoria insieme in base ai criteri.
- Topologia leggere: Nodi, core, dispositivi PCIe per socket.
- Percorsi di I/O bundle: Accoppiare NIC, NVMe e app nello stesso nodo.
- fiere invece di tirare a indovinare: P95/P99, accesso remoto e tracciamento della velocità.
Comprendere la topologia NUMA
Prima di spostare i carichi di lavoro, leggo il documento Topologia del server: quanti nodi NUMA esistono, quanti core e quanta RAM sono collegati a ciascun nodo. Presto anche attenzione a quali dispositivi PCIe, come le schede NIC o le unità SSD NVMe, sono collegati a quale socket, in quanto ciò determina i percorsi di interrupt e gli accessi alla memoria, e a quanta RAM è collegata a ciascun nodo. Latenza caratterizzato. Un nodo consente l'accesso alla memoria locale a breve distanza; tutto ciò che va oltre costa tempo e larghezza di banda. Più la macchina si ingrandisce con più socket, più l'accesso remoto incide sui tempi di risposta e consuma larghezza di banda. Produttività. Per un'introduzione comprensibile alla logica dell'hardware, trovo che un compatto I nodi NUMA in sintesi, per considerare consapevolmente i confini dei nodi ed evitare distribuzioni errate.
In pratica, inizio con un breve inventario della topologia e lo documento in modo da poter poi ricavare le decisioni sull'affinità in modo comprensibile. Comandi utili:
Core # e assegnazione NUMA
lscpu -e=CPU,Core,Socket,Nodo
Panoramica dell'hardware # NUMA
numactl --hardware
# Assegnare i dispositivi PCIe al proprio nodo NUMA
lspci -nn | grep -E "Ethernet|Non volatile"
per d in /sys/bus/pci/devices/*; fare echo -n "$d: "; cat $d/numa_node; done
L'importante è che Complesso radice PCIe e gli slot dei dispositivi ai socket. Due porte della stessa NIC possono essere assegnate a nodi diversi; questo influenza la posizione delle code RX/TX e degli IRQ. Lo stesso vale per NVMe: i moderni controller hanno diverse code che dovrebbero essere assegnate a core vicini al nodo, in modo che il DMA non attivi alcun salto di nodo.
Utilizzo corretto dell'affinità di memoria della CPU
Con l'Affinità CPU-Memoria, collego saldamente i processi alle aree centrali e impongo l'allocazione della memoria locale per quanto possibile, in modo che Discussioni non superino costantemente il bordo del nodo. In Linux, definisco le CPU tramite systemd o cgroup e combino questo con le politiche di memoria in modo che la RAM sia preferibilmente creata sullo stesso nodo e Remoto rimane ridotto al minimo. I servizi critici (front-end API, cache in-memory, database) ne beneficiano immediatamente, perché i tempi di attesa del controller di memoria si riducono e gli hit della cache sono più frequenti. Tuttavia, limiti di pinning troppo rigidi possono limitare la pianificazione, quindi mi avvalgo di ogni regolazione con dei benchmark e osservo i valori P95/P99 per verificare se ci sono effetti evidenti su Utente-esperienza. Un'introduzione compatta ad Affinity in hosting vi aiuta a iniziare: Affinità e consapevolezza NUMA fornire gli strumenti necessari per un posizionamento pulito.
Il fattore decisivo è la Principio del primo contattoLa memoria viene creata sul nodo che scrive per primo sulla pagina. Pertanto, inizializzare gli heap o i buffer di grandi dimensioni sui core di destinazione del nodo in cui il servizio verrà eseguito in seguito, idealmente con i criteri di CPU e memoria già impostati (ad esempio, tramite systemd unit o numactl). Se si avvia il cold sul nodo 0 e poi si spostano i thread sul nodo 1, la maggior parte delle pagine rimane remota. Per gli heap di grandi dimensioni, vale la pena di usare il „pre-touch“ durante l'avvio, in modo che le pagine ruotino localmente e rimangano calde.
Consapevolezza di NUMA nello stack di hosting
Un sistema operativo NUMA-aware, un hypervisor adatto e applicazioni con thread pinning dispiegano insieme tutto il loro potenziale. Potenziale. Il sistema operativo favorisce il posizionamento locale se nel nodo sono disponibili risorse libere, mentre l'hypervisor alloca le macchine virtuali in modo tale che le vCPU e la vRAM non si allontanino tra loro e che le macchine virtuali non si allontanino tra loro. Località viene mantenuto. Nell'applicazione, separo i pool di lavoratori per ogni nodo e mantengo le code a livello locale invece di gestire pool globali in modo trasversale. Organizzo i processi di database, i demoni della cache e le istanze del server web su base nodo per nodo, in modo che i percorsi a caldo rimangano brevi e Jitter diminuisce. Ciò aumenta la coerenza e la prevedibilità sotto carico, che influisce direttamente sulla prevedibilità degli SLA in euro ed evita un costoso overprovisioning.
A livello di Ingress, mi occupo di Affinità dei nodi delle sessioni, ad esempio attraverso un instradamento appiccicoso o un hashing coerente (ad esempio sull'IP del cliente o sui token di sessione), in modo che le richieste finiscano di nuovo al „loro“ worker locale del nodo e alla cache. Per i servizi stateful, pianifico le repliche per nodo e bilancia l'accesso in lettura a livello locale; equalizzo i percorsi di scrittura tramite repliche asincrone o batching per evitare il ping-pong tra i nodi.
Programmare i servizi nodo per nodo
Raggruppo i livelli di una pila in modo tale che ogni livello abbia un chiaro riferimento a un nodo e Percorsi rimanere breve. Una separazione classica: web/API per nodo, app worker accanto ad esso, più la cache locale; anche il database si trova vicino al nodo, se l'ingombro della RAM è sufficiente e il database non è troppo grande. IO-non viene interrotto. Sposto i lavori di reporting, i backup o i batch worker su nodi meno critici, in modo che le richieste interattive non vengano influenzate. Evito le istanze monolite di grandi dimensioni perché spesso attraversano i confini dei nodi e quindi generano un carico remoto, che Prestazioni sfocata. Le istanze più piccole e replicate per nodo spesso offrono un throughput migliore nell'uso quotidiano, poiché rispettano le regole NUMA e attenuano i picchi.
Per la pianificazione della capacità, calcolo spazio libero separatamente per ogni nodo: buffer della CPU per i burst, buffer della RAM contro l'OOM e margini separati per la cache delle pagine. In questo modo, evito che il kernel commuti involontariamente in remoto. Definisco percorsi di commutazione chiari per il failover: se un nodo si guasta, le istanze sostitutive possono essere eseguite da un nodo all'altro, ma limito la loro concomitanza fino al ripristino del nodo originale, in modo da mantenere stabile la latenza complessiva.
Impostazione dell'affinità della CPU: Metodi e insidie
Per l'allocazione dei core, uso systemd con CPUAffinity o cgroups con cpuset.cpus, in modo da avere servizi fissi Aree principali ottenere. Quando faccio il pinning, faccio attenzione alle coppie di hyper-threading, perché due thread logici di un'unità fisica condividono le risorse e possono rallentarsi a vicenda se li combino in modo infelice, e Suggerimenti creare. I percorsi di latenza - terminazione TLS, ingresso API, lettori di cache - ottengono core esclusivi, mentre i log, la compressione o i backup si spostano in altri pool. I pool troppo stretti senza buffer causano code, per cui considero lo spazio a disposizione e verifico gli switch di contesto, la lunghezza delle code e il numero di giri. IRQ-distribuzione. Dall'osservazione deduco se aprire maggiormente i core o concentrarli ulteriormente fino a quando la distribuzione della latenza si riduce in modo netto e i picchi di P99 diventano più silenziosi.
Per ridurre ulteriormente il jitter, ho impostato selettivamente gli interruttori del kernel, come ad esempio nohz_full e rcu_nocbs per i core a latenza esclusiva, isolarli dai servizi di sistema e posizionare deliberatamente gli IRQ solo sulle CPU destinate a questo scopo. Uso il servizio „irqbalance“ con cautela: configuratelo in modo specifico o disattivatelo se ostacola l'affinità IRQ manuale. Uso SCHED_FIFO/SCHED_RR con parsimonia e solo con limiti di Be per evitare l'inversione di priorità o l'inedia.
Politiche di memoria e maschere NUMA
Per la politica della memoria, si distingue tra allocazione locale preferita, interleave su più nodi e maschere NUMA fisse tramite cpuset.mems, in modo che RAM in base al luogo in cui i thread sono effettivamente in esecuzione. Per i servizi interattivi, di solito imposto „preferito“, il che significa che il sistema alloca localmente e devia solo quando c'è una carenza, il che è Remoto-Gli accessi sono limitati. I lavori di analisi o di streaming a volte traggono vantaggio dall'interleave perché la larghezza di banda è distribuita tra i nodi e la pressione su un controller è ridotta. Le maschere fisse offrono un controllo, ma richiedono una disciplina nella pianificazione della capacità, in modo che non si verifichino eventi OOM indesiderati in un nodo e che si verifichino eventi OOM indesiderati in un nodo. Servizi interferire. La tabella seguente suddivide le politiche comuni in scenari tipici e aiuta a prendere una decisione rapida.
| Politica | Effetto | Carichi di lavoro tipici | Il rischio |
|---|---|---|---|
| Preferito (locale) | RAM principalmente nel nodo locale, opzione di ripiego in caso di scarsità | Web/Api, cache, database OLTP | Leggera deriva a pieno carico su altri nodi |
| Interleave | Distribuzione uniforme sui nodi selezionati | Streaming, analisi, scansioni di grandi dimensioni | Latenza più elevata per i singoli accessi |
| Maschera NUMA fissa | Legame rigoroso con i nodi di memoria definiti | Servizi rigorosamente incapsulati, test deterministici | Rischio di OOM se il budget è pianificato in modo errato |
Tenere d'occhio gli interruttori a livello di sistema: modalità_recupero_zona influenza se un nodo pulisce in modo aggressivo la propria memoria prima di allocarla in remoto - spesso indesiderabile per i percorsi a latenza. Pagine trasparenti di grandi dimensioni (THP) può innescare la migrazione delle pagine o generare stalli; per i servizi sensibili alla latenza, di solito scelgo „madvise“ e uso hugepages statiche dove ha senso, in modo da aumentare le visite al TLB e ridurre i picchi di errore delle pagine.
Legare i percorsi di rete e di I/O vicino al nodo
Allineo le code delle NIC (RX/TX) in modo che i loro IRQ puntino ai core del nodo appropriato e l'elaborazione dei pacchetti avvenga nel punto in cui il nodo è stato selezionato. App calcoli. Lo stesso vale per le unità SSD NVMe o i controller RAID: i thread di I/O dovrebbero essere eseguiti sul nodo a cui il dispositivo è collegato tramite PCIe, in modo che i percorsi DMA rimangano brevi e il dispositivo possa essere utilizzato in modo più efficiente. Colli di bottiglia non si concretizzano. Su Linux, regolo le maschere di affinità IRQ e le collego ai pool di CPU dei miei servizi per creare un percorso continuo. In caso di microburst dalla rete, come ad esempio molti handshake TLS, questa vicinanza si ripaga direttamente perché i percorsi di copia sono più brevi e le cache della CPU rimangono calde e Contesto meno frequentemente. In questo modo si ottiene un flusso di dati coerente dal pacchetto all'applicazione alla memoria, senza inutili salti di nodo.
Leve concrete nello stack di rete: RSS per la distribuzione hardware alle code, RPS/RFS per il controllo della CPU basato su software e XPS per la selezione dei TX. Uso ethtool per assegnare le code RX ai gruppi di core che funzionano nello stesso nodo dei lavoratori. Per l'archiviazione uso blk-mq-I controller NVMe offrono diverse code di invio/completamento, che scalano e affinano ≤ numero di core per nodo. Controllate regolarmente se gli interrupt (cat /proc/interrupts) si attivano nei punti in cui si trovano i core dell'applicazione: potete riconoscere una deriva dall'aumento dei byte remoti nonostante un carico stabile.
Struttura dell'architettura applicativa in linea con NUMA
A livello di applicazione, creo i miei pool di lavoratori per ogni nodo NUMA, mantengo le code a livello locale ed evito gli hotspot di blocco globale, in modo che Discussioni non saltare avanti e indietro. Ho impostato lo sharding delle sessioni e dei dati in modo che le partizioni calde rimangano dove sono in esecuzione i lavoratori richiedenti e che le partizioni calde rimangano dove sono in esecuzione i lavoratori richiedenti. Tempo non si perde nel traffico tra i nodi. Per le cache, spesso uso repliche invece di un'istanza centrale, in modo che i lettori raggiungano le copie locali dei nodi. In Netty, Tokio, libuv o nei client DB, applico i cicli di eventi a core fissi e faccio attenzione alla vicinanza degli IRQ, in modo che le modifiche ai task siano limitate e che i task non siano in conflitto con i core. Cache colpire meglio. Questa disposizione riduce l'effetto ping-pong e rende i tempi di risposta più costanti nel corso della giornata.
Una leva sottovalutata è Allocatore e le opzioni di runtime: Gli allocatori abilitati NUMA (jemalloc/tcmalloc) riducono la contesa tra thread e mantengono le pagine più vicine ai kernel di origine dei thread. Negli stack JVM, opzioni come NUMA awareness e pre-touch contribuiscono a rendere deterministiche le fasi di errore; in .NET, allineo i thread GC vicino ai nodi e presto attenzione al GC del server per attenuare i tempi di arresto. In Go, dimensiono GOMAXPROCS per pool di nodi e tengo gli scheduler delle goroutine lontani dai core a latenza che lavorano vicino agli IRQ.
Controllo sensibile dell'autobilanciamento NUMA
I meccanismi automatici di bilanciamento NUMA del kernel possono aiutare a smussare il carico distribuito, ma verifico sempre se sono in grado di supportare la mia Affinità sono compromessi. Nei servizi critici per la latenza, disabilito o limito lo spostamento automatico se questo tira fuori i thread dalla loro memoria locale e Suggerimenti generato. Per i lavori di analisi o per l'elaborazione in batch, tendo a lasciare il bilanciamento attivo perché può aumentare la larghezza di banda senza degradare l'interazione. Un'introduzione pratica alle strategie di bilanciamento mi fornisce ulteriori punti di partenza: Capire il bilanciamento NUMA mostra quando il sistema automatico deve funzionare e quando deve essere assegnato manualmente. Alla fine, prendo una decisione basata sui dati per ogni classe di servizio invece di adottare ciecamente un'impostazione predefinita globale e Obiettivi da non perdere.
Quando il bilanciamento è attivato, monitoro i tassi di migrazione, i picchi di errore minore/maggiore e il consumo di CPU per nodo. Se le pagine vengono spostate avanti e indietro ciclicamente, le contrasto con un pinning più stretto, un pre-touch e maschere di memoria più strette. Nei carichi di lavoro con scansioni lunghe e sequenziali, tuttavia, il bilanciamento può armonizzare il carico, a condizione che non siano interessati percorsi a latenza interattiva.
Monitoraggio: misurare, confrontare, decidere
Senza misurazioni, la messa a punto rimane un gioco di ipotesi, per cui tengo traccia del carico della CPU per core e per nodo, dell'utilizzo della memoria per nodo e della proporzione di Remoto-accessi. Per l'esperienza dell'utente, le latenze P95/P99 contano molto di più dei valori medi, perché gli outlier caratterizzano l'impressione di SLA e Costi verso l'alto. Eseguo profili di carico realistici con cache fredde e calde, perché entrambi i mondi mostrano colli di bottiglia diversi. Dopo ogni modifica, documento le impostazioni, la data del test e i risultati, in modo da poter invertire con sicurezza le modifiche in un secondo momento e Conoscenza non va persa. Se si mettono in relazione anche le metriche delle applicazioni (lunghezza delle code, tentativi, garbage collection) con i valori del sistema, è possibile riconoscere più rapidamente la causa e l'effetto.
Aiuto pratico nell'analisi:
- numastat (relativi al sistema e al processo) per la zona locale e per la zona di processo. Remoto-Hit
- /proc/interruzioni e tempo SoftIRQ della CPU per la deriva degli IRQ
- eventi perf e statistiche dello scheduler per la profondità della coda, gli scambi di contesto, le mancanze di LLC, ecc.
- fio/iperf/wrk con pool di lavoratori specifici per nodo per confronti riproducibili
La valutazione viene effettuata per nodo: Mi aspetto che gli istogrammi di latenza siano vicini. Se un nodo si sposta verso l'alto, cerco innanzitutto un carico IRQ distribuito in modo errato, una deriva nella cache delle pagine o heap allocati al nodo sbagliato durante il riscaldamento.
NUMA nelle macchine virtuali e nei container
Nella virtualizzazione, la collocazione delle vCPU e della vRAM su un nodo comune è importante per evitare che i carichi di lavoro dei guest si frammentino e Latenza tira su. Dimensiono la RAM in modo che si adatti al nodo locale ed evito le macchine virtuali di grandi dimensioni che si estendono su diversi nodi e Deriva innesco. Per i container, uso i controller cpuset in modo che i gruppi di pod funzionino in modo coerente su un nodo e lo storage sia creato localmente. Preferisco posizionare i guest che fanno uso di I/O sul nodo con una connessione diretta allo storage, in modo da mantenere i percorsi DMA corti e IRQ-Ridurre il rumore. Ciò significa che anche gli host di virtualizzazione più densi rimangono prevedibili e portano avanti più progetti sullo stesso hardware.
Presto attenzione a vNUMA-Esposizione: il guest deve vedere la stessa struttura di nodi che l'hypervisor fornisce fisicamente. vCPU pinning e vRAM binding vanno di pari passo; se possibile, sposto gli hot-add durante le finestre di manutenzione, perché altrimenti le nuove pagine finiscono in remoto. In Kubernetes, imposto QoS „garantito“, CPU manager „statico“ e posizionamento topology-aware in modo che i pod non si spostino tra i nodi. Per le SR-IOV/VF, assegno le VF al nodo fisico appropriato e lego le code IRQ ai set di CPU dei pod o delle VM che servono.
Preparazione mirata del primo tocco, del riscaldamento e dei cumuli
Molti errori di prestazione si verificano durante InizioGli accumuli crescono nella fase di riscaldamento, quando arrivano le prime richieste, spesso centralmente su un nodo. Pertanto, eseguo un riscaldamento controllato per ogni nodo: avvio le istanze con una maschera CPU/memoria impostata, eseguo query mirate di pre-caricamento e inizializzo le cache in parallelo per ogni nodo. Per i servizi JVM, attivo il pre-touch dell'heap; per i database, segmento i buffer pool nodo per nodo. Questo riduce le migrazioni successive delle pagine e garantisce che le prime richieste non caratterizzino in modo casuale la distribuzione della memoria.
Messa a punto del Kernel/BIOS per latenze costanti
Sotto il cofano, regolo la potenza e la politica di interruzione:
- Impostate il governatore della CPU su „performance“, limitate gli stati C profondi, utilizzate con attenzione gli stati C dei pacchetti al fine di Jitter ridurre.
- Non limitate la frequenza della memoria; i profili energetici bilanciati spesso riducono al minimo la frequenza della memoria. Produttività sotto carico.
- Evitare lo spettro diffuso/modulazione di clock se la coerenza è più importante del minimo risparmio energetico.
A livello di kernel, tengo separate le CPU di manutenzione dai core di latenza, riduco al minimo gli interrupt del timer sui core caldi (nohz_full) e parcheggio il lavoro in background (compattazione, Kswapd) preferibilmente sui core di sistema di un nodo che non esegue percorsi di latenza.
Risoluzione dei problemi e tipici anti-pattern
- SintomoLa latenza di P99 salta dopo le distribuzioni. CausaHeaps/Cache first-touch sul nodo sbagliato. SoluzioneRiscaldamento/pre-trattamento sotto l'affinità target, quindi aprire il distributore di carico.
- SintomoTempo SoftIRQ elevato su CPU „sbagliate“. Causairqbalance distribuito sui nodi. SoluzioneCorreggere l'affinità IRQ, impostare RPS/RFS/XPS conforme ai nodi.
- SintomoOOM in un nodo, anche se la RAM di sistema è libera. CausaMaschera NUMA rigorosa senza buffer. SoluzioneCorreggere la capacità o usare „preferito“, stabilire gli avvisi per nodo.
- SintomoProduttività irregolare con NVMe. CausaMappatura errata delle code, code condivise tra i nodi. Soluzione: code blk-mq/NVMe per nodo, thread I/O appuntati.
Lista di controllo pratica
- Topologia record: Nodi, core, RAM, dispositivi PCIe per socket.
- Disegnare la sezione di servizio: Quali percorsi sono Latenza-critico, quale lotto?
- Impostare l'affinità CPU/memoria per ogni classe; notare il primo tocco all'inizio.
- Collegare IRQ/queues vicino al nodo; controllare le code RSS/RPS/XPS e NVMe.
- Monitoraggio su P95/P99, accesso remoto, coda di esecuzione, distribuzione IRQ.
- Controllare in modo specifico l'autobilanciamento; selezionare la modalità THP/zone_reclaim_mode in modo appropriato.
- Mantenere vNUMA, vCPU pinning e vRAM binding coerenti nelle macchine virtuali/container.
- Eseguire test iterativi, documentare, tornare indietro in caso di deriva e mettere a punto.
Breve riassunto e programma di messa a punto
Porta il massimo rendimento, Discussioni e memoria insieme, accorciare i percorsi di I/O e distribuirli solo con attenzione. Inizio con l'analisi della topologia, pianifico i servizi nodo per nodo, imposto l'affinità di CPU e memoria, collego rete/storage in modo appropriato e monitoro i valori P95/P99 con particolare attenzione a Remoto-accessi. Quindi modifico le dimensioni dei pool, le maschere IRQ e le politiche fino a quando i picchi di latenza si attenuano e il throughput aumenta. Per le macchine virtuali e i container, verifico il posizionamento separatamente perché l'hypervisor ha un'influenza notevole e Confini funzionano in modo diverso. Se ripetete e documentate questo processo, otterrete un aumento misurabile delle prestazioni di Server NUMA Locality e CPU-Memory Affinity, spesso più economico dell'aggiornamento di hardware aggiuntivo in euro.


