processes framework example ejemplo deploy python django uwsgi django-rest-framework

python - framework - Mal rendimiento de Django/uwsgi



uwsgi nginx python (3)

Estoy ejecutando una aplicación django con nginx y uwsgi. Así es como corro uwsgi:

sudo uwsgi -b 25000 --chdir=/www/python/apps/pyapp --module=wsgi:application --env DJANGO_SETTINGS_MODULE=settings --socket=/tmp/pyapp.socket --cheaper=8 --processes=16 --harakiri=10 --max-requests=5000 --vacuum --master --pidfile=/tmp/pyapp-master.pid --uid=220 --gid=499

& nginx configuraciones:

server { listen 80; server_name test.com root /www/python/apps/pyapp/; access_log /var/log/nginx/test.com.access.log; error_log /var/log/nginx/test.com.error.log; # https://docs.djangoproject.com/en/dev/howto/static-files/#serving-static-files-in-production location /static/ { alias /www/python/apps/pyapp/static/; expires 30d; } location /media/ { alias /www/python/apps/pyapp/media/; expires 30d; } location / { uwsgi_pass unix:///tmp/pyapp.socket; include uwsgi_params; proxy_read_timeout 120; } # what to serve if upstream is not available or crashes #error_page 500 502 503 504 /media/50x.html; }

Aquí viene el problema. Al hacer "ab" (ApacheBenchmark) en el servidor obtengo los siguientes resultados:

Versión nginx: versión nginx: nginx / 1.2.6

Versión uwsgi: 1.4.5

Server Software: nginx/1.0.15 Server Hostname: pycms.com Server Port: 80 Document Path: /api/nodes/mostviewed/8/?format=json Document Length: 8696 bytes Concurrency Level: 100 Time taken for tests: 41.232 seconds Complete requests: 1000 Failed requests: 0 Write errors: 0 Total transferred: 8866000 bytes HTML transferred: 8696000 bytes Requests per second: 24.25 [#/sec] (mean) Time per request: 4123.216 [ms] (mean) Time per request: 41.232 [ms] (mean, across all concurrent requests) Transfer rate: 209.99 [Kbytes/sec] received

Mientras se ejecuta en 500 nivel de concurrencia

oncurrency Level: 500 Time taken for tests: 2.175 seconds Complete requests: 1000 Failed requests: 50 (Connect: 0, Receive: 0, Length: 50, Exceptions: 0) Write errors: 0 Non-2xx responses: 950 Total transferred: 629200 bytes HTML transferred: 476300 bytes Requests per second: 459.81 [#/sec] (mean) Time per request: 1087.416 [ms] (mean) Time per request: 2.175 [ms] (mean, across all concurrent requests) Transfer rate: 282.53 [Kbytes/sec] received

Como puede ver ... todas las solicitudes en el servidor fallan con errores de tiempo de espera o "Cliente desconectado prematuramente" o:

writev(): Broken pipe [proto/uwsgi.c line 124] during GET /api/nodes/mostviewed/9/?format=json

Aquí hay un poco más sobre mi aplicación: Básicamente, es una colección de modelos que reflejan tablas MySQL que contienen todo el contenido. En la interfaz, tengo django-rest-framework que sirve contenido json a los clientes.

He instalado la barra de herramientas de depuración django-profiling & django para ver qué está pasando. En django-profiling, esto es lo que obtengo al ejecutar una única solicitud:

Instance wide RAM usage Partition of a set of 147315 objects. Total size = 20779408 bytes. Index Count % Size % Cumulative % Kind (class / dict of class) 0 63960 43 5726288 28 5726288 28 str 1 36887 25 3131112 15 8857400 43 tuple 2 2495 2 1500392 7 10357792 50 dict (no owner) 3 615 0 1397160 7 11754952 57 dict of module 4 1371 1 1236432 6 12991384 63 type 5 9974 7 1196880 6 14188264 68 function 6 8974 6 1076880 5 15265144 73 types.CodeType 7 1371 1 1014408 5 16279552 78 dict of type 8 2684 2 340640 2 16620192 80 list 9 382 0 328912 2 16949104 82 dict of class <607 more rows. Type e.g. ''_.more'' to view.> CPU Time for this request 11068 function calls (10158 primitive calls) in 0.064 CPU seconds Ordered by: cumulative time ncalls tottime percall cumtime percall filename:lineno(function) 1 0.000 0.000 0.064 0.064 /usr/lib/python2.6/site-packages/django/views/generic/base.py:44(view) 1 0.000 0.000 0.064 0.064 /usr/lib/python2.6/site-packages/django/views/decorators/csrf.py:76(wrapped_view) 1 0.000 0.000 0.064 0.064 /usr/lib/python2.6/site-packages/rest_framework/views.py:359(dispatch) 1 0.000 0.000 0.064 0.064 /usr/lib/python2.6/site-packages/rest_framework/generics.py:144(get) 1 0.000 0.000 0.064 0.064 /usr/lib/python2.6/site-packages/rest_framework/mixins.py:46(list) 1 0.000 0.000 0.038 0.038 /usr/lib/python2.6/site-packages/rest_framework/serializers.py:348(data) 21/1 0.000 0.000 0.038 0.038 /usr/lib/python2.6/site-packages/rest_framework/serializers.py:273(to_native) 21/1 0.000 0.000 0.038 0.038 /usr/lib/python2.6/site-packages/rest_framework/serializers.py:190(convert_object) 11/1 0.000 0.000 0.036 0.036 /usr/lib/python2.6/site-packages/rest_framework/serializers.py:303(field_to_native) 13/11 0.000 0.000 0.033 0.003 /usr/lib/python2.6/site-packages/django/db/models/query.py:92(__iter__) 3/1 0.000 0.000 0.033 0.033 /usr/lib/python2.6/site-packages/django/db/models/query.py:77(__len__) 4 0.000 0.000 0.030 0.008 /usr/lib/python2.6/site-packages/django/db/models/sql/compiler.py:794(execute_sql) 1 0.000 0.000 0.021 0.021 /usr/lib/python2.6/site-packages/django/views/generic/list.py:33(paginate_queryset) 1 0.000 0.000 0.021 0.021 /usr/lib/python2.6/site-packages/django/core/paginator.py:35(page) 1 0.000 0.000 0.020 0.020 /usr/lib/python2.6/site-packages/django/core/paginator.py:20(validate_number) 3 0.000 0.000 0.020 0.007 /usr/lib/python2.6/site-packages/django/core/paginator.py:57(_get_num_pages) 4 0.000 0.000 0.020 0.005 /usr/lib/python2.6/site-packages/django/core/paginator.py:44(_get_count) 1 0.000 0.000 0.020 0.020 /usr/lib/python2.6/site-packages/django/db/models/query.py:340(count) 1 0.000 0.000 0.020 0.020 /usr/lib/python2.6/site-packages/django/db/models/sql/query.py:394(get_count) 1 0.000 0.000 0.020 0.020 /usr/lib/python2.6/site-packages/django/db/models/query.py:568(_prefetch_related_objects) 1 0.000 0.000 0.020 0.020 /usr/lib/python2.6/site-packages/django/db/models/query.py:1596(prefetch_related_objects) 4 0.000 0.000 0.020 0.005 /usr/lib/python2.6/site-packages/django/db/backends/util.py:36(execute) 1 0.000 0.000 0.020 0.020 /usr/lib/python2.6/site-packages/django/db/models/sql/query.py:340(get_aggregation) 5 0.000 0.000 0.020 0.004 /usr/lib64/python2.6/site-packages/MySQLdb/cursors.py:136(execute) 2 0.000 0.000 0.020 0.010 /usr/lib/python2.6/site-packages/django/db/models/query.py:1748(prefetch_one_level) 4 0.000 0.000 0.020 0.005 /usr/lib/python2.6/site-packages/django/db/backends/mysql/base.py:112(execute) 5 0.000 0.000 0.019 0.004 /usr/lib64/python2.6/site-packages/MySQLdb/cursors.py:316(_query) 60 0.000 0.000 0.018 0.000 /usr/lib/python2.6/site-packages/django/db/models/query.py:231(iterator) 5 0.012 0.002 0.015 0.003 /usr/lib64/python2.6/site-packages/MySQLdb/cursors.py:278(_do_query) 60 0.000 0.000 0.013 0.000 /usr/lib/python2.6/site-packages/django/db/models/sql/compiler.py:751(results_iter) 30 0.000 0.000 0.010 0.000 /usr/lib/python2.6/site-packages/django/db/models/manager.py:115(all) 50 0.000 0.000 0.009 0.000 /usr/lib/python2.6/site-packages/django/db/models/query.py:870(_clone) 51 0.001 0.000 0.009 0.000 /usr/lib/python2.6/site-packages/django/db/models/sql/query.py:235(clone) 4 0.000 0.000 0.009 0.002 /usr/lib/python2.6/site-packages/django/db/backends/__init__.py:302(cursor) 4 0.000 0.000 0.008 0.002 /usr/lib/python2.6/site-packages/django/db/backends/mysql/base.py:361(_cursor) 1 0.000 0.000 0.008 0.008 /usr/lib64/python2.6/site-packages/MySQLdb/__init__.py:78(Connect) 910/208 0.003 0.000 0.008 0.000 /usr/lib64/python2.6/copy.py:144(deepcopy) 22 0.000 0.000 0.007 0.000 /usr/lib/python2.6/site-packages/django/db/models/query.py:619(filter) 22 0.000 0.000 0.007 0.000 /usr/lib/python2.6/site-packages/django/db/models/query.py:633(_filter_or_exclude) 20 0.000 0.000 0.005 0.000 /usr/lib/python2.6/site-packages/django/db/models/fields/related.py:560(get_query_set) 1 0.000 0.000 0.005 0.005 /usr/lib64/python2.6/site-packages/MySQLdb/connections.py:8()

..etc

Sin embargo, la barra de herramientas django-debug muestra lo siguiente:

Resource Usage Resource Value User CPU time 149.977 msec System CPU time 119.982 msec Total CPU time 269.959 msec Elapsed time 326.291 msec Context switches 11 voluntary, 40 involuntary and 5 queries in 27.1 ms

El problema es que "top" muestra que el promedio de carga aumenta rápidamente y el punto de referencia de apache que ejecuté tanto en el servidor local como desde una máquina remota dentro de la red muestra que no estoy atendiendo muchas solicitudes / segundo. ¿Cuál es el problema? Esto es lo más lejos que pude llegar al perfilar el código, por lo que sería de agradecer si alguien pudiera señalar lo que estoy haciendo aquí.

Edición (23/02/2013): Agregando más detalles basados ​​en la respuesta de Andrew Alcock: Los puntos que requieren mi atención / respuesta son (3) (3) He ejecutado "mostrar variables globales" en MySQL y descubrí que las configuraciones de MySQL tenía 151 para el ajuste de max_connections que es más que suficiente para servir a los trabajadores que estoy empezando para uwsgi.

(3) (4) (2) La solicitud única que estoy perfilando es la más pesada. Ejecuta 4 consultas de acuerdo con django-debug-toolbar. Lo que sucede es que todas las consultas se ejecutan en: 3.71, 2.83, 0.88, 4.84 ms respectivamente.

(4) ¿Aquí te refieres a la paginación de memoria? Si es así, ¿cómo podría saber?

(5) En 16 trabajadores, 100 tasa de concurrencia, 1000 solicitudes, el promedio de carga sube a ~ 12. Realicé las pruebas en diferentes números de trabajadores (el nivel de concurrencia es 100):

  1. 1 trabajador, carga promedio ~ 1.85, 19 requisitos / segundo, Tiempo por solicitud: 5229.520, 0 non-2xx
  2. 2 trabajadores, carga promedio ~ 1.5, 19 requisitos / segundo, tiempo por solicitud: 516.520, 0 no-2xx
  3. 4 trabajadores, carga promedio ~ 3, 16 requisitos / segundo, tiempo por solicitud: 5929.921, 0 no 2xx
  4. 8 trabajadores, carga promedio ~ 5, 18 requisitos / segundo, tiempo por solicitud: 5301.458, 0 non-2xx
  5. 16 trabajadores, carga promedio ~ 19, 15 requisitos / segundo, tiempo por solicitud: 6384.720, 0 non-2xx

Como puede ver, cuantos más trabajadores tenemos, más carga tenemos en el sistema. Puedo ver en el registro del daemon de uwsgi que el tiempo de respuesta en milisegundos aumenta cuando aumento el número de trabajadores.

En 16 trabajadores, ejecutando 500 solicitudes de nivel de concurrencia uwsgi comienza a registrar los errores:

writev(): Broken pipe [proto/uwsgi.c line 124]

La carga sube hasta ~ 10 también. y las pruebas no toman mucho tiempo porque las respuestas que no son 2xx son 923 de 1000, por lo que la respuesta aquí es bastante rápida ya que está casi vacía. Lo que también es una respuesta a su punto # 4 en el resumen.

Suponiendo que lo que estoy enfrentando aquí es una latencia del sistema operativo basada en E / S y redes, ¿cuál es la acción recomendada para escalar esto? nuevo hardware? servidor más grande?

Gracias


Agregar más trabajadores y obtener menos r / s significa que su solicitud "es CPU pura" y que no hay espera de IO que otro trabajador pueda usar para atender otra solicitud.

Si desea escalar, necesitará usar otro servidor con más cpu (o más rápido).

Sin embargo, esta es una prueba sintética, la cantidad de r / s que obtiene es el límite superior para la solicitud exacta que está probando, una vez en producción, hay muchas más variables que pueden afectar el rendimiento.


Ejecute los puntos de referencia durante más de un minuto (al menos 5-10). Realmente no obtendrá mucha información de una prueba tan breve. Y use el plugin de carbono de uWSGI para enviar las estadísticas al servidor de carbón / grafito (tendrá que tener uno), tendrá mucha más información para la depuración.

Cuando envía 500 solicitudes simultáneas a su aplicación y no puede manejar dicha carga, la cola de escucha en cada backend se llenará bastante rápido (son 100 solicitudes por defecto), es posible que desee aumentar eso, pero si los trabajadores no pueden procesar solicita que la cola de escucha rápida y (también conocida como acumulación) esté llena, la pila de la red de Linux eliminará la solicitud y comenzará a recibir errores.

Su primer punto de referencia indica que puede procesar una solicitud única en ~ 42 ms, por lo que un solo trabajador podría procesar a lo más 1000ms / 42ms = ~ 23 solicitudes por segundo (si la db y otras partes de la pila de aplicaciones no se ralentizan a medida que aumenta la concurrencia) . Entonces, para procesar 500 solicitudes simultáneas, necesitaría al menos 500/23 = 21 trabajadores (pero en realidad diría que al menos 40), tiene solo 16, no es de extrañar que se rompa bajo tal carga.

EDITAR: He mezclado la velocidad con la concurrencia: al menos 21 trabajadores le permitirán procesar 500 solicitudes por segundo, no 500 solicitudes simultáneas. Si realmente desea manejar 500 solicitudes simultáneas, simplemente necesita 500 trabajadores. A menos que ejecute su aplicación en modo asíncrono, consulte la sección "Gevent" en los documentos uWSGI.

PD. uWSGI viene con un excelente equilibrador de carga con configuración automática de servidor (lea los documentos en "Servidor de suscripción" y "FastRouter"). Puede configurarlo de una manera que le permita conectar en caliente el nuevo backend según sea necesario. Simplemente inicie los trabajadores en el nuevo nodo, se suscribirán a FastRouter y comenzarán a recibir solicitudes. Esta es la mejor manera de escalar horizontalmente. Y con backends en AWS Puede automatizar esto para que los nuevos backends se inicien rápidamente cuando sea necesario.


EDITAR 1 Visto el comentario de que tiene 1 núcleo virtual, agregando comentarios a través de todos los puntos relevantes

EDITAR 2 Más información de Maverick, así que estoy eliminando las ideas descartadas y desarrollando los problemas confirmados.

EDITAR 3 Complete más detalles sobre la cola de solicitud uwsgi y las opciones de escalado. Gramática mejorada.

EDITAR 4 Actualizaciones de Maverick y mejoras menores

Los comentarios son demasiado pequeños, así que aquí hay algunos pensamientos:

  1. El promedio de carga es básicamente cuántos procesos se están ejecutando o esperando la atención de la CPU. Para un sistema perfectamente cargado con 1 núcleo de CPU, el promedio de carga debe ser 1.0; para un sistema de 4 núcleos, debería ser 4.0. En el momento en que ejecuta la prueba web, los subprocesos de subprocesamiento y tiene muchos procesos en espera de CPU. A menos que el promedio de carga exceda el número de núcleos de CPU por un margen significativo, no es una preocupación
  2. El primer valor de "Tiempo por solicitud" de 4s se correlaciona con la longitud de la cola de solicitudes: 1000 solicitudes volcadas en Django casi instantáneamente y llevaron en promedio 4s al servicio, de los cuales aproximadamente 3,4 estaban esperando en una cola. Esto se debe a la gran falta de coincidencia entre el número de solicitudes (100) y el número de procesadores (16), lo que hace que 84 de las solicitudes estén a la espera de un procesador en cualquier momento.
  3. Con una concurrencia de 100, las pruebas tardan 41 segundos a 24 solicitudes / seg. Tiene 16 procesos (subprocesos), por lo que cada solicitud se procesa unos 700 ms. Dado su tipo de transacción, es mucho tiempo por solicitud. Esto puede ser porque:

    1. El costo de CPU de cada solicitud es alto en Django (lo cual es muy poco probable dado el bajo valor de CPU de la barra de herramientas de depuración)
    2. El sistema operativo es mucho el cambio de tareas (especialmente si el promedio de carga es superior a 4-8), y la latencia se reduce a tener demasiados procesos.
    3. No hay suficientes conexiones de base de datos al servicio de los 16 procesos, por lo que los procesos están a la espera de que haya uno disponible. ¿Tiene al menos una conexión disponible por proceso?
    4. Hay una latencia considerable alrededor del DB, ya sea :

      1. Decenas de pequeñas solicitudes, cada una de las cuales toma, digamos, 10 ms, la mayoría de las cuales son sobrecarga de red. Si es así, ¿puede introducir el almacenamiento en caché o reducir las llamadas de SQL a un número menor? O
      2. Una o un par de solicitudes están tomando 100 de ms. Para comprobar esto, ejecute el perfilado en la base de datos. Si es así, necesita optimizar esa solicitud.
  4. La división entre el sistema y el costo de la CPU del usuario es inusualmente alto en el sistema, aunque la CPU total es baja. Esto implica que la mayor parte del trabajo en Django está relacionado con el kernel, como las redes o el disco. En este escenario, podría tratarse de costos de red (por ejemplo, recibir y enviar solicitudes HTTP y recibir y enviar solicitudes a la base de datos). A veces esto será alto debido a la paginación . Si no se está realizando la paginación, entonces probablemente no tenga que preocuparse por esto en absoluto.

  5. Ha establecido los procesos en 16, pero tiene un alto promedio de carga (qué tan alto no indica) . Lo ideal es que siempre tenga al menos un proceso esperando la CPU (para que las CPU no funcionen de forma ociosa). Los procesos aquí no parecen vinculados a la CPU, pero tienen una latencia significativa, por lo que necesita más procesos que núcleos. ¿Cuántos más? Intente ejecutar uwsgi con diferentes números de procesadores (1, 2, 4, 8, 12, 16, 24, etc.) hasta obtener el mejor rendimiento. Si cambia la latencia del proceso promedio, deberá ajustar esto nuevamente.
  6. El nivel de concurrencia 500 definitivamente es un problema , pero ¿es el cliente o el servidor? El informe dice que 50 (de 100) tenían la longitud de contenido incorrecta, lo que implica un problema con el servidor. El no-2xx también parece apuntar allí. ¿Es posible capturar las respuestas no 2xx para la depuración? Los seguimientos de la pila o el mensaje de error específico serían increíblemente útiles (EDITAR) y se deben a que la cola de solicitudes uwsgi se ejecuta con su valor predeterminado de 100.

Entonces, en resumen:

  1. Django parece bien
  2. Falta de coincidencia entre la concurrencia de la prueba de carga (100 o 500) frente a los procesos (16): está insertando demasiadas solicitudes simultáneas en el sistema para la cantidad de procesos que deben manejarse. Una vez que esté por encima del número de procesos, todo lo que sucederá es que alargará la cola de solicitud HTTP en el servidor web
  3. Hay una gran latencia, por lo que tampoco

    1. No coincidencia entre los procesos (16) y los núcleos de la CPU (1): si el promedio de carga es> 3, es probable que haya demasiados procesos. Inténtalo de nuevo con un número menor de procesos.

      1. Carga promedio> 2 -> intente 8 procesos
      2. Carga promedio> 4 -> prueba 4 procesos
      3. Carga promedio> 8 -> intente 2 procesos
    2. Si el promedio de carga es <3, puede estar en la base de datos, así que haga un perfil de la base de datos para ver si hay un montón de solicitudes pequeñas (causantes de la latencia de forma aditiva) o una o dos declaraciones SQL son el problema

  4. Sin capturar la respuesta fallida, no hay mucho que pueda decir sobre las fallas en 500 concurrencias

Ideas en desarrollo

Sus promedios de carga> 10 en una máquina con un solo núcleo es realmente desagradable y (como puede observar) conlleva muchos cambios de tareas y un comportamiento lento en general. Personalmente no recuerdo haber visto una máquina con un promedio de carga de 19 (que tiene para 16 procesos) - felicitaciones por hacerlo tan alto;)

El rendimiento de la base de datos es excelente, por lo que le daría una idea clara en este momento.

Paginación : para responder su pregunta sobre cómo ver la paginación, puede detectar la paginación del sistema operativo de varias maneras. Por ejemplo, en la parte superior, el encabezado tiene entradas y salidas de página (consulte la última línea):

Processes: 170 total, 3 running, 4 stuck, 163 sleeping, 927 threads 15:06:31 Load Avg: 0.90, 1.19, 1.94 CPU usage: 1.37% user, 2.97% sys, 95.65% idle SharedLibs: 144M resident, 0B data, 24M linkedit. MemRegions: 31726 total, 2541M resident, 120M private, 817M shared. PhysMem: 1420M wired, 3548M active, 1703M inactive, 6671M used, 1514M free. VM: 392G vsize, 1286M framework vsize, 1534241(0) pageins, 0(0) pageouts. Networks: packets: 789684/288M in, 912863/482M out. Disks: 739807/15G read, 996745/24G written.

Número de procesos : en su configuración actual, el número de procesos es demasiado alto. Escala el número de procesos a 2 . Podríamos aumentar este valor más tarde, dependiendo de la carga adicional de este servidor.

Ubicación de Apache Benchmark : el promedio de carga de 1.85 para un proceso me sugiere que está ejecutando el generador de carga en la misma máquina que uwsgi, ¿es correcto?

Si es así, realmente necesita ejecutar esto desde otra máquina, de lo contrario, las ejecuciones de prueba no son representativas de la carga real; está tomando memoria y CPU de los procesos web para su uso en el generador de carga. Además, los 100 o 500 subprocesos del generador de carga generalmente harán hincapié en su servidor de una manera que no sucede en la vida real. De hecho, esta podría ser la razón por la que falla toda la prueba.

Ubicación de la base de datos : el promedio de carga para un proceso también sugiere que está ejecutando la base de datos en la misma máquina que los procesos web. ¿Es esto correcto?

Si estoy en lo cierto acerca de la base de datos, entonces la primera y mejor manera de comenzar a escalar es mover la base de datos a otra máquina. Hacemos esto por un par de razones:

  1. Un servidor de base de datos necesita un perfil de hardware diferente de un nodo de procesamiento:

    1. Disco: la base de datos necesita mucho disco de copia de seguridad, redundante y rápido, y un nodo de procesamiento solo necesita un disco básico
    2. CPU: un nodo de procesamiento necesita la CPU más rápida que pueda permitirse, mientras que una máquina de base de datos a menudo puede prescindir (a menudo su rendimiento está integrado en el disco y la RAM)
    3. RAM: una máquina de base de datos generalmente necesita la mayor cantidad de RAM posible (y la base de datos más rápida tiene todos sus datos en la RAM), mientras que muchos nodos de procesamiento necesitan mucho menos (el suyo necesita unos 20 MB por proceso, muy pequeño)
    4. Escalado: las bases de datos atómicas se escalan mejor al tener máquinas monstruosas con muchas CPU, mientras que el nivel web (sin estado) se puede escalar conectando muchos cuadros pequeños idénticos.
  2. Afinidad de CPU: es mejor que la CPU tenga un promedio de carga de 1.0 y que los procesos tengan afinidad a un solo núcleo. Al hacerlo, maximiza el uso de la memoria caché de la CPU y minimiza los gastos generales de conmutación de tareas. Al separar la base de datos y procesar los nodos, está aplicando esta afinidad en HW.

500 concurrencia con excepciones La cola de solicitud en el diagrama anterior es a lo sumo 100: si uwsgi recibe una solicitud cuando la cola está llena, la solicitud se rechaza con un error 5xx. Creo que esto sucedió en su prueba de carga de concurrencia 500 - básicamente la cola se llenó con los primeros 100 o más subprocesos, luego los otros 400 subprocesos emitieron las 900 solicitudes restantes y recibieron errores 5xx inmediatos.

Para manejar 500 solicitudes por segundo, debe asegurarse dos cosas:

  1. El tamaño de la cola de solicitud está configurado para manejar la ráfaga: use el argumento uwsgi para uwsgi
  2. El sistema puede manejar un rendimiento superior a 500 solicitudes por segundo si 500 es una condición normal, o un poco por debajo si 500 es un pico. Vea las notas de escala a continuación.

Me imagino que uwsgi tiene la cola configurada en un número más pequeño para manejar mejor los ataques DDoS; si se coloca bajo una carga enorme, la mayoría de las solicitudes fallan de inmediato y casi no se procesa, lo que permite que la caja en su conjunto siga respondiendo a los administradores.

Consejos generales para escalar un sistema.

Su consideración más importante es probablemente maximizar el rendimiento . Otra posible necesidad de minimizar el tiempo de respuesta, pero no discutiré esto aquí. Al maximizar el rendimiento, está intentando maximizar el sistema , no los componentes individuales; algunas reducciones locales podrían mejorar el rendimiento general del sistema (por ejemplo, hacer un cambio que ocurra para agregar latencia en el nivel web para mejorar el rendimiento del DB es una ganancia neta).

En específicos:

  1. Mueva el DB a una máquina separada . Después de esto, haga un perfil de la base de datos durante su prueba de carga ejecutando top y su herramienta de monitoreo MySQL favorita. Necesitas poder hacer un perfil. Mover la base de datos a una máquina separada introducirá un poco de latencia adicional (varios ms) por solicitud, por lo que espere aumentar ligeramente la cantidad de procesos en el nivel web para mantener el mismo rendimiento.
  2. Asegúrese de que la cola de solicitudes de uswgi sea ​​lo suficientemente grande como para manejar una ráfaga de tráfico usando el argumento --listen . Esto debería ser varias veces la cantidad máxima de solicitudes por segundo en estado estable que puede manejar su sistema.
  3. En el nivel web / aplicación: equilibre el número de procesos con el número de núcleos de CPU y la latencia inherente en el proceso. Demasiados procesos ralentizan el rendimiento, muy pocos significa que nunca utilizará completamente los recursos del sistema. No hay un punto de equilibrio fijo, ya que cada aplicación y patrón de uso es diferente, por lo tanto, haga una prueba y ajuste. Como guía, use la latencia de los procesos, si cada tarea tiene:

    • 0% de latencia, entonces necesita 1 proceso por núcleo
    • 50% de latencia (es decir, el tiempo de CPU es la mitad del tiempo real), entonces necesita 2 procesos por núcleo
    • 67% de latencia, entonces necesitas 3 procesos por núcleo
  4. Compruebe la top durante la prueba para asegurarse de que está por encima del 90% de utilización de la CPU (para cada núcleo) y que tiene un promedio de carga un poco por encima de 1.0. Si el promedio de carga es mayor, reduzca los procesos. Si todo va bien, en algún momento no podrá alcanzar este objetivo, y DB podría ser el cuello de botella.

  5. En algún momento necesitarás más poder en el nivel web. Puede elegir agregar más CPU a la máquina (relativamente fácil) y así agregar más procesos, o puede agregar más nodos de procesamiento (capacidad de ampliación horizontal). Lo último se puede lograr en uwsgi usando el método discutido here por Łukasz Mierzwa