Оптимізація Linux для обробки 1.2 млн. JSON-запитів в секунду

Опубліковано детальний посібник з тюнінгу оточення Linux для досягнення максимальної продуктивності обробки HTTP-запитів. Запропоновані методи дозволили підняти продуктивність обробника JSON на основі бібліотеки libreactor в оточенні Amazon EC2 (4 vCPU) c 224 тисяч запитів API в секунду при штатних налаштуваннях Amazon Linux 2 з ядром 4.14 до 1.2 млн запитів в секунду після проведення оптимізації (приріст 436 а також призвели до скорочення затримок під час обробки запитів на 79%. Запропоновані методи не специфічні для libreactor і працюють при використанні інших http-серверів, включаючи nginx, Actix, Netty і Node.js (libreactor використовувався в тестах, оскільки рішення на його основі показало кращу продуктивність).

Оптимізація Linux для обробки 1.2 млн. JSON-запитів в секунду

Основні оптимізації:

  • Оптимізація коду libreactor. В якості основи був використаний варіант R18 з набору Techempower, який був покращений шляхом видалення коду для обмеження кількості ядер CPU, що використовуються (оптимізація дозволила прискорити роботу на 25-27%), збірки в GCC з опціями «-O3» (приріст 5-10% ) та «-march-native» (5-10%), заміни викликів read/write на recv/send (5-10%) та зниження накладних витрат при використанні pthreads (2-3%). Загальний приріст продуктивності після оптимізації коду становив 55%, а пропускна здатність зросла з 224k req/s до 347k req/s.
  • Вимкнення захисту від уразливостей, викликаних спекулятивним виконанням інструкцій. Використання параметрів під час завантаження ядра "nospectre_v1 nospectre_v2 pti=off mds=off tsx_async_abort=off" дозволило підняти продуктивність на 28%, а пропускна здатність зросла з 347k req/s до 446k req/s. Окремо приріст від параметра "nospectre_v1" (захист від Spectre v1 + SWAPGS) склав 1-2%, "nospectre_v2" (захист від Spectre v2) - 15-20%, "pti = off" (Spectre v3/Meltdown) - 6 %, "mds=off tsx_async_abort=off" (MDS/Zombieload і TSX Asynchronous Abort) - 6%. Залишено без зміни налаштування для захисту від атак L1TF/Foreshadow (l1tf=flush), iTLB multihit, Speculative Store Bypass і SRBDS, які не впливали на продуктивність, оскільки не перетиналися з конфігурацією, що тестується (наприклад, специфічні для KVM, вкладеної віртуалізації та інших моделей CPU).
  • Відключення механізмів аудиту та блокування системних викликів за допомогою команди «auditctl -a never,task» та вказівки опції «security-opt seccomp=unconfined» при запуску контейнера docker. Загальний приріст продуктивності становив 11%, а пропускна здатність зросла з 446k req/s до 495k req/s.
  • Відключення iptables/netfilter через розвантаження пов'язаних із ними модулів ядра. До ідеї відключити міжмережевий екран, який не використовувався у специфічному серверному рішенні, підштовхнули результати профілювання, судячи з яких виконання функції nf_hook_slow витрачало 18% часу. Відзначається, що nftables працює більш ефективно, ніж iptables, але в Amazon Linux продовжує використовувати iptables. Після відключення iptables приріст продуктивності становив 22%, а пропускна здатність зросла з 495k req/s до 603k req/s.
  • Зниження міграції обробників між різними ядрами CPU підвищення ефективності використання процесорного кеша. Оптимізація була проведена як на рівні прив'язки процесів libreactor до ядр CPU (CPU Pinning), так і через закріплення мережевих обробників ядра (Receive Side Scaling). Наприклад, виконано відключення irqbalance та явне виставлення прив'язок черг до CPU в /proc/irq/$IRQ/smp_affinity_list. Для використання одного ядра CPU для обробки процесу libreactor і мережевої черги вхідних пакетів задіяний власний BPF-обробник, підключений через установку прапора SO_ATTACH_REUSEPORT_CBPF при створенні сокету. Для прив'язки до CPU черг вихідних пакетів змінено налаштування /sys/class/net/eth0/queues/tx- /xps_cpus. Загальний приріст продуктивності становив 38%, а пропускна здатність зросла з 603k req/s до 834k req/s.
  • Оптимізація обробки переривань та використання полінгу (polling). Увімкнення режиму adaptive-rx у драйвері ENA та маніпуляції з sysctl net.core.busy_read дозволили підняти продуктивність на 28% (пропускна здатність зросла з 834k req/s до 1.06M req/s, а затримки знизилися з 361μs).
  • Відключення системних сервісів, що призводять до зайвих блокувань у мережевому стеку. Відключення dhclient та встановлення IP-адреси вручну призвели до підвищення продуктивності на 6%, а пропускна здатність зросла з 1.06M req/s до 1.12M req/s. Причина впливу dhclient на продуктивність в аналізі трафіку за допомогою raw-сокету.
  • Боротьба зі Spin Lock. Переведення мережевого стека в режим "noqueue" через sysctl "net.core.default_qdisc=noqueue" і "tc qdisc replace dev eth0 root mq" призвело до приросту продуктивності на 2%, а пропускна здатність зросла з 1.12M req/s до 1.15. req/s.
  • Фінальні дрібні оптимізації, такі як відключення GRO (Generic Receive Offload) командою "ethtool -K eth0 gro off" та заміна алгоритму контролю за навантаженням cubic на reno за допомогою sysctl "net.ipv4.tcp_congestion_control=reno". Загальний приріст продуктивності становив 4%. Пропускна спроможність зросла з 1.15M req/s до 1.2M req/s.

Крім оптимізацій, що працювали, у статті також розглядаються методи, які не призвели до очікуваного зростання продуктивності. Наприклад, неефективними виявились:

  • Окремий запуск libreactor не відрізнявся у продуктивності від запуску у контейнері. Не вплинули заміна writev на send, збільшення maxevents в epoll_wait, експерименти з версіями та прапорами GCC (ефект був помітний тільки для прапорів "-O3" і "-march-native").
  • Не вплинуло на продуктивність оновлення ядра Linux до версій 4.19 та 5.4, використання планувальників SCHED_FIFO та SCHED_RR, маніпуляції з sysctl kernel.sched_min_granularity_ns, kernel.sched_wakeup_granularity_ns, transparent_hu=
  • У драйвері ENA не вплинуло включення режимів Offload (segmentation, scatter-gather, rx/tx checksum), складання з прапором "-O3" та застосування параметрів ena.rx_queue_size та ena.force_large_llq_header.
  • Не спричинили підвищення продуктивності зміни в мережевому стеку:
    • Вимкнення IPv6: ipv6.disable=1
    • Вимкнення VLAN: modprobe -rv 8021q
    • Вимкнення перевірки джерела пакету
      • net.ipv4.conf.all.rp_filter=0
      • net.ipv4.conf.eth0.rp_filter=0
      • net.ipv4.conf.all.accept_local=1 (негативний ефект)
    • 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

    Джерело: opennet.ru

Додати коментар або відгук