Ein detailliertes Handbuch zur Feinabstimmung der Linux-Umgebung zur Maximierung der HTTP-Anfrage-Verarbeitungsleistung wurde veröffentlicht. Die vorgeschlagenen Methoden ermöglichten es, die Leistung des JSON-Handlers auf Basis der libreactor-Bibliothek in der Amazon EC2-Umgebung (4 vCPU) von 224.000 API-Anfragen pro Sekunde bei den Standardeinstellungen von Amazon Linux 2 mit Kernel 4.14 auf 1,2 Millionen Anfragen pro Sekunde nach der Optimierung (436 % Steigerung) zu erhöhen und gleichzeitig die Latenzzeiten bei der Anfrageverarbeitung um 79 % zu reduzieren. Die vorgeschlagenen Methoden sind nicht spezifisch für libreactor und funktionieren auch mit anderen HTTP-Servern, einschließlich nginx, Actix, Netty und Node.js (libreactor wurde in den Tests verwendet, da diese Lösung die beste Leistung erzielt hat).

Wesentliche Optimierungen:
- Optimierung des libreactor-Codes. Als Grundlage diente die R18-Version aus dem Techempower-Benchmark, die durch das Entfernen von Code zur Beschränkung der Anzahl der verwendeten CPU-Kerne optimiert wurde (dies führte zu einer Beschleunigung um 25-27 %). Zudem wurde mit GCC unter den Optionen „-O3“ (Gewinn von 5-10 %) und „-march-native“ (ebenso 5-10 %) kompiliert. Aufrufe von read/write wurden durch recv/send ersetzt (5-10 %), und die Overheadkosten bei der Verwendung von pthreads wurden gesenkt (2-3 %). Der Gesamtnutzen nach der Code-Optimierung betrug 55 %, während die Durchsatzrate von 224k req/s auf 347k req/s anstieg.
- Deaktivierung des Schutzes vor Schwachstellen, die durch spekulative Ausführung von Anweisungen verursacht werden. Die Verwendung der Boot-Parameter „nospectre_v1 nospectre_v2 pti=off mds=off tsx_async_abort=off“ ermöglichte eine Leistungssteigerung um 28 %, während die Durchsatzrate von 347k req/s auf 446k req/s stieg. Der separate Anstieg durch den Parameter „nospectre_v1“ (Schutz vor Spectre v1 + SWAPGS) betrug 1-2 %, „nospectre_v2“ (Schutz vor Spectre v2) – 15-20 %, „pti=off“ (Spectre v3/Meltdown) – 6 %, „mds=off tsx_async_abort=off“ (MDS/Zombieload und TSX Asynchronous Abort) – 6 %. Die Einstellungen zum Schutz vor L1TF/Foreshadow-Angriffen (l1tf=flush), iTLB-Multihit, Speculative Store Bypass und SRBDS, die die Leistung nicht beeinflussten, da sie nicht mit der getesteten Konfiguration überlappten (z. B. spezifisch für KVM, verschachtelte Virtualisierung und andere CPU-Modelle).
- Deaktivierung der Überwachungsmechanismen und Blockierung von Systemaufrufen mit dem Befehl „auditctl -a never,task“ und Angabe der Option „—security-opt seccomp=unconfined“ beim Starten des Docker-Containers. Der Gesamtnutzungsgewinn betrug 11 %, und die Durchsatzrate stieg von 446k req/s auf 495k req/s.
- Deaktivierung von iptables/netfilter durch das Entladen der zugehörigen Kernelmodule. Die Idee, die Firewall zu deaktivieren, die in dieser speziellen Serverlösung nicht genutzt wurde, wurde durch Profilierungsergebnisse motiviert, die zeigten, dass 18 % der Zeit für die Ausführung der Funktion nf_hook_slow verwendet wurden. Es wird festgestellt, dass nftables effizienter als iptables arbeitet, jedoch wird in Amazon Linux weiterhin iptables verwendet. Nach der Deaktivierung von iptables betrug der Leistungszuwachs 22 %, und die Durchsatzrate stieg von 495k req/s auf 603k req/s.
- Reduzierung der Migration von Handlern zwischen verschiedenen CPU-Kernen zur Verbesserung der Nutzung des Prozessor-Caches. Die Optimierung erfolgte sowohl auf der Ebene der Prozessbindung von libreactor an die CPU-Kerne (CPU-Pinning) als auch durch die Festlegung von Netzwerk-Handler-Bindungen (Receive Side Scaling). Beispielsweise wurde irqbalance deaktiviert und die Bindungen von Warteschlangen an die CPU in /proc/irq/$IRQ/smp_affinity_list explizit festgelegt. Zur Nutzung desselben CPU-Kerns für die Verarbeitung des libreactor-Prozesses und der Netzwerkwarteschlange für eingehende Pakete wurde ein eigener BPF-Handler verwendet, der durch Setzen des Flags SO_ATTACH_REUSEPORT_CBPF beim Erstellen des Sockets angeschlossen wurde. Für die Bindung der Warteschlangen für ausgehende Pakete wurden die Einstellungen in /sys/class/net/eth0/queues/tx-<n>/xps_cpus geändert. Der Gesamte Leistungszuwachs betrug 38%, und die Durchsatzrate stieg von 603k req/s auf 834k req/s.
- Optimierung der Interrupt-Verarbeitung und Verwendung von Polling. Die Aktivierung des adaptive-rx-Modus im ENA-Treiber und Anpassungen an sysctl net.core.busy_read ermöglichten eine Leistungssteigerung um 28% (der Durchsatz stieg von 834k req/s auf 1,06M req/s, und die Latenzen sanken von 361μs auf 292μs).
- Deaktivierung von Systemdiensten, die zu überflüssigen Blockierungen im Netzwerk-Stack führen. Das manuelle Deaktivieren von dhclient und die entsprechende Einstellung IP-Adressen führte zu einer Leistungssteigerung von 6 %, während die Bandbreite von 1,06M req/s auf 1,12M req/s anstieg. Der Einfluss von dhclient auf die Leistung wurde in einer Verkehrsanalytik unter Verwendung von Raw-Sockets untersucht.
- Bekämpfung von Spin Lock. Die Umstellung des Netzwerk-Stacks in den Modus "noqueue" über sysctl "net.core.default_qdisc=noqueue" und "tc qdisc replace dev eth0 root mq" führte zu einer Leistungssteigerung von 2 %, während die Bandbreite von 1,12M req/s auf 1,15M req/s anstieg.
- Abschließende kleine Optimierungen, wie das Deaktivieren von GRO (Generic Receive Offload) mit dem Befehl "ethtool -K eth0 gro off" und die Umstellung des Staukontrollalgorithmus von cubic auf reno mittels sysctl "net.ipv4.tcp_congestion_control=reno". Die gesamte Leistungssteigerung betrug 4 %. Die Bandbreite stieg von 1,15M req/s auf 1,2M req/s.
Neben den durchgeführten Optimierungen werden in diesem Artikel auch Methoden behandelt, die nicht zu dem erwarteten Leistungszuwachs führten. Beispielsweise erwiesen sich folgende Maßnahmen als ineffektiv:
- Die separate Ausführung von libreactor wies keine Unterschiede in der Leistung im Vergleich zur Ausführung in einem Container auf. Die Änderung von writev auf send, die Erhöhung von maxevents in epoll_wait sowie Experimente mit GCC-Versionen und -Flags hatten keinen Einfluss (der Effekt war nur bei den Flags „-O3“ und „-march-native“ spürbar).
- Das Update des Linux-Kernels auf die Versionen 4.19 und 5.4, die Verwendung der Scheduler SCHED_FIFO und SCHED_RR sowie Manipulationen mit sysctl kernel.sched_min_granularity_ns, kernel.sched_wakeup_granularity_ns, transparent_hugepages=never, skew_tick=1 und clocksource=tsc hatten keinen Einfluss auf die Leistung.
- Im ENA-Treiber hatte die Aktivierung der Offload-Modi (Segmentation, Scatter-Gather, RX/TX-Checksumme), das Kompilieren mit dem Flag „-O3“ und die Anwendung der Parameter ena.rx_queue_size und ena.force_large_llq_header keinen Einfluss.
- Änderungen im Netzwerk-Stack führten nicht zu einer Leistungssteigerung:
- Deaktivierung von IPv6: ipv6.disable=1
- Deaktivierung von VLAN: modprobe -rv 8021q
- Deaktivierung der Überprüfung der Quelladresse von Paketen
- net.ipv4.conf.all.rp_filter=0
- net.ipv4.conf.eth0.rp_filter=0
- net.ipv4.conf.all.accept_local=1 (negativer Effekt)
- 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_PRIORITY
- TCP_NODELAY
Quelle: opennet.ru
