Optimización: configuración del servidor web Nginx para mejorar el rendimiento de RPS en la API HTTP



Antes de escalar y escalar su infraestructura, el primer paso es asegurarse de que los recursos se utilicen correctamente y que la configuración de la aplicación no obstaculice su rendimiento. El objetivo principal del equipo de ingeniería es garantizar el funcionamiento continuo e ininterrumpido de cualquier sistema diseñado e implementado con recursos mínimos.



Nos enfrentamos al problema anterior en el que nuestro sistema implementado fue utilizado diariamente por un millón de usuarios que se conectaron en ráfagas de vez en cuando. Esto significa que implementar varios servidores o escalarlos no será la mejor solución en esta situación.



Este artículo trata sobre cómo ajustar Nginx para mejorar el rendimiento, es decir, para aumentar el RPS (solicitudes por segundo) en la API HTTP. Traté de contarles sobre la optimización que aplicamos en el sistema implementado para procesar decenas de miles de solicitudes por segundo sin desperdiciar una gran cantidad de recursos.



Plan de acción: necesita ejecutar la API HTTP (escrita en Python usando un matraz), proxy con Nginx; Se requiere un gran ancho de banda. El contenido de la API cambiará a intervalos de un día.



optimización del proceso

sustantivo



para lograr el mejor resultado; el uso más eficiente de una situación o recurso.


Usamos supervisor para iniciar WSGI Server con las siguientes configuraciones:



  • Gunicorn con trabajadores de Meinheld
  • Número de trabajadores: número de CPU * 2 + 1
  • Vincula el socket a una dirección Unix en lugar de a una IP, esto aumentará ligeramente la velocidad .


El comando de supervisor tiene este aspecto:



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


Intentamos optimizar la configuración de Nginx y verificamos qué funcionó mejor para nosotros.



Para evaluar el rendimiento de la API, usamos wrk con el siguiente comando:



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


Configuración predeterminada



Primero realizamos pruebas de carga de la API sin ningún cambio y obtuvimos las siguientes estadí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


Actualización de la configuración predeterminada



Actualicemos la configuración predeterminada de Nginx, es decir, nginx.conf en /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)



Después de los cambios, ejecutamos la verificación de configuración:



sudo nginx -t


Si la verificación se realiza correctamente, puede reiniciar Nginx para reflejar los cambios:



sudo service nginx restart


Con esta configuración, realizamos pruebas de carga de la API y obtuvimos el siguiente 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


Estas configuraciones redujeron los tiempos de espera y aumentaron las RPS (solicitudes por segundo), pero no mucho.



Agregar caché de Nginx



Dado que, en nuestro caso, el contenido del punto final se actualizará en un intervalo de un día, esto crea un entorno adecuado para almacenar en caché las respuestas de la API.



Pero agregar caché lo invalida ... esta es una de las dos dificultades aquí.

En informática, solo hay dos complicaciones: invalidar el caché y nombrar cosas. - Phil Carlton



Elegimos una solución simple para borrar el directorio de caché usando un cronjob después de actualizar el contenido en el sistema descendente.



A continuación, Nginx hará todo el trabajo duro, ¡pero ahora debemos estar seguros de que Nginx está 100% listo!



Para agregar almacenamiento en caché a Nginx, debe agregar varias directivas al archivo de configuración de Nginx.



Antes de eso, necesitamos crear un directorio para almacenar los datos de la caché:



sudo mkdir -p /data/nginx/cache


Cambios en la configuración de 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;
    }
    ...
}


Almacenamiento en caché de solicitudes de proxy (configuración de Nginx)



Después de este cambio de configuración, probamos la API y obtuvimos el siguiente 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


Por lo tanto, obtuvimos un aumento de casi 19 veces en el rendimiento al agregar almacenamiento en caché.

Nota de un experto de Timeweb :



Es importante recordar que las consultas de almacenamiento en caché que escriben en la base de datos darán como resultado una respuesta en caché, pero no escribirán en la base de datos.

Caché Nginx en RAM (memoria de acceso aleatorio)



¡Vamos un paso más allá! Actualmente, nuestros datos de caché se almacenan en disco. ¿Y si guardamos estos datos en la RAM? En nuestro caso, los datos de respuesta son limitados y no grandes.



Entonces, primero debe crear un directorio donde se montará la memoria caché de RAM:



sudo mkdir -p /data/nginx/ramcache


Para montar el directorio creado en RAM usando tmpfs , use el comando:



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


Esto monta / data / nginx / ramcache en RAM, asignando 256 MB.



Si cree que desea deshabilitar la memoria caché de RAM, simplemente ejecute el comando:



sudo umount /data/nginx/ramcache


Para volver a crear automáticamente el directorio de caché en la RAM después de reiniciar, necesitamos actualizar el archivo / etc / fstab . Agregue la siguiente línea:



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


Nota: También tenemos que registrar el valor de proxy_cache_path con la ruta a ramcache ( / data / nginx / ramcache ).



Después de actualizar la configuración, volvimos a realizar pruebas de carga de API y obtuvimos el siguiente 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


El almacenamiento de la caché en RAM resultó en una mejora significativa de casi 23 veces .



Registro de acceso en búfer



Mantenemos un registro de acceso a aplicaciones proxy, pero primero puede guardar el registro en un búfer y solo luego escribirlo en el disco:



  • si la siguiente línea del registro no cabe en el búfer
  • si los datos en el búfer son más antiguos que los especificados en el parámetro flush .


Este procedimiento reducirá la frecuencia de grabación realizada con cada solicitud. Para hacer esto, solo necesitamos agregar el búfer y vaciar los parámetros con el valor apropiado en la directiva 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 de búfer antes de



escribirse en el disco Por lo tanto, de acuerdo con la configuración anterior, inicialmente los registros de acceso se almacenarán en búfer y se guardarán en el disco sólo cuando el búfer alcance 256 KB o los datos almacenados en búfer tengan más de 10 segundos.



Nota: El nombre es log_format combinado aquí .



Después de repetidas pruebas de estrés, obtuvimos el siguiente 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


Esta configuración aumentó significativamente el número de solicitudes por segundo, unas 30 veces en comparación con la etapa inicial.



Salida



En este artículo, discutimos el proceso de optimización de la configuración de Nginx para mejorar el rendimiento de RPS. El RPS se ha aumentado de 1663 a ~ 50413 ( un aumento de aproximadamente 30 veces ), lo que proporciona un alto rendimiento. Al ajustar la configuración predeterminada, puede mejorar el rendimiento del sistema.



Terminemos el artículo con una cita:

Haz que funcione primero. Entonces hazlo bien. Luego optimice. - Kent Beck

Fuentes






All Articles