Problemas de tiempo de espera del servidor Node.js (EC2 + Express + PM2)

Soy relativamente nuevo en la ejecución de aplicaciones de producción node.js y recientemente tuve problemas con el tiempo de espera de mi servidor.

Básicamente, después de una cierta cantidad de uso y tiempo, mi aplicación node.js deja de responder a las solicitudes. Ya ni siquiera veo las rutas que se activan en mi consola: es como si todo se detuviera y las llamadas HTTP de mi cliente (iPhone con AFNetworking) ya no llegan al servidor. Pero si reinicio mi servidor de aplicaciones node.js, todo vuelve a funcionar, hasta que las cosas inevitablemente se detengan de nuevo. La aplicación nunca falla, solo deja de responder a las solicitudes.

No recibo ningún error y me he asegurado de manejar y registrar todos los errores de conexión de la base de datos, por lo que no estoy seguro de por dónde empezar. Pensé que podría tener algo que ver con las memory leaks, así que instalé node-memwatch y configuré un servicio de escucha para las memory leaks, pero eso no se realiza antes de que mi servidor deje de responder a las solicitudes.

¿Alguna pista sobre lo que podría estar pasando y cómo puedo resolver este problema?

Aquí está mi stack:

  • Node.js en AWS EC2 Micro Instance (utilizando Express 4.0 + PM2)
  • Base de datos en el volumen AWS RDS que ejecuta MySQL (usando node-mysql)
  • Sesiones almacenadas con Redis en la misma instancia de EC2 que la aplicación node.js
  • Los clientes son iPhones que acceden al servidor a través de AFNetworking

Una vez más, no se disparan errores con ninguno de los módulos mencionados anteriormente.

En primer lugar, debe ser un poco más específico acerca de los tiempos de espera.

  • Tiempos de espera de TCP : TCP divide un mensaje en paquetes que se envían uno por uno. El receptor debe reconocer haber recibido el paquete. Si el receptor no reconoce haber recibido el paquete dentro de cierto período de tiempo, se produce una retransmisión de TCP, que está enviando nuevamente el mismo paquete. Si esto sucede un par de veces más, el remitente se da por vencido y elimina la conexión.

  • Tiempo de espera HTTP : un cliente HTTP como un navegador, o su servidor mientras actúa como cliente (por ejemplo, enviando solicitudes a otros servidores HTTP), puede establecer un tiempo de espera arbitrario. Si no se recibe una respuesta dentro de ese período de tiempo, se desconectará y lo llamará un tiempo de espera.

Ahora, hay muchas, muchas causas posibles para esto … desde más trivial a menos trivial:

  • Cálculo incorrecto de la longitud del contenido : si envías una solicitud con un encabezado Content-Length: 20 , significa “Te enviaré 20 bytes”. Si envía 19, el otro extremo esperará el 1. restante. Si eso toma demasiado tiempo … tiempo de espera.

  • No hay suficiente infraestructura : tal vez debería asignar más máquinas a su aplicación. Si (total load / # of CPU cores) es superior a 1, o el uso de la memoria es alto, es posible que su sistema tenga un exceso de capacidad. Sin embargo sigue leyendo …

  • Excepción silenciosa : se produjo un error pero no se registró en ningún lugar. La solicitud nunca terminó de procesarse, lo que lleva al siguiente elemento.

  • Fugas de recursos : cada solicitud debe manejarse hasta su finalización. Si no haces esto, la conexión permanecerá abierta. Además, el objeto IncomingMesage (también conocido como: generalmente llamado req en código expreso) seguirá siendo referenciado por otros objetos (por ejemplo: expressse a sí mismo). Cada uno de esos objetos puede usar mucha memoria.

  • Suceso de bucle de evento de nodo : llegaré a eso al final.


Para las memory leaks, los síntomas serían: el proceso del nodo usaría una cantidad creciente de memoria.

Para empeorar las cosas, si la memoria disponible es baja y su servidor está mal configurado para usar el intercambio, Linux comenzará a mover la memoria al disco (intercambio), que requiere mucha E / S y CPU. Los servidores no deberían tener habilitado el intercambio.

 cat /proc/sys/vm/swappiness 

le devolverá el nivel de swappiness configurado en su sistema (va de 0 a 100). Puede modificarlo de forma persistente a través de /etc/sysctl.conf (requiere reinicio) o de forma volátil usando: sysctl vm.swappiness=10

Una vez que haya establecido que tiene una pérdida de memoria, debe obtener un volcado del núcleo y descargarlo para su análisis. Se puede encontrar una forma de hacerlo en esta otra respuesta de Stackoverflow: herramientas para analizar el volcado del núcleo desde Node.js

Para las fugas de conexión (usted filtró una conexión al no manejar una solicitud hasta su finalización), tendría un número creciente de conexiones establecidas con su servidor. Puede verificar sus conexiones establecidas con netstat -a -p tcp | grep ESTABLISHED | wc -l netstat -a -p tcp | grep ESTABLISHED | wc -l netstat -a -p tcp | grep ESTABLISHED | wc -l puede usarse para contar conexiones establecidas.

Ahora, el evento de la inanición del bucle es el peor problema. Si tienes un nodo de código de corta duración funciona muy bien. Pero si realiza tareas de uso intensivo de la CPU y tiene una función que mantiene a la CPU ocupada durante un período de tiempo excesivo … como 50 ms (50 ms de sólido, locking, tiempo de CPU síncrono, sin código asíncrono que toma 50 ms), las operaciones son manejado por el bucle de eventos, como el procesamiento de solicitudes HTTP, comienza a retrasarse y eventualmente se agota.

La forma de encontrar un cuello de botella en la CPU es usar un perfilador de rendimiento. nodegrind / qcachegrind son mis herramientas preferidas de creación de perfiles, pero otros prefieren flamegraphs y demás. Sin embargo, puede ser difícil ejecutar un perfilador en producción. Simplemente tome un servidor de desarrollo y slam con solicitudes. aka: una prueba de carga. Hay muchas herramientas para esto.


Finalmente, otra forma de depurar el problema es:

env NODE_DEBUG=tls,net node <...arguments for your app>

El nodo tiene instrucciones de depuración opcionales que se habilitan a través de la variable de entorno NODE_DEBUG . Al establecer NODE_DEBUG en tls,net hará que el nodo emita información de depuración para los módulos tls y net … así que básicamente todo se envía o se recibe. Si hay un tiempo de espera, verás de dónde viene.

Fuente: Experiencia de mantener grandes despliegues de servicios de nodo durante años.