Otimização: Configurando o servidor da web Nginx para melhorar o desempenho RPS na API HTTP



Antes de dimensionar e dimensionar sua infraestrutura, a primeira etapa é garantir que os recursos estejam sendo usados ​​corretamente e que a configuração do aplicativo não prejudique seu desempenho. O principal objetivo da equipe de engenharia é garantir a operação contínua e ininterrupta de qualquer sistema projetado e implantado com recursos mínimos.



Enfrentamos o problema acima, em que nosso sistema implantado era usado diariamente por um milhão de usuários que se conectavam em bursts de vez em quando. Isso significa que implantar vários servidores ou dimensioná-los não será a melhor solução nessa situação.



Este artigo é sobre como ajustar o Nginx para melhorar o desempenho, ou seja, aumentar o RPS (Solicitações por segundo) na API HTTP. Tentei falar sobre a otimização que aplicamos no sistema implantado para processar dezenas de milhares de solicitações por segundo sem desperdiçar uma grande quantidade de recursos.



Plano de ação: você precisa executar a API HTTP (escrita em Python usando flask), proxy com Nginx; alta largura de banda é necessária. O conteúdo da API mudará em intervalos de um dia.



otimização do processo

nominal



de obtenção do melhor resultado; o uso mais eficiente de uma situação ou recurso.


Usamos o supervisor para iniciar o servidor WSGI com as seguintes configurações:





O comando do supervisor se parece com isto:



gunicorn api:app --workers=5 --worker-
class=meinheld.gmeinheld.MeinheldWorker --bind=unix:api.sock


Tentamos otimizar a configuração do Nginx e verificamos o que funcionou melhor para nós.



Para avaliar o desempenho da API, usamos wrk com o seguinte comando:



wrk -t20 -c200 -d20s http://api.endpoint/resource


Configuração padrão



Primeiro, executamos o teste de carga da API sem nenhuma alteração e obtivemos as seguintes estatísticas:



Running 20s test @ http://api.endpoint/resource
  20 threads and 200 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   192.48ms  274.78ms   1.97s    87.18%
    Req/Sec    85.57     29.20   202.00     72.83%
  33329 requests in 20.03s, 29.59MB read
  Socket errors: connect 0, read 0, write 0, timeout 85
Requests/sec:   1663.71
Transfer/sec:      1.48MB


Atualizando a configuração padrão



Vamos atualizar a configuração padrão do Nginx, ou seja, nginx.conf em /etc/nginx/nginx.conf



worker_processes auto;
#or should be equal to the CPU core, you can use `grep processor /proc/cpuinfo | wc -l` to find; auto does it implicitly.

worker_connections 1024;
# default is 768; find optimum value for your server by `ulimit -n`

access_log off;
# to boost I/O on HDD we can disable access logs
# this prevent nginx from logging every action in a log file named `access.log`.

keepalive_timeout 15;
# default is 65;
# server will close connection after this time (in seconds)

gzip_vary on;
gzip_proxied any;
gzip_comp_level 2;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_min_length 256;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
# reduces the data that needs to be sent over the network
nginx.conf (/etc/nginx/nginx.conf)



Após as alterações, executamos a verificação de configuração:



sudo nginx -t


Se a verificação for bem-sucedida, você pode reiniciar o Nginx para refletir as alterações:



sudo service nginx restart


Com essa configuração, realizamos o teste de carga da API e obtivemos o seguinte resultado:



Running 20s test @ http://api.endpoint/resource
  20 threads and 200 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   145.80ms  237.97ms   1.95s    89.51%
    Req/Sec   107.99     41.34   202.00     66.09%
  42898 requests in 20.03s, 39.03MB read
  Socket errors: connect 0, read 0, write 0, timeout 46
  Non-2xx or 3xx responses: 2
Requests/sec:   2141.48
Transfer/sec:      1.95MB


Essas configurações reduziram os tempos limites e aumentaram o RPS (solicitações por segundo), mas não muito.



Adicionando Cache Nginx



Como, em nosso caso, o conteúdo do endpoint será atualizado em um intervalo de um dia, isso cria um ambiente adequado para o armazenamento em cache das respostas da API.



Mas adicionar cache o torna inválido ... esta é uma das duas dificuldades aqui.

Na ciência da computação, existem apenas duas complicações: invalidar o cache e nomear coisas. - Phil Carlton



Escolhemos uma solução simples para limpar o diretório de cache usando um cronjob depois de atualizar o conteúdo no sistema downstream.



Em seguida, o Nginx fará todo o trabalho difícil, mas agora precisamos ter certeza de que o Nginx está 100% pronto!



Para adicionar cache ao Nginx, você precisa adicionar várias diretivas ao arquivo de configuração do Nginx.



Antes disso, precisamos criar um diretório para armazenar os dados do cache:



sudo mkdir -p /data/nginx/cache


Mudanças na configuração do Nginx:



proxy_cache_path /data/nginx/cache keys_zone=my_zone:10m inactive=1d;
server {
    ...
    location /api-endpoint/ {
        proxy_cache my_zone;
        proxy_cache_key "$host$request_uri$http_authorization";
        proxy_cache_valid 404 302 1m;
        proxy_cache_valid 200 1d;
        add_header X-Cache-Status $upstream_cache_status;
    }
    ...
}


Solicitações por proxy de cache (configuração Nginx)



Após essa alteração na configuração, testamos o carregamento da API e obtivemos o seguinte resultado:



Running 20s test @ http://api.endpoint/resource
  20 threads and 200 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     6.88ms    5.44ms  88.91ms   81.36%
    Req/Sec     1.59k   500.04     2.95k    62.50%
  634405 requests in 20.06s, 589.86MB read
Requests/sec:  31624.93
Transfer/sec:     29.40MB


Assim, obtivemos um aumento de quase 19x no desempenho ao adicionar o cache.

Observação de um especialista do Timeweb :



É importante lembrar que as consultas de cache que gravam no banco de dados resultarão em uma resposta em cache, mas nenhuma gravação no banco de dados.

Cache Nginx em RAM (memória de acesso aleatório)



Vamos dar um passo adiante! Atualmente, nossos dados de cache são armazenados em disco. E se salvarmos esses dados na RAM? Em nosso caso, os dados de resposta são limitados e não grandes.



Portanto, primeiro você precisa criar um diretório onde o cache de RAM será montado:



sudo mkdir -p /data/nginx/ramcache


Para montar o diretório criado na RAM usando tmpfs , use o comando:



sudo mount -t tmpfs -o size=256M tmpfs /data/nginx/ramcache


Isso monta / data / nginx / ramcache na RAM, alocando 256 MB.



Se você acha que deseja desativar o cache de RAM, basta executar o comando:



sudo umount /data/nginx/ramcache


Para recriar automaticamente o diretório de cache na RAM após a reinicialização, precisamos atualizar o arquivo / etc / fstab . Adicione a seguinte linha a ele:



tmpfs /data/nginx/ramcache tmpfs defaults,size=256M 0 0


Observação: também temos que registrar o valor proxy_cache_path com o caminho para ramcache ( / data / nginx / ramcache ).



Depois de atualizar a configuração, realizamos novamente o teste de carga da API e obtivemos o seguinte resultado:



Running 20s test @ http://api.endpoint/resource
  20 threads and 200 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     5.57ms    5.69ms 277.76ms   92.94%
    Req/Sec     1.98k   403.94     4.55k    71.77%
  789306 requests in 20.04s, 733.89MB read
Requests/sec:  39387.13
Transfer/sec:     36.62MB


O armazenamento do cache na RAM resultou em uma melhoria significativa de quase 23 vezes .



Registro de acesso em buffer



Mantemos um registro de acesso aos aplicativos com proxy, mas você pode primeiro salvar o registro em um buffer e só depois gravá-lo no disco:



  • se a próxima linha do log não couber no buffer
  • se os dados no buffer forem mais antigos do que o especificado no parâmetro flush .


Este procedimento reduzirá a frequência de gravação realizada com cada solicitação. Para fazer isso, precisamos apenas adicionar os parâmetros buffer e flush com o valor apropriado na diretiva access_log :



location / {
    ...
    access_log /var/log/nginx/fast_api.log combined buffer=256k flush=10s;
    error_log /var/log/nginx/fast_api.err.log;
}






Registro do buffer antes de ser gravado no disco Assim, de acordo com a configuração acima, inicialmente os registros de acesso serão armazenados em buffer e salvos no disco somente quando o buffer atingir 256 KB ou os dados do buffer tiverem mais de 10 segundos.



Nota: o nome é log_format combinado aqui .



Após repetidos testes de estresse, obtivemos o seguinte resultado:



Running 20s test @ http://api.endpoint/resource
  20 threads and 200 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     4.21ms    3.19ms  84.83ms   83.84%
    Req/Sec     2.53k   379.87     6.02k    77.05%
  1009771 requests in 20.03s, 849.31MB read
Requests/sec:  50413.44
Transfer/sec:     42.40MB


Essa configuração aumentou significativamente o número de solicitações por segundo, cerca de 30 vezes em comparação com o estágio inicial.



Resultado



Neste artigo, discutimos o processo de otimização da configuração do Nginx para melhorar o desempenho do RPS. O RPS foi aumentado de 1663 para ~ 50413 ( um aumento de cerca de 30 vezes ), o que fornece alto rendimento. Ajustando as configurações padrão, você pode melhorar o desempenho do sistema.



Vamos terminar o artigo com uma citação:

Faça funcionar primeiro. Então faça certo. Em seguida, otimize. - Kent Beck

Fontes






All Articles