Otimizando o Linux para lidar com 1.2 milhão de solicitações JSON por segundo

Foi publicado um guia detalhado sobre como ajustar o ambiente Linux para obter desempenho máximo no processamento de solicitações HTTP. Os métodos propostos permitiram aumentar o desempenho do processador JSON baseado na biblioteca libreator no ambiente Amazon EC2 (4 vCPU) de 224 mil solicitações de API por segundo com configurações padrão do Amazon Linux 2 com kernel 4.14 para 1.2 milhão de solicitações por segundo depois da otimização (um aumento de 436%), e também levou a uma redução de 79% nos atrasos no processamento de solicitações. Os métodos propostos não são específicos do libreator e funcionam ao utilizar outros servidores http, incluindo nginx, Actix, Netty e Node.js (o libreator foi utilizado nos testes porque a solução baseada nele apresentou melhor desempenho).

Otimizando o Linux para lidar com 1.2 milhão de solicitações JSON por segundo

Otimizações básicas:

  • Otimizando o código do libreator. Foi utilizada como base a opção R18 do kit Techempower, que foi aprimorada com a remoção de código para limitar o número de núcleos de CPU utilizados (a otimização permitiu acelerar o trabalho em 25-27%), montando em GCC com as opções “-O3” (um aumento de 5-10%) e "-march-native" (5-10%), substituindo chamadas de leitura/gravação por recv/send (5-10%) e reduzindo a sobrecarga ao usar pthreads (2-3%) . O aumento geral de desempenho após a otimização do código foi de 55% e a taxa de transferência aumentou de 224 mil req/s para 347 mil req/s.
  • Desative a proteção contra vulnerabilidades de execução especulativa. Usar os parâmetros “nospectre_v1 nospectre_v2 pti=off mds=off tsx_async_abort=off” ao carregar o kernel permitiu aumentar o desempenho em 28% e a taxa de transferência aumentou de 347k req/s para 446k req/s. Separadamente, o aumento do parâmetro “nospectre_v1” (proteção contra Spectre v1 + SWAPGS) foi de 1-2%, “nospectre_v2” (proteção contra Spectre v2) - 15-20%, "pti=off" (Spectre v3/Meltdown) - 6%, "mds=off tsx_async_abort=off" (MDS/Zombieload e TSX Asynchronous Abort) - 6%. As configurações de proteção contra ataques L1TF/Foreshadow (l1tf=flush), iTLB multihit, Speculative Store Bypass e SRBDS permaneceram inalteradas, o que não afetou o desempenho, uma vez que não cruzaram com a configuração testada (por exemplo, específico para KVM, aninhado virtualização e outros modelos de CPU).
  • Desativar mecanismos de auditoria e bloqueio de chamadas do sistema usando o comando "auditctl -a never,task" e especificando a opção "--security-opt seccomp = unconfined" ao iniciar o contêiner do docker. O aumento geral de desempenho foi de 11% e a taxa de transferência aumentou de 446 mil req/s para 495 mil req/s.
  • Desativando iptables/netfilter descarregando módulos de kernel associados. A ideia de desabilitar o firewall, que não era utilizado em uma solução de servidor específica, foi motivada pelos resultados do perfil, a julgar pelos quais a função nf_hook_slow levou 18% do tempo para ser executada. Observa-se que o nftables funciona com mais eficiência do que o iptables, mas o Amazon Linux continua a usar o iptables. Depois de desabilitar o iptables, o aumento de desempenho foi de 22% e a taxa de transferência aumentou de 495 mil req/s para 603 mil req/s.
  • Migração reduzida de manipuladores entre diferentes núcleos de CPU para melhorar a eficiência do uso do cache do processador. A otimização foi realizada tanto no nível de ligação dos processos do libreator aos núcleos da CPU (CPU Pinning) quanto através da fixação de manipuladores de rede do kernel (Receive Side Scaling). Por exemplo, o irqbalance foi desabilitado e a afinidade da fila com a CPU foi explicitamente definida em /proc/irq/$IRQ/smp_affinity_list. Para usar o mesmo núcleo da CPU para processar o processo libreator e a fila de rede de pacotes recebidos, um manipulador BPF customizado é usado, conectado pela configuração do sinalizador SO_ATTACH_REUSEPORT_CBPF ao criar o soquete. Para vincular filas de pacotes de saída à CPU, as configurações /sys/class/net/eth0/queues/tx- foram alteradas /xps_cpus. O aumento geral de desempenho foi de 38% e a taxa de transferência aumentou de 603 mil req/s para 834 mil req/s.
  • Otimização do tratamento de interrupções e uso de polling. Habilitar o modo adaptive-rx no driver ENA e manipular sysctl net.core.busy_read aumentou o desempenho em 28% (a taxa de transferência aumentou de 834k req/s para 1.06M req/s, e a latência diminuiu de 361μs para 292μs).
  • Desativar serviços do sistema que levam a bloqueios desnecessários na pilha de rede. Desabilitar o dhclient e configurar manualmente o endereço IP resultou em um aumento de desempenho de 6% e a taxa de transferência aumentou de 1.06M req/s para 1.12M req/s. A razão pela qual o dhclient afeta o desempenho está na análise de tráfego usando um soquete bruto.
  • Lutando contra o Spin Lock. Mudar a pilha de rede para o modo “noqueue” via sysctl “net.core.default_qdisc=noqueue” e “tc qdisc replace dev eth0 root mq” levou a um aumento de desempenho de 2% e a taxa de transferência aumentou de 1.12M req/s para 1.15M solicitação/s.
  • Pequenas otimizações finais, como desabilitar GRO (Generic Receive Offload) com o comando “ethtool -K eth0 gro off” e substituir o algoritmo de controle de congestionamento cúbico por reno usando sysctl “net.ipv4.tcp_congestion_control=reno”. O aumento geral da produtividade foi de 4%. A taxa de transferência aumentou de 1.15 milhões de req/s para 1.2 milhões de req/s.

Além das otimizações que funcionaram, o artigo também discute métodos que não levaram ao aumento de desempenho esperado. Por exemplo, o seguinte revelou-se ineficaz:

  • A execução do libreator separadamente não diferiu em desempenho da execução em um contêiner. Substituir writev por send, aumentar maxevents em epoll_wait e experimentar versões e sinalizadores do GCC não teve efeito (o efeito foi perceptível apenas para os sinalizadores “-O3” e “-march-native”).
  • A atualização do kernel Linux para as versões 4.19 e 5.4, usando os agendadores SCHED_FIFO e SCHED_RR, manipulando sysctl kernel.sched_min_granularity_ns, kernel.sched_wakeup_granularity_ns, transparente_hugepages=never, skew_tick=1 e clocksource=tsc não afetou o desempenho.
  • No driver ENA, ativar os modos Offload (segmentação, coleta de dispersão, soma de verificação rx/tx), construir com o sinalizador “-O3” e usar os parâmetros ena.rx_queue_size e ena.force_large_llq_header não teve efeito.
  • As alterações na pilha de rede não melhoraram o desempenho:
    • Desabilitar IPv6: ipv6.disable=1
    • Desative a VLAN: modprobe -rv 8021q
    • Desabilitar a verificação da origem do pacote
      • net.ipv4.conf.all.rp_filter=0
      • net.ipv4.conf.eth0.rp_filter=0
      • net.ipv4.conf.all.accept_local=1 (efeito negativo)
    • 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_PRIORIDADE
    • TCP_NODELAY

    Fonte: opennet.ru

Adicionar um comentário