Ein detailliertes Handbuch zur Optimierung von Linux-Umgebungen zur Maximierung der Leistung bei der Verarbeitung von HTTP-Anfragen 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 mit Standardkonfiguration von Amazon Linux 2 mit Kernel 4.14 auf 1,2 Millionen Anfragen pro Sekunde nach der Optimierung (Zuwachs 436%) zu steigern, und führten außerdem zu einer Reduzierung der Latenzzeiten bei der Verarbeitung von Anfragen um 79%. Die vorgeschlagenen Methoden sind nicht spezifisch für libreactor und funktionieren auch mit anderen HTTP-Servern wie nginx, Actix, Netty und Node.js (libreactor wurde in den Tests verwendet, da die Lösung darauf basierend die beste Leistung zeigte).

Wesentliche Optimierungen:
- Optimierung des libreactor-Codes. Als Grundlage wurde die R18-Variante aus dem Techempower-Set verwendet, die durch das Entfernen des Codes zur Beschränkung der Anzahl der verwendeten CPU-Kerne verbessert wurde (diese Optimierung erlaubte eine Steigerung der Leistung um 25-27%), die Kompilierung in GCC mit den Optionen „-O3“ (Zuwachs 5-10%) und „-march-native“ (5-10%), der Austausch von read/write-Aufrufen durch recv/send (5-10%) und die Senkung der Kosten bei der Verwendung von pthreads (2-3%). Der Gesamte Leistungszuwachs nach der Code-Optimierung betrug 55%, und die Durchsatzrate stieg von 224.000 req/s auf 347.000 req/s.
- Deaktivierung des Schutzes vor Schwachstellen, die durch spekulative Ausführung von Instruktionen verursacht werden. Die Verwendung der Kernel-Boot-Parameter „nospectre_v1 nospectre_v2 pti=off mds=off tsx_async_abort=off“ ermöglichte eine Leistungssteigerung von 28%, und die Durchsatzrate stieg von 347.000 req/s auf 446.000 req/s. Einzelne Zuwächse durch den Parameter „nospectre_v1“ (Schutz vor Spectre v1 + SWAPGS) betrugen 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 Angriffen L1TF/Foreshadow (l1tf=flush), iTLB multihit, Speculative Store Bypass und SRBDS blieben unverändert, da sie keinen Einfluss auf die Leistung hatten, da sie nicht mit der getesteten Konfiguration in Konflikt standen (z. B. spezifisch für KVM, nested virtualization und andere CPU-Modelle).
- Deaktivierung von Audit-Mechanismen und der Sperre von Systemaufrufen mittels des Befehls „auditctl -a never,task“ und der Angabe der Option „—security-opt seccomp=unconfined“ beim Starten des Docker-Containers. Der Gesamte Leistungszuwachs betrug 11%, und die Durchsatzrate stieg von 446.000 req/s auf 495.000 req/s.
- Deaktivierung von iptables/netfilter durch das Entladen verwandter Kernel-Module. Die Idee, die Firewall zu deaktivieren, die in einer spezifischen Serverlösung nicht verwendet wurde, wurde durch Profilierungsergebnisse angestoßen, aus denen hervorging, dass 18 % der Zeit für die Ausführung der Funktion nf_hook_slow aufgewendet wurden. Es wird angemerkt, dass nftables effizienter arbeitet als iptables, jedoch weiterhin iptables in Amazon Linux verwendet wird. Nach der Deaktivierung von iptables betrug der Leistungszuwachs 22 %, und die Durchsatzrate erhöhte sich von 495k req/s auf 603k req/s.
- Verringerung der Migration von Prozessor-Handles zwischen verschiedenen CPU-Kernen zur Verbesserung der Effizienz der Nutzung des Prozessor-Caches. Die Optimierung wurde sowohl auf der Ebene der Prozessbindung von libreactor an CPU-Kerne (CPU Pinning) als auch durch die Bindung von Netzwerk-Handles an den Kern (Receive Side Scaling) vorgenommen. Beispielsweise wurde irqbalance deaktiviert und die Bindungen der Warteschlangen an die CPU in /proc/irq/$IRQ/smp_affinity_list explizit festgelegt. Um denselben CPU-Kern für die Verarbeitung von libreactor-Prozessen und der Netzwerk-Warteschlange für eingehende Pakete zu verwenden, wurde ein eigener BPF-Handler verwendet, der über das Setzen des Flags SO_ATTACH_REUSEPORT_CBPF beim Erstellen des Sockets angebunden wurde. Für die Bindung der Warteschlangen ausgehender Pakete wurden die Einstellungen in /sys/class/net/eth0/queues/tx-/xps_cpus geändert. Der gesamte Leistungszuwachs betrug 38 %, während die Durchsatzrate von 603k req/s auf 834k req/s anstieg.
- Optimierung der Interrupt-Verarbeitung und Nutzung von Polling. Die Aktivierung des adaptive-rx-Modus im ENA-Treiber und Manipulationen mit sysctl net.core.busy_read ermöglichten eine Leistungssteigerung von 28 % (die Durchsatzrate stieg von 834k req/s auf 1.06M req/s, und die Latenz sank von 361μs auf 292μs).
- Deaktivierung von Systemdiensten, die zu unnötigen Blockierungen im Netzwerkstack führen. Das Deaktivieren von dhclient und das manuelle Festlegen führte zu einer Leistungssteigerung von 6 %, während die Durchsatzrate von 1.06M req/s auf 1.12M req/s anstieg. Der Einfluss von dhclient auf die Leistung wurde durch die Analyse des Verkehrs mit einem Raw-Socket festgestellt. IP-Adressen Die Bekämpfung von Spin Locks. Die Umstellung des Netzwerkstacks in den „noqueue“-Modus über sysctl „net.core.default_qdisc=noqueue“ und „tc qdisc replace dev eth0 root mq“ führte zu einem Leistungszuwachs von 2 %, während die Durchsatzrate von 1.12M req/s auf 1.15M req/s anstieg.
- Die Bekämpfung von Spin Locks. Die Umstellung des Netzwerkstacks in den „noqueue“-Modus über sysctl „net.core.default_qdisc=noqueue“ und „tc qdisc replace dev eth0 root mq“ führte zu einem Leistungszuwachs von 2 %, während die Durchsatzrate von 1.12M req/s auf 1.15M req/s anstieg.
- Letzte feine Optimierungen, wie das Deaktivieren von GRO (Generic Receive Offload) mit dem Befehl „ethtool -K eth0 gro off“ und das Ändern des Überlastkontrollalgorithmus von cubic auf reno mit sysctl „net.ipv4.tcp_congestion_control=reno“. Der Gesamte Leistungszuwachs betrug 4 %. Die Durchsatzrate stieg von 1,15 M req/s auf 1,2 M req/s.
Neben den funktionierenden Optimierungen werden in dem Artikel auch Methoden diskutiert, die nicht zu dem erwarteten Anstieg der Leistung geführt haben. Beispielsweise erwiesen sich folgende Maßnahmen als ineffektiv:
- Ein separater Start von libreactor unterschied sich in der Leistung nicht vom Start in einem Container. Der Austausch von writev durch send hatte keinen Einfluss, die Erhöhung von maxevents in epoll_wait, Experimente mit Versionen und Flags von GCC (der Effekt war nur bei den Flags „-O3“ und „-march-native“ sichtbar).
- Das Aktualisieren des Linux-Kernels auf die Versionen 4.19 und 5.4 hatte keinen Einfluss auf die Leistung, ebenso wie die Verwendung der Scheduler SCHED_FIFO und SCHED_RR, Manipulationen mit sysctl kernel.sched_min_granularity_ns, kernel.sched_wakeup_granularity_ns, transparent_hugepages=never, skew_tick=1 und clocksource=tsc.
- Im ENA-Treiber hatte das Aktivieren der Offload-Modi (Segmentation, Scatter-Gather, rx/tx Checksumme), der Build mit dem Flag „-O3“ und die Verwendung der Parameter ena.rx_queue_size und ena.force_large_llq_header keinen Einfluss auf die Leistung.
- Änderungen im Netzwerk-Stack führten nicht zu einer Leistungssteigerung:
- Deaktivierung von IPv6: ipv6.disable=1
- Deaktivierung von VLAN: modprobe -rv 8021q
- Deaktivierung der Quellpaketprüfung
- 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
