Opublikowano szczegółowy przewodnik po optymalizacji środowiska Linux w celu osiągnięcia maksymalnej wydajności przetwarzania żądań HTTP. Proponowane metody umożliwiły zwiększenie wydajności handlera JSON opartego na bibliotece libreactor w środowisku Amazon EC2 (4 vCPU) z 224 tysięcy żądań API na sekundę przy domyślnych ustawieniach Amazon Linux 2 z jądrem 4.14 do 1,2 miliona żądań na sekundę po przeprowadzeniu optymalizacji (wzrost o 436%), a także doprowadziły do redukcji opóźnień przy przetwarzaniu żądań o 79%. Proponowane metody nie są specyficzne dla libreactor i działają przy użyciu innych serwerów http, w tym nginx, Actix, Netty i Node.js (libreactor był używany w testach, ponieważ rozwiązanie na jego podstawie wykazało najlepszą wydajność).

Główne optymalizacje:
- Optymalizacja kodu libreactor. Jako podstawę wykorzystano wersję R18 z zestawu Techempower, która została poprawiona poprzez usunięcie kodu ograniczającego liczbę używanych rdzeni CPU (optymalizacja pozwoliła przyspieszyć działanie o 25-27%), kompilację w GCC z opcjami „-O3” (wzrost 5-10%) oraz „-march-native” (5-10%), zamianę wywołań read/write na recv/send (5-10%) oraz zmniejszenie narzuconych kosztów przy użyciu pthreads (2-3%). Ogólny wzrost wydajności po optymalizacji kodu wyniósł 55%, a przepustowość zwiększyła się z 224k req/s do 347k req/s.
- Wyłączenie zabezpieczeń przed lukami, spowodowanymi przez spekulatywne wykonanie instrukcji. Użycie parametrów podczas ładowania jądra „nospectre_v1 nospectre_v2 pti=off mds=off tsx_async_abort=off” pozwoliło zwiększyć wydajność o 28%, a przepustowość wzrosła z 347k req/s do 446k req/s. W szczególności przyrost z parametru „nospectre_v1” (ochrona przed Spectre v1 + SWAPGS) wyniósł 1-2%, „nospectre_v2” (ochrona przed Spectre v2) — 15-20%, „pti=off” (Spectre v3/Meltdown) — 6%, „mds=off tsx_async_abort=off” (MDS/Zombieload i TSX Asynchronous Abort) — 6%. Pozostawiono bez zmian ustawienia ochrony przed atakami L1TF/Foreshadow (l1tf=flush), iTLB multihit, Speculative Store Bypass oraz SRBDS, które nie wpływały na wydajność, ponieważ nie kolidowały z testowaną konfiguracją (na przykład specyficzne dla KVM, zagnieżdżonej wirtualizacji i innych modeli CPU).
- Wyłączenie mechanizmów audytu i blokowania wywołań systemowych przy pomocy polecenia „auditctl -a never,task” oraz wskazania opcji „—security-opt seccomp=unconfined” podczas uruchamiania kontenera docker. Ogólny wzrost wydajności wyniósł 11%, a przepustowość zwiększyła się z 446k req/s do 495k req/s.
- Dezaktywacja iptables/netfilter przez załadowanie powiązanych z nimi modułów jądra. Pomysł na dezaktywację zapory, która nie była używana w specyficznym rozwiązaniu serwerowym, został zainspirowany wynikami profilowania, z których wynika, że funkcja nf_hook_slow zajmowała 18% czasu. Zauważono, że nftables działa bardziej efektywnie niż iptables, ale w Amazon Linux nadal używa się iptables. Po dezaktywacji iptables wzrost wydajności wyniósł 22%, a przepustowość zwiększyła się z 495k req/s do 603k req/s.
- Ograniczenie migracji handlerów pomiędzy różnymi rdzeniami CPU w celu zwiększenia efektywności wykorzystania pamięci podręcznej procesora. Optymalizacja została przeprowadzona zarówno na poziomie przypisania procesów libreactor do rdzeni CPU (CPU Pinning), jak i przez przypisanie sieciowych handlerów jądra (Receive Side Scaling). Na przykład, wykonano dezaktywację irqbalance oraz jawne ustawienie przypisań kolejek do CPU w /proc/irq/$IRQ/smp_affinity_list. Aby użyć tego samego rdzenia CPU do przetwarzania procesu libreactor i sieciowej kolejki przychodzących pakietów, zastosowano własny handler BPF, podłączony przez ustawienie flagi SO_ATTACH_REUSEPORT_CBPF przy tworzeniu gniazda. Dla przypisania do CPU kolejek pakietów wychodzących zmieniono ustawienia /sys/class/net/eth0/queues/tx-/xps_cpus. Całkowity wzrost wydajności wyniósł 38%, a przepustowość zwiększyła się z 603k req/s do 834k req/s.
- Optymalizacja obsługi przerwań oraz wykorzystanie polowania (polling). Włączenie trybu adaptive-rx w sterowniku ENA oraz manipulacje z sysctl net.core.busy_read umożliwiły zwiększenie wydajności o 28% (przepustowość wzrosła z 834k req/s do 1.06M req/s, a opóźnienia zmniejszyły się z 361μs do 292μs).
- Dezaktywacja systemowych usług, prowadzących do zbędnych blokad w stosie sieciowym. Dezaktywacja dhclient oraz ustawienie adresy IP ręcznie doprowadziło do wzrostu wydajności o 6%, a przepustowość wzrosła z 1.06M req/s do 1.12M req/s. Powód wpływu dhclient na wydajność w analizie ruchu z użyciem raw-socket.
- Walcząc z Spin Lock. Przeniesienie stosu sieciowego w tryb „noqueue” przez sysctl „net.core.default_qdisc=noqueue” oraz „tc qdisc replace dev eth0 root mq” doprowadziło do wzrostu wydajności o 2%, a przepustowość wzrosła z 1.12M req/s do 1.15M req/s.
- Ostateczne drobne optymalizacje, takie jak wyłączenie GRO (Generic Receive Offload) poleceniem „ethtool -K eth0 gro off” oraz zmiana algorytmu kontroli przeciążenia z cubic na reno za pomocą sysctl „net.ipv4.tcp_congestion_control=reno”. Całkowity wzrost wydajności wyniósł 4%. Przepustowość wzrosła z 1,15M req/s do 1,2M req/s.
Oprócz zastosowanych optymalizacji, w artykule omówiono także metody, które nie przyniosły oczekiwanego wzrostu wydajności. Na przykład, następujące okazały się nieefektywne:
- Oddzielne uruchomienie libreactor nie różniło się wydajnością od uruchomienia w kontenerze. Nie wpłynęły na wydajność zmiany writev na send, zwiększenie maxevents w epoll_wait, eksperymenty z wersjami i flagami GCC (efekt był widoczny tylko dla flag „-O3” i „-march-native”).
- Nie wpłynęło na wydajność zaktualizowanie jądra Linux do wersji 4.19 i 5.4, użycie planistów SCHED_FIFO i SCHED_RR, manipulacje z sysctl kernel.sched_min_granularity_ns, kernel.sched_wakeup_granularity_ns, transparent_hugepages=never, skew_tick=1 oraz clocksource=tsc.
- W sterowniku ENA nie miało wpływu włączenie trybów Offload (segmentation, scatter-gather, rx/tx checksum), kompilacja z flagą „-O3” oraz zastosowanie parametrów ena.rx_queue_size i ena.force_large_llq_header.
- Nie przyniosły wzrostu wydajności zmiany w stosie sieciowym:
- Wyłączenie IPv6: ipv6.disable=1
- Wyłączenie VLAN: modprobe -rv 8021q
- Wyłączenie sprawdzania źródła pakietu
- net.ipv4.conf.all.rp_filter=0
- net.ipv4.conf.eth0.rp_filter=0
- net.ipv4.conf.all.accept_local=1 (negatywny efekt)
- 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
- Opublikowano szczegółowy przewodnik po stroju środowiska Linux w celu osiągnięcia maksymalnej wydajności przetwarzania żądań HTTP.
Źródło: opennet.ru
