Optymalizacja Linuksa do obsługi 1.2 miliona żądań JSON na sekundę

Opublikowano szczegółowy przewodnik na temat dostrajania środowiska Linux w celu osiągnięcia maksymalnej wydajności przetwarzania żądań HTTP. Zaproponowane metody pozwoliły zwiększyć wydajność procesora JSON opartego na bibliotece libreactor w środowisku Amazon EC2 (4 vCPU) z 224 tys. żądań API na sekundę przy standardowych ustawieniach Amazon Linux 2 z jądrem 4.14 do 1.2 mln żądań na sekundę drugie miejsce po optymalizacji (wzrost o 436%), a także doprowadziło do zmniejszenia opóźnień w przetwarzaniu wniosków o 79%. Zaproponowane metody nie są specyficzne dla libreactor i działają podczas korzystania z innych serwerów http, w tym nginx, Actix, Netty i Node.js (w testach wykorzystano libreactor, ponieważ oparte na nim rozwiązanie wykazało lepszą wydajność).

Optymalizacja Linuksa do obsługi 1.2 miliona żądań JSON na sekundę

Podstawowe optymalizacje:

  • Optymalizacja kodu libreactor. Jako podstawę wykorzystano opcję R18 z zestawu Techempower, którą udoskonalono poprzez usunięcie kodu ograniczającego liczbę wykorzystywanych rdzeni procesora (optymalizacja pozwoliła przyspieszyć pracę o 25-27%), asemblowanie w GCC z opcjami „-O3” (wzrost o 5-10%) i „-march-native” (5-10%), zastąpienie wywołań odczytu/zapisu przez recv/send (5-10%) i zmniejszenie narzutu podczas korzystania z pthreads (2-3%) . Ogólny wzrost wydajności po optymalizacji kodu wyniósł 55%, a przepustowość wzrosła z 224 tys. żądań/s do 347 tys. żądań/s.
  • Wyłącz ochronę przed lukami w zabezpieczeniach związanych ze spekulatywnym wykonaniem. Użycie parametrów „nospectre_v1 nospectre_v2 pti=off mds=off tsx_async_abort=off” podczas ładowania jądra pozwoliło zwiększyć wydajność o 28%, a przepustowość wzrosła z 347 tys. req/s do 446 tys. req/s. Oddzielnie wzrost 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%. Ustawienia ochrony przed atakami L1TF/Foreshadow (l1tf=flush), iTLB multihit, Speculative Store Bypass i SRBDS pozostawiono bez zmian, co nie miało wpływu na wydajność, gdyż nie kolidowały z testowaną konfiguracją (np. specyficzne dla KVM, zagnieżdżone wirtualizacja i inne modele procesorów).
  • Wyłączanie mechanizmów kontroli i blokowania wywołań systemowych za pomocą polecenia „auditctl -a Never,task” i określenie opcji „--security-opt seccomp=unconfined” podczas uruchamiania kontenera dokowanego. Ogólny wzrost wydajności wyniósł 11%, a przepustowość wzrosła z 446 tys. zapotrzebowań/s do 495 tys. zapotrzebowań/s.
  • Wyłączenie iptables/netfilter poprzez wyładowanie powiązanych modułów jądra. Pomysł wyłączenia firewalla, który nie był używany w konkretnym rozwiązaniu serwerowym, zrodził się z wyników profilowania, według których wykonanie funkcji nf_hook_slow zajęło 18% czasu. Należy zauważyć, że nftables działa wydajniej niż iptables, ale Amazon Linux nadal korzysta z iptables. Po wyłączeniu iptables wzrost wydajności wyniósł 22%, a przepustowość wzrosła z 495 tys. żądań/s do 603 tys. żądań/s.
  • Zmniejszona migracja procedur obsługi pomiędzy różnymi rdzeniami procesora w celu poprawy efektywności wykorzystania pamięci podręcznej procesora. Optymalizację przeprowadzono zarówno na poziomie powiązania procesów libreactora z rdzeniami procesora (CPU Pinning), jak i poprzez przypinanie procedur obsługi sieci jądra (Receive Side Scaling). Na przykład, irqbalance zostało wyłączone, a powinowactwo kolejki do procesora zostało jawnie ustawione w /proc/irq/$IRQ/smp_affinity_list. Aby używać tego samego rdzenia procesora do przetwarzania procesu libreactor i kolejki sieciowej przychodzących pakietów, używana jest niestandardowa procedura obsługi BPF, połączona poprzez ustawienie flagi SO_ATTACH_REUSEPORT_CBPF podczas tworzenia gniazda. Aby powiązać kolejki pakietów wychodzących z procesorem, zmieniono ustawienia /sys/class/net/eth0/queues/tx- /xps_cpus. Ogólny wzrost wydajności wyniósł 38%, a przepustowość wzrosła z 603 tys. zapotrzebowań/s do 834 tys. zapotrzebowań/s.
  • Optymalizacja obsługi przerwań i wykorzystania odpytywania. Włączenie trybu adaptive-rx w sterowniku ENA i manipulowanie sysctl net.core.busy_read zwiększyło wydajność o 28% (przepustowość wzrosła z 834 tys. req/s do 1.06 M req/s, a opóźnienie spadło z 361 μs do 292 μs).
  • Wyłączenie usług systemowych prowadzących do niepotrzebnego blokowania stosu sieciowego. Wyłączenie dhclient i ręczne ustawienie adresu IP spowodowało wzrost wydajności o 6%, a przepustowość wzrosła z 1.06 mln zapotrzebowań/s do 1.12 mln zapotrzebowań/s. Powodem, dla którego dhclient wpływa na wydajność, jest analiza ruchu przy użyciu surowego gniazda.
  • Walka z blokadą wirowania. Przełączenie stosu sieciowego w tryb „noqueue” za pomocą sysctl „net.core.default_qdisc=noqueue” i „tc qdisc zamiana dev eth0 root mq” doprowadziło do wzrostu wydajności o 2%, a przepustowość wzrosła z 1.12 mln wymagań/s do 1.15 mln zapotrzebowanie/s.
  • Ostatnie drobne optymalizacje, takie jak wyłączenie GRO (Generic Odbiór Offload) za pomocą polecenia „ethtool -K eth0 gro off” i zastąpienie algorytmu kontroli przeciążenia sześciennego reno przy użyciu sysctl „net.ipv4.tcp_congestion_control=reno”. Ogólny wzrost produktywności wyniósł 4%. Zwiększono przepustowość z 1.15 mln zapotrzebowań/s do 1.2 mln zapotrzebowań/s.

Oprócz optymalizacji, które zadziałały, w artykule omówiono także metody, które nie doprowadziły do ​​oczekiwanego wzrostu wydajności. Na przykład następujące okazały się nieskuteczne:

  • Oddzielne uruchomienie libreactora nie różniło się wydajnością od uruchomienia go w kontenerze. Zastąpienie writev przez send, zwiększenie maxevents w epoll_wait i eksperymentowanie z wersjami i flagami GCC nie przyniosło żadnego efektu (efekt był zauważalny tylko dla flag „-O3” i „-march-native”).
  • Aktualizacja jądra Linuksa do wersji 4.19 i 5.4, użycie harmonogramów SCHED_FIFO i SCHED_RR, manipulowanie sysctl kernel.sched_min_granularity_ns, kernel.sched_wakeup_granularity_ns, transparent_hugepages=never, skew_tick=1 i clocksource=tsc nie miało wpływu na wydajność.
  • W sterowniku ENA włączenie trybów Offload (segmentacja, scatter-gather, suma kontrolna rx/tx), budowanie z flagą „-O3” i używanie parametrów ena.rx_queue_size i ena.force_large_llq_header nie przyniosło efektu.
  • Zmiany w stosie sieciowym nie poprawiły wydajności:
    • Wyłącz IPv6: ipv6.disable=1
    • Wyłącz VLAN: modprobe -rv 8021q
    • Wyłącz sprawdzanie ź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 (efekt negatywny)
    • 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

    Źródło: opennet.ru

Dodaj komentarz