...

softirq cpu hosting: ottimizzare le prestazioni del server e il throughput di rete

Mostro come softirq cpu insieme a NAPI, distribuzione degli IRQ e progettazione delle code limita o libera il throughput di rete nell'hosting. Con punti di misurazione chiari, messa a punto mirata e affinità pulite, riduco Latenze e aumentare costantemente il throughput pps dei server produttivi.

Punti centrali

Queste idee fondamentali trasportano i pacchetti di rete in modo efficiente attraverso la CPU, il kernel e la NIC, mantenendo i tempi di risposta al minimo. costante basso.

  • Bilancio NAPI regolazione fine: Un numero maggiore di pacchetti per sondaggio riduce le spese generali e rende più fluida la Carico della CPU.
  • Bilanciamento IRQ e affinità: evitare gli hotspot, aumentare le visite alla cache, Picchi di latenza Premere.
  • Multi-queue, RSS/RPS/XPS: parallelizzazione dei flussi, mantenimento dell'allineamento NUMA, pps aumento.
  • Scarichi utilizzare consapevolmente: GRO/LRO, TSO, valutare la coalescenza, Jitter in vista.
  • Isolamento e il Busy Polling: tempi di risposta prevedibili su sistemi dedicati. Nuclei.

Nozioni di base: cosa succede nel kernel durante il traffico di rete

Un pacchetto arriva prima in un interrupt hardware, dopodiché il kernel si occupa del lavoro in SoftIRQ e i cicli di polling NAPI. Mi assicuro che la fase HardIRQ veloce rimanga molto breve e che la logica attuale si sposti nel contesto giusto, in modo che la tempo di CPU non si esaurisce. I thread di ksoftirqd intervengono solo se l'elaborazione diretta non è possibile, il che porta rapidamente a code sotto carico continuo. È proprio qui che si verificano i tempi di attesa, che si riflettono in un aumento del TTFB e in una fluttuazione del throughput. Se volete approfondire, potete trovare nozioni pratiche sull'elaborazione degli IRQ in questo articolo su Gestione degli interrupt e prestazioni della CPU, che utilizzo per la categorizzazione.

NAPI, SoftIRQ e ksoftirqd: controllare la latenza invece di gestirla

NAPI riduce le tempeste di interruzioni raccogliendo diversi pacchetti per esecuzione entro un budget definito e riducendo così al minimo il tempo di interruzione. Spese generali si abbassa. Se il budget è insufficiente, i pacchi si accumulano, il ksoftirqd si surriscalda e la Latenza aumenta in modo misurabile. In queste situazioni, controllo sistematicamente /proc/softirqs e /proc/net/softnet_stat per visualizzare cadute, time_squeeze o code traboccanti. Poi aumento gradualmente net.core.netdev_budget o net.core.netdev_budget_usecs e monitoro in parallelo il carico della CPU, la distribuzione p95/p99 e le perdite di pacchetti. Il trucco sta nel riuscire a fare abbastanza lavoro per ogni poll senza affollare l'esecuzione interattiva dei thread userland.

Bilanciamento e affinità IRQ: evitare gli hotspot, aumentare le visite alla cache

Un singolo core con tutti gli IRQ della NIC diventa un collo di bottiglia perché deve servire gli interrupt, gli IRQ soft e i thread dell'applicazione; quindi distribuisco IRQ mirato. Il servizio irqbalance è d'aiuto, ma per le alte velocità di trasmissione mappo esplicitamente le code RX/TX tramite l'affinità con le code più adatte. nuclei. Sui sistemi NUMA, lego le code ai core dello stesso nodo per evitare accessi remoti alla memoria. I thread delle applicazioni vengono eseguiti su core vicini ma separati, il che migliora la localizzazione della cache e la schedulabilità. Questa guida alla distribuzione strategica fornisce una buona panoramica della Bilanciamento degli IRQ nel data center, che uso come riferimento per la messa a punto.

Multi-queue, RSS/RPS/XPS: utilizzare correttamente la parallelizzazione

Le moderne schede NIC sono dotate di diverse code RX/TX, che posso controllare tramite RSS ai flussi, ottenendo così un vero parallelismo. Se la scheda offre un numero insufficiente di code, utilizzo RPS/XPS per effettuare regolazioni sul lato software al fine di distribuire i pacchetti in modo sensato tra i flussi. nuclei da spingere. La distribuzione pulita degli hash è importante per far sì che un flusso rimanga sempre sulla stessa CPU e che non si verifichino costose distorsioni della cache. Allo stesso tempo, mantengo i percorsi TX e RX vicini tra loro per evitare la contesa dei blocchi e gli accessi non necessari tra i nodi. In questo modo si aumenta il throughput pps senza che un singolo core tiri il freno.

Affinità della CPU nello spazio utente: pensiero end-to-end

Pianifico il percorso dei dati dalla NIC-IRQ tramite le code NAPI ai thread worker dell'applicazione, in modo che i pacchetti raggiungano la loro destinazione senza agganci inutili e che la Tempo di risposta rimane costante. Per ottenere questo risultato, separo coerentemente i core per gli interrupt/softIRQ da quelli per le applicazioni e creo dei nuclei chiari per le applicazioni. Affinità-regole. Ai server web, ai reverse proxy e ai database vengono assegnati set di CPU fissi, vicini ai core IRQ, per mantenere i percorsi brevi. Inoltre, ho impostato il governatore della CPU sulle prestazioni, in modo che le variazioni di clock non spingano il jitter in p99. Questa assegnazione coerente rende il comportamento prevedibile e aiuta a diagnosticare i colli di bottiglia in modo pulito.

Offload, GRO/LRO, firewall e eBPF: risparmiare il carico senza volare alla cieca

Salvataggio di checksum offload, TSO e coalescenza tempo di CPU, Tuttavia, possono modificare le dimensioni dei pacchetti, il comportamento dei burst e il jitter, motivo per cui misuro gli effetti in modo specifico. GRO/LRO raggruppano i frame e riducono il carico sullo stack, ma per le esigenze in tempo reale decido in base alla situazione. Disattivazione o di uso limitato. Le tabelle di Conntrack e le catene profonde di nftables/iptables costano orologi, quindi riordino le regole superflue e semplifico i percorsi. Se necessario, ricorro a eBPF (XDP, tc-BPF) per prendere decisioni tempestive sul NIC ed evitare percorsi costosi. Un buon punto di partenza per la pratica della messa a punto è questa panoramica di Interruzione della coalescenza, che tengo in considerazione per i budget di latenza sensibili.

Polling occupato e isolamento della CPU: blocco dei tempi di risposta

Per gli obiettivi a bassa latenza, utilizzo il polling occupato, in modo che i socket dello spazio utente raccolgano i pacchetti anche prima e Tempi di attesa accorciare. Questo aumenta il carico, ma mi fornisce distribuzioni p99 molto strette per i carichi di lavoro API o di trading su un sistema dedicato. Nuclei. Inoltre, isolo i core con isolcpus=, nohz_full= e rcu_nocbs= in modo che timer, RCU e servizi di sistema vengano eseguiti solo sulle CPU di mantenimento. Questa separazione evita le interferenze sui core di latenza e rende il comportamento riproducibile. Il risultato è una chiara tabella di marcia: core dedicati, raccolta anticipata dei pacchetti, budget definiti.

Monitoraggio e risoluzione dei problemi: dal sintomo alla causa

Inizio con pps, throughput e carico del core, poi controllo le cadute e l'attività di ksoftirqd-nel tempo per riconoscere in modo affidabile gli schemi. Strumenti come sar, htop, ss, nload e ethtool mi mostrano quando e dove si verificano le congestioni e se il sistema è in grado di gestire il traffico. Spunti raggiungono i loro limiti. Le distribuzioni sono importanti invece dei valori medi, in modo da non perdere i picchi serali, le finestre cron o le campagne. Metto in relazione i picchi TTFB con la distribuzione degli IRQ, il budget NAPI e le impostazioni di offload per apportare modifiche mirate. Una regolazione dell'affinità IRQ o un nuovo budget NAPI su misura sono spesso sufficienti per ridurre sensibilmente i timeout.

Panoramica dei parametri di sintonizzazione

La seguente panoramica mi aiuta a utilizzare le modifiche con saggezza e ad assegnare gli effetti in modo chiaro prima di apportare modifiche permanenti. lanci piano. Testiamo ogni aggiustamento in modo iterativo, misuriamo le distribuzioni di latenza e osserviamo gli effetti secondari su CPU e la memoria. Modifico sempre e solo un punto per ogni finestra di test, in modo che la causa e l'effetto rimangano chiari. Quindi documento i risultati e stabilisco i valori di soglia per gli avvisi. In questo modo ottengo miglioramenti riproducibili senza rischiare sorprese nel traffico produttivo.

Parametro/caratteristica Effetto nel percorso dati Quando raccogliere/attivare Rischi/effetti collaterali
net.core.netdev_budget Più pacchetti per sondaggio NAPI Per le gocce in softnet_stat I sondaggi più lunghi eliminano le discussioni degli utenti
net.core.netdev_budget_usecs Limitare la finestra temporale per ogni sondaggio Per il jitter dovuto a burst di grandi dimensioni Troppo piccolo: più modifiche al contesto
RSS/RPS/XPS Distribuire i flussi tra i core Per gli hotspot su un nucleo Hash errati: distorsioni della cache
Affinità IRQ Legare gli IRQ vicino al core Con NUMA-Missmatch La cattiva allocazione crea nuovi hotspot
GRO/LRO/TSO Riduce il numero di pacchetti Per il collo di bottiglia della CPU Jitter, burst più grandi
Sondaggio occupato Raccolta anticipata dei pacchi Per gli obiettivi difficili di p99 Maggiore consumo di CPU

Anelli RX/TX e profondità della stecca: dimensionamento corretto dei buffer

Anche con IRQ distribuiti correttamente e budget adeguati, anelli NIC troppo piccoli o troppo grandi possono ridurre le prestazioni. Per questo motivo verifico le dimensioni degli anelli RX/TX della scheda e le adatto agli obiettivi di burst e latenza. Anelli troppo piccoli causano cali nella NIC durante i picchi di traffico, visibili come rx_missed_errors o fifo_errors nelle statistiche del driver. Gli anelli troppo grandi nascondono la congestione, aumentano la latenza e creano lunghi bordi di uscita in p95/p99. Sto cercando una via di mezzo: un buffer sufficiente per assorbire brevi raffiche, ma non così tanto da far “invecchiare” i pacchetti nelle code.

Inoltre, si esamina il lato host tx_queue_len e il disco Q utilizzato. Con sch_fq o fq_codel posso attenuare il comportamento dei burst e distribuire i pacchetti TSO di grandi dimensioni tramite il pacing. In questo modo si riducono i microburst sulla porta dello switch e si rende la curva di latenza più uniforme, importante per i carichi di lavoro misti in cui piccoli RPC vengono eseguiti insieme a grandi upload. Monitoro le statistiche di ethtool e le metto in relazione con softnet_stat per capire se la congestione si verifica nell'anello NIC, nel backlog netdev o nel Qdisc.

MTU, jumbo frame e segmentazione

Il sito MTU è una leva classica che spesso viene sottovalutata. I jumbo frame riducono il numero di pacchetti per Gbit/s e riducono il carico sulla CPU, ma solo se il percorso è veramente in grado di gestire i jumbo end-to-end. Pertanto, convalido sistematicamente le stazioni remote, gli switch e i tunnel. Non appena la frammentazione torna a 1500 da qualche parte, c'è il rischio di problemi di MTU del percorso, di ritrasmissioni e di inutili Jitter. Nei data center con una comunicazione dominante tra Est e Ovest, vale la pena adottare una strategia omogenea a 9k, mentre 1500 è spesso la scelta più stabile per i carichi di lavoro rivolti a Internet.

Valuto sempre l'MTU insieme a TSO/GSO/GROUn bundling troppo aggressivo può portare a grandi burst nel TX che riempiono i buffer a monte e generano picchi di latenza. L'obiettivo è un percorso coerente: una segmentazione sensata al trasmettitore, meccanismi di pacing sufficienti e un GRO che risparmia lavoro sul lato del ricevitore senza vanificare i requisiti in tempo reale.

UDP, QUIC e carichi di lavoro in streaming: considerare le specifiche

Non tutto il traffico è TCP. UDP-I profili pesanti (DNS, VoIP, QUIC, telemetria) si comportano in modo diverso in RSS/RPS e GRO. Gli stack moderni supportano UDP-GRO/GSO, che può ridurre il carico sulla CPU - io lo uso selettivamente e misuro se i rischi di riordino o il jitter aumentano. Per i carichi QUIC/HTTP3, la distribuzione pulita dei flussi è fondamentale: RPS può aiutare se la NIC offre un numero troppo basso di code RSS, ma non deve “buttare in giro” i flussi caldi della cache. Sul lato TX ho impostato XPS per raggruppare i percorsi di trasmissione e ridurre la contesa sui blocchi. In pratica, un'allocazione silenziosa e core-affine è vantaggiosa, soprattutto con molti flussi UDP di medie dimensioni, dove ogni hit della cache è importante.

Virtualizzazione e container: integrazione pulita di host, guest e vhost

Negli ambienti virtualizzati, il lavoro si sposta tra host, thread vhost e IRQ guest. Mi assicuro che vhost-rete-I thread ricevono i propri core e non si scontrano con gli app worker. Le loro affinità devono corrispondere alle code RX/TX fisiche, altrimenti si verificherà un'inutile migrazione tra CPU. Nel guest, controllo le code di virtio-net, attivo il multi-queue e imposto RSS/RPS analoghi a quelli di bare metal. Dove latenza e pps sono in primo piano SR-IOV ridurre ulteriormente i costi generali - il prerequisito è una topologia NUMA coerente: VF, vCPU e memoria appartengono allo stesso nodo.

Nello stack container, le reti overlay, le catene NAT profonde e le topologie CNI complesse causano ulteriori hop. Per i servizi critici in termini di latenza, preferisco hostNetwork o reti snelle (macvlan/ipvlan), equalizzare i percorsi NAT e mantenere Traccia di collegamento il più piccolo possibile. È importante una strategia coerente per la CPU: i core IRQ e NAPI dell'host dovrebbero essere situati nelle vicinanze dei core su cui vengono eseguiti i vhost/container worker; questo è l'unico modo per mantenere il percorso dei dati breve e prevedibile.

Schedulazione, stati C e threading IRQ

Perché la latenza non è solo tempo di calcolo, ma anche Orario di sveglia Riduco al minimo gli stati C profondi sui core a latenza. Un risparmio energetico aggressivo può costare millisecondi prima che una SoftIRQ venga effettivamente eseguita. Per questo mi affido ai regolatori di prestazioni, limito gli stati C profondi e mantengo il turbo costante per rendere prevedibili i salti di frequenza. Altrettanto importante è Filettatura IRQDove i driver lo consentono, sposto il lavoro sui thread IRQ e stabilisco le priorità in modo che RX si avvii prima del lavoro a valle, senza spostare completamente userland. L'interazione tra le politiche di schedulazione, le affinità e i budget è complicata; faccio dei test passo dopo passo, registro p99 e sto attento alle interferenze con ksoftirqd, che altrimenti diventa un collo di bottiglia segreto.

L'osservazione in profondità: punti traccia, contatori, istogrammi

Se le metriche rimangono vaghe, vado un livello più profondo: utilizzo i punti di traccia del kernel attorno a netif_receive_skb, napi_poll e coda_di_rete, per visualizzare la durata dei sondaggi, il volume dei pacchetti e i tempi di attesa sotto forma di istogrammi. Tali distribuzioni mostrano se 1 % dei polling sta impiegando troppo tempo o se le singole code si stanno esaurendo. Inoltre, ethtool-rx/tx-I contatori, le ritrasmissioni TCP, le risposte al poll busy e softnet_stat indicano chiaramente dove vengono persi i pacchetti. Uso l'analisi dei drop per riconoscere se la NIC sta perdendo (anello pieno), se il backlog di netdev sta collassando (time_squeeze) o se il Qdisc/firewall sta rallentando. Solo quando questi pezzi del puzzle si incastrano, modifico anelli, budget o offload.

Semplificare i percorsi di sicurezza e filtraggio

ACL complesse, catene profonde di nftables/iptables e ampie tabelle di conntrack aggiungono una latenza costante per pacchetto. Consolido le regole, lavoro con set/mappe e sposto i drop generici il più avanti possibile nel percorso, idealmente il più presto possibile sulla NIC (XDP/clsact) se la latenza è fondamentale. I flussi stateless, la telemetria o le porte “sicure” conosciute possono essere utilizzati in modo mirato. senza tracciamento per eliminare la necessità di costose ricerche. Allo stesso tempo, mantengo fresche le tabelle di stato, regolo le dimensioni degli hash in base ai picchi di carico e riordino aggressivamente le voci orfane. L'obiettivo è un percorso di policy pulito e tracciabile, che non si noti nel profilo come un carico permanente.

Anti-pattern tipici e come li evito

  • Tutti gli IRQ su un core: porta alla congestione e al ksoftirqd caldo. Antidoto: affinità mirate per cue, NUMA-coherent.
  • Massimizzazione cieca di anelli/budget: nasconde la congestione, aumenta le code di latenza. Antidoto: aumentare in modo incrementale, misurare le distribuzioni.
  • Configurazione errata dell'hashing del flusso: I flussi saltano da un core all'altro, le cache si esauriscono. Antidoto: chiavi RSS stabili, RPS/XPS solo con un obiettivo chiaro.
  • I thread dell'applicazione sugli stessi core di SoftIRQ: Interferenze e jitter. Antidoto: separazione rigida, allocazione per prossimità.
  • Sovrapposizioni/NAT senza budget: aggiunto a ciascun hop. Rimedio: razionalizzare i percorsi e le reti di host per i carichi di lavoro con latenza.
  • Risparmio energetico sui core a latenza: Gli stati C profondi rallentano la reazione. Antidoto: regolatore di prestazioni, limitazione degli stati C.
  • Scarico senza misurazione: TSO/GRO può esacerbare i burst e il jitter. Rimedio: attivare il carico di lavoro specifico, monitorare p99.

Hosting pratico: passi che funzionano

Inizio con una fase di misurazione pulita, stabilisco delle linee di base e mantengo tutti i cambiamenti di piccola entità in finestre temporali brevi, in modo da poter Cause possono essere separati. Attivo quindi irqbalance, controllo la distribuzione automatica e, se necessario, imposto le affinità manuali fino a quando non Hotspot non sono più visibili. Quindi configuro Multi-Queue, RSS e, se necessario, RPS/XPS, sincronizzati con NUMA. Lego gli app worker a core vicini ai loro IRQ, ma senza collisioni dirette. Infine, spurgo i percorsi del firewall, controllo le tabelle di conntrack e prendo decisioni consapevoli sugli offload in base agli obiettivi di latenza.

Esempio di playbook per le latenze di p99

Per prima cosa misuro p95/p99 tramite il carico rappresentativo e i log sicuri di /proc/softirqs e /proc/net/softnet_stat, al fine di Gocce e time_squeeze sono chiaramente visibili. Poi aumento netdev_budget o netdev_budget_usecs passo dopo passo e tengo premuto p99 dopo ogni modifica in modo da poter vedere i dati reali. Tendenze riconoscere. In parallelo, lego gli IRQ ai core di un nodo NUMA e sposto i lavoratori dell'applicazione nei vicini adatti. Se p99 continua a saltare, provo le varianti GRO/LRO e i profili di coalescenza degli interrupt, ciascuno con un breve percorso di misurazione. Solo quando la distribuzione rimane stabile trasferisco la configurazione ai ruoli Ansible o ai dropin systemd.

Versione breve per gli amministratori

Raggiungo la massima leva finanziaria SoftIRQ, Il budget NAPI, le affinità IRQ e i thread dell'applicazione come percorso dati coerente. Distribuisco il lavoro di rete tra i core, mantengo le code coerenti con NUMA e collego i lavoratori in modo sensato in modo che Percorsi non è un problema. Imposto deliberatamente gli offload e misuro il jitter invece di ottimizzare alla cieca il throughput. Per gli obiettivi di latenza difficili, mi affido al polling occupato e all'isolamento della CPU, mentre le CPU di mantenimento intercettano le interferenze. Se si implementano questi passaggi in modo disciplinato, si ottiene un throughput costante, distribuzioni di latenza più strette e un ambiente di hosting che reagisce in modo prevedibile ai picchi di carico.

Articoli attuali