Ottimizzazione di Linux per gestire 1.2 milioni di richieste JSON al secondo

È stata pubblicata una guida dettagliata sull'ottimizzazione dell'ambiente Linux per ottenere le massime prestazioni nell'elaborazione delle richieste HTTP. I metodi proposti hanno permesso di aumentare le prestazioni del processore JSON basato sulla libreria libreactor in ambiente Amazon EC2 (4 vCPU) da 224mila richieste API al secondo con impostazioni standard di Amazon Linux 2 con kernel 4.14 a 1.2 milioni di richieste al secondo secondo dopo l'ottimizzazione (con un aumento del 436%) e ha portato anche a una riduzione dei ritardi nell'elaborazione delle richieste del 79%. I metodi proposti non sono specifici di libreactor e funzionano quando si utilizzano altri server http, inclusi nginx, Actix, Netty e Node.js (libreactor è stato utilizzato nei test perché la soluzione basata su di esso ha mostrato prestazioni migliori).

Ottimizzazione di Linux per gestire 1.2 milioni di richieste JSON al secondo

Ottimizzazioni di base:

  • Ottimizzazione del codice libreactor. Come base è stata utilizzata l'opzione R18 del kit Techempower, che è stata migliorata rimuovendo il codice per limitare il numero di core della CPU utilizzati (l'ottimizzazione ha consentito di accelerare il lavoro del 25-27%), assemblando in GCC con le opzioni “-O3” (un aumento del 5-10%) e "-march-native" (5-10%), sostituendo le chiamate di lettura/scrittura con recv/send (5-10%) e riducendo il sovraccarico quando si utilizzano pthread (2-3%) . L'aumento complessivo delle prestazioni dopo l'ottimizzazione del codice è stato del 55% e il throughput è aumentato da 224 req/s a 347 req/s.
  • Disabilita la protezione contro le vulnerabilità dell'esecuzione speculativa. L'utilizzo dei parametri "nospectre_v1 nospectre_v2 pti=off mds=off tsx_async_abort=off" durante il caricamento del kernel ha consentito di aumentare le prestazioni del 28% e il throughput è aumentato da 347k req/s a 446k req/s. Separatamente, l'aumento del parametro "nospectre_v1" (protezione da Spectre v1 + SWAPGS) è stato dell'1-2%, "nospectre_v2" (protezione da Spectre v2) - 15-20%, "pti=off" (Spectre v3/Meltdown) - 6%, "mds=off tsx_async_abort=off" (interruzione asincrona MDS/Zombieload e TSX) - 6%. Le impostazioni per la protezione contro gli attacchi L1TF/Foreshadow (l1tf=flush), iTLB multihit, Speculative Store Bypass e SRBDS sono rimaste invariate, il che non ha influito sulle prestazioni poiché non si intersecavano con la configurazione testata (ad esempio, specifica per KVM, annidata virtualizzazione e altri modelli di CPU).
  • Disabilitare i meccanismi di controllo e di blocco delle chiamate di sistema utilizzando il comando "auditctl -a never,task" e specificando l'opzione "--security-opt seccomp=unconfined" all'avvio del contenitore docker. L'aumento complessivo delle prestazioni è stato dell'11% e il throughput è aumentato da 446 req/s a 495 req/s.
  • Disabilitare iptables/netfilter scaricando i moduli del kernel associati. L'idea di disattivare il firewall, che non veniva utilizzato in una determinata soluzione server, è nata dai risultati della profilazione, a giudicare dal fatto che la funzione nf_hook_slow impiegava il 18% del tempo per essere eseguita. È da notare che nftables funziona in modo più efficiente di iptables, ma Amazon Linux continua a utilizzare iptables. Dopo aver disabilitato iptables, l'aumento delle prestazioni è stato del 22% e il throughput è aumentato da 495 req/s a 603 req/s.
  • Migrazione ridotta dei gestori tra diversi core della CPU per migliorare l'efficienza dell'utilizzo della cache del processore. L'ottimizzazione è stata effettuata sia a livello di associazione dei processi libreactor ai core della CPU (CPU Pinning) sia attraverso il pinning dei gestori di rete del kernel (Receive Side Scaling). Ad esempio, irqbalance è stato disabilitato e l'affinità della coda con la CPU è stata impostata esplicitamente in /proc/irq/$IRQ/smp_affinity_list. Per utilizzare lo stesso core della CPU per elaborare il processo libreactor e la coda di rete dei pacchetti in entrata, viene utilizzato un gestore BPF personalizzato, connesso impostando il flag SO_ATTACH_REUSEPORT_CBPF durante la creazione del socket. Per legare le code dei pacchetti in uscita alla CPU sono state modificate le impostazioni /sys/class/net/eth0/queues/tx- /xps_cpus. L'aumento complessivo delle prestazioni è stato del 38% e il throughput è aumentato da 603 req/s a 834 req/s.
  • Ottimizzazione della gestione degli interrupt e dell'uso del polling. Abilitando la modalità Adaptive-Rx nel driver ENA e manipolando sysctl net.core.busy_read le prestazioni sono aumentate del 28% (il throughput è aumentato da 834k req/s a 1.06M req/s e la latenza è diminuita da 361μs a 292μs).
  • Disabilitazione dei servizi di sistema che portano a blocchi non necessari nello stack di rete. La disabilitazione di dhclient e l'impostazione manuale dell'indirizzo IP hanno comportato un aumento delle prestazioni del 6% e un aumento del throughput da 1.06 milioni di req/s a 1.12 milioni di req/s. Il motivo per cui dhclient influisce sulle prestazioni è nell'analisi del traffico utilizzando un socket non elaborato.
  • Combattere lo Spin Lock. Il passaggio dello stack di rete alla modalità "noqueue" tramite sysctl "net.core.default_qdisc=noqueue" e "tc qdisc replace dev eth0 root mq" ha portato ad un aumento delle prestazioni del 2% e il throughput è aumentato da 1.12 milioni di req/s a 1.15 milioni richiesta/i.
  • Ultime ottimizzazioni minori, come disabilitare GRO (Generic Receive Offload) con il comando “ethtool -K eth0 gro off” e sostituire l'algoritmo di controllo della congestione cubica con reno utilizzando sysctl “net.ipv4.tcp_congestion_control=reno”. L'aumento complessivo della produttività è stato del 4%. Il throughput è aumentato da 1.15 milioni di richieste/s a 1.2 milioni di richieste/s.

Oltre alle ottimizzazioni che hanno funzionato, l'articolo illustra anche i metodi che non hanno portato all'aumento delle prestazioni previsto. Ad esempio, quanto segue si è rivelato inefficace:

  • L'esecuzione separata di libreactor non differiva in termini di prestazioni rispetto all'esecuzione in un contenitore. La sostituzione di writev con send, l'aumento di maxevents in epoll_wait e la sperimentazione con versioni e flag di GCC non hanno avuto alcun effetto (l'effetto era evidente solo per i flag "-O3" e "-march-native").
  • L'aggiornamento del kernel Linux alle versioni 4.19 e 5.4, utilizzando gli scheduler SCHED_FIFO e SCHED_RR, manipolando sysctl kernel.sched_min_granularity_ns, kernel.sched_wakeup_granularity_ns, trasparente_hugepages=never, skew_tick=1 e clocksource=tsc non ha influenzato le prestazioni.
  • Nel driver ENA, l'abilitazione delle modalità Offload (segmentazione, scatter-gather, checksum rx/tx), la creazione con il flag "-O3" e l'utilizzo dei parametri ena.rx_queue_size e ena.force_large_llq_header non hanno avuto alcun effetto.
  • Le modifiche allo stack di rete non hanno migliorato le prestazioni:
    • Disabilita IPv6: ipv6.disable=1
    • Disabilita VLAN: modprobe -rv 8021q
    • Disabilita il controllo dell'origine del pacchetto
      • net.ipv4.conf.all.rp_filter=0
      • net.ipv4.conf.eth0.rp_filter=0
      • net.ipv4.conf.all.accept_local=1 (effetto negativo)
    • net.ipv4.tcp_sack = 0
    • net.ipv4.tcp_dsack=0
    • net.ipv4.tcp_mem/tcp_wmem/tcp_rmem
    • net.core.netdev_budget
    • net.core.dev_weight
    • net.core.netdev_max_backlog
    • net.ipv4.tcp_slow_start_after_idle=0
    • net.ipv4.tcp_moderate_rcvbuf=0
    • net.ipv4.tcp_timestamps=0
    • net.ipv4.tcp_low_latency = 1
    • SO_PRIORITÀ
    • TCP_NODELAY

    Fonte: opennet.ru

Aggiungi un commento