Optimización del rendimiento de Node.js para el alojamiento VPS en producción

Optimización del rendimiento de Node.js para el alojamiento VPS en producción - Imagen principal

Los problemas de rendimiento de Node.js en producción casi siempre se deben a las mismas causas: una aplicación de un solo proceso asignada a un núcleo de CPU, la falta de capas de almacenamiento en caché, un bucle de eventos bloqueado por tareas sincrónicas y un gestor de procesos que se cuelga sin avisar a las 2 de la madrugada.

Este artículo trata sobre los ajustes que realmente mejoran la latencia del p95 y la capacidad de solicitudes simultáneas en un VPS de producción, con patrones de configuración que se mantienen bajo tráfico real.

Por qué es importante optimizar el rendimiento de Node.js en un VPS de producción

Un VPS te ofrece acceso de root, asignación de vCPU dedicada y control de procesos persistentes, algo que el alojamiento compartido no puede ofrecer, que es justo lo que necesita un proceso de Node.js de larga duración. La configuración predeterminada de `server.js` en Node.js ejecuta tu aplicación como un único proceso en un solo subproceso, por lo que un VPS de 4 vCPU que ejecute una aplicación sin optimizar utiliza aproximadamente el 25 % del hardware por el que estás pagando. La optimización reduce esa diferencia.

La otra razón para optimizar en la capa del VPS es que Node.js está diseñado para que el código de la aplicación se ejecute en un solo subproceso. El entorno de ejecución utiliza un bucle de eventos y un grupo de subprocesos de libuv para gestionar la E/S, pero cualquier tarea que consuma recursos de la CPU que escribas seguirá bloqueando todas las solicitudes en ese subproceso. La optimización en producción consiste principalmente en sacar las tareas que consumen recursos de la CPU del bucle de eventos. También implica colocar capas más ligeras delante de Node para que el entorno de ejecución solo se ocupe de lo que debe.

¿Cuáles son los cuellos de botella más comunes en el rendimiento de Node.js?

Los problemas reales de producción se concentran en unas pocas causas fundamentales:

  • Implementaciones de un solo proceso. Un proceso de One Node no puede utilizar más de un núcleo de CPU para el código de la aplicación, por lo que un VPS multinúcleo queda inactivo cuando hay carga.
  • Bucle de eventos bloqueado. Las lecturas de archivos sincrónicas , el uso de `JSON.parse` con cargas de datos grandes, el hash bcrypt en el hilo principal o el bloqueo indefinido de expresiones regulares detienen todas las solicitudes simultáneas.
  • Las fugas de memoria provocadas por referencias retenidas. Los cierres de larga duración , las cachés en memoria que crecen sin que se eliminen elementos antiguos y los detectores de eventos asociados sin limpieza hacen que el uso del montón supere silenciosamente el límite predeterminado de 1,5 GB.
  • No hay almacenamiento en caché a nivel HTTP. Todas las solicitudes pasan por el código de la aplicación, incluso las respuestas que cambian cada hora.
  • Exposición directa a Internet. Si ejecutas un nodo en el puerto 80 o 443 sin Nginx , la terminación TLS, el servicio de archivos estáticos y el almacenamiento en búfer de los clientes lentos recaerán en tu aplicación.
  • Las idas y venidas a la base de datos en la ruta principal. La falta de índices y las consultas N+1 se detectan como problemas de rendimiento del nodo, aunque el tiempo real se dedica a esperar a la base de datos.

Para saber cuál es el problema, hay que medir, no adivinar. Empieza por comprobar el retraso del bucle de eventos y el uso del montón antes de cambiar nada.

¿Cómo se configura correctamente el clúster de Node.js y el número de trabajadores?

El patrón de clúster ejecuta un proceso Node por cada núcleo de CPU, con un proceso maestro que distribuye las conexiones entre los procesos de trabajo. El módulo de clúster de Node.js está integrado en el entorno de ejecución y es la base que utilizan PM2 y la mayoría de los gestores de procesos en segundo plano.

La regla general:

  • Cargas de trabajo limitadas por la CPU o equilibradas: número de trabajadores = número de vCPU. En un VPS de 4 vCPU, ejecuta 4 trabajadores.
  • Cargas de trabajo con gran volumen de E/S: la regla «trabajadores = vCPU» sigue siendo el punto de partida adecuado. Añadir más rara vez ayuda, porque el cuello de botella es la base de datos o la API externa, no Node.
  • Planes de VPS con memoria limitada: número de trabajadores = floor(RAM disponible / montón por trabajador). Si cada trabajador ocupa 400 MB de montón y te quedan 2 GB libres después del sistema operativo, el máximo son cuatro trabajadores, independientemente del número de núcleos.

Con PM2, esto se configura de forma declarativa:

pm2 start app.js -i max –name api

En -i max La opción genera un proceso de trabajo por cada núcleo disponible. Usa un número concreto, como -i 4, cuando quieras dejar margen para un proceso de base de datos o de caché en el mismo VPS.

¿Qué ajustes de PM2 y Process Manager mejoran la estabilidad?

PM2 es el gestor de procesos de producción más habitual para Node, y los ajustes predeterminados no son los que necesitas a gran escala. Un sistema listo para producción ecosystem.config.js se parece más a esto:

module.exports = {
  apps: [{
    name: ‘api’,
    script: ‘./server.js’,
    instances: ‘max’,
    exec_mode: ‘cluster’,
    max_memory_restart: ‘500M’,
    node_args: ‘–max-old-space-size=460’,
    env_production: {
      NODE_ENV: ‘production’,
      PORT: 3000
    },
    error_file: ‘/var/log/pm2/api-err.log’,
    out_file: ‘/var/log/pm2/api-out.log’,
    merge_logs: true,
    time: true
  }]
};

Algunos detalles importantes en la producción:

  • max_memory_restart provoca un reinicio controlado antes de que un proceso de trabajo alcance el límite de memoria del montón de V8 y sea eliminado por el OOM Killer del sistema operativo. Configúralo entre un 5 y un 10 por ciento por debajo --max-old-space-size.
  • exec_mode: cluster es lo que realmente permite el equilibrio de carga entre los trabajadores. El modo «fork» ejecuta procesos independientes sin que compartan el mismo puerto.
  • Rotación de registros no está activado por defecto. Instálalo pm2-logrotate y configura pm2 set pm2-logrotate:max_size 50M y pm2 set pm2-logrotate:retain 14 para que los registros no llenen el disco durante un pico de tráfico.
  • Persistencia de inicio. Correr pm2 startup systemd y pm2 save para que los procesos se reinicien automáticamente tras un reinicio o una actualización del kernel.

Para recargas sin interrupciones durante las implementaciones, usa pm2 reload api en lugar de restart. Reload cambia a los trabajadores de uno en uno sin que el clúster se desconecte.

¿Cómo se configura Nginx proxy inverso para Node.js?

Colocar Nginx Node es el cambio que más impacto tiene en la mayoría de las implementaciones en producción. Nginx la terminación TLS, la entrega de recursos estáticos, la compresión gzip y Brotli, el almacenamiento en búfer de las solicitudes para clientes lentos y la multiplexación HTTP/2, lo que libera a Node para que se dedique exclusivamente al trabajo que requiere el código de tu aplicación.

Un bloque de servidor de producción básico:

uupstream node_api {
    server 127.0.0.1:3000;
    keepalive 64;
}
 
server {
    listen 443 ssl http2;
    server_name api.example.com;
 
    ssl_certificate     /etc/letsencrypt/live/api.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;
 
    gzip on;
    gzip_types application/json text/css application/javascript;
 
    location /static/ {
        alias /var/www/api/public/;
        expires 30d;
        add_header Cache-Control “public, immutable”;
    }
 
    location / {
        proxy_pass http://node_api;
        proxy_http_version 1.1;
        proxy_set_header Connection “”;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 60s;
    }
}

Dos detalles que los desarrolladores suelen pasar por alto: la configuración proxy_http_version 1.1 además del vacío Connection El encabezado permite reutilizar la conexión desde el grupo de keepalive de origen, lo que reduce drásticamente la sobrecarga del protocolo TCP bajo carga. Servir /static/ directamente desde Nginx un archivo Cache-Control Además, los encabezados descargan miles de solicitudes por minuto de tus trabajadores de Node para archivos a los que nunca deberían haber tenido que acceder.

¿Qué parámetros de memoria y recolección de basura deberías ajustar?

Node utiliza V8 en segundo plano, y el tamaño predeterminado del montón de la generación anterior de V8 es de aproximadamente 1,5 GB en sistemas de 64 bits, independientemente de la cantidad de RAM que tenga realmente el VPS. En un VPS de 4 GB que ejecuta cuatro workers, ese valor predeterminado deja unos 10 GB de capacidad teórica del montón que no puedes usar porque cada worker tiene su propio límite.

El indicador que hay que activar es --max-old-space-size, expresado en megabytes:

node –max-old-space-size=460 server.js

Guía de tallas:

  • Reserva aproximadamente el 25 % de la memoria RAM total para el sistema operativo, Nginx y cualquier base de datos o caché que se ejecute en el mismo VPS.
  • Divide el resto entre el número de trabajadores y luego resta un 10 % para cubrir los gastos generales de V8. En un VPS de 2 GB con 4 trabajadores, el resultado es de unos 460 MB por trabajador.
  • Partido max_memory_restart en PM2 hasta este valor o ligeramente por debajo. Un proceso reiniciado por PM2 se puede recuperar; uno que haya sido eliminado por el OOM killer del kernel, no.

Para servicios de muy alto rendimiento, otras opciones que vale la pena probar son: --max-semi-space-size para dar más margen a la generación joven (reduciendo la frecuencia de GC menores en los servicios que asignan de forma agresiva) y --no-compilation-cache si observas que el código compilado almacenado en caché está ejerciendo presión sobre la memoria en los trabajadores de corta duración. Prueba los cambios bajo carga antes de implementarlos en producción.

¿Cómo se optimiza una aplicación de Node.js que va lenta?

La mayoría de los trabajos de optimización fracasan porque el ingeniero ha optimizado lo que no debía. Primero haz un perfilado, luego cambia el código:

  • node --inspect server.js Con Chrome DevTools tendrás un gráfico de llama del tiempo de CPU y una herramienta de instantáneas del montón para localizar objetos retenidos. La pestaña «Rendimiento» de DevTools es la forma más rápida de identificar un bucle de eventos bloqueado.
  • clinic doctor (clinicjs.org) ejecuta tu aplicación bajo carga y genera un diagnóstico. Es especialmente útil para detectar retrasos en el bucle de eventos y una presión excesiva del recolector de basura antes de que te adentres más en el análisis.
  • autocannon es el generador de carga al que recurren la mayoría de los desarrolladores de Node. Realizar una prueba de rendimiento de referencia antes de cualquier ajuste te proporciona el punto de comparación que necesitas para saber si tus cambios han ayudado o han perjudicado.
  • Supervisión del retraso del bucle de eventos en producción debe ir en tu APM o en un simple perf_hooks.monitorEventLoopDelay() exportador a Prometheus. Un retraso superior a 50 ms bajo una carga constante indica que hay algún proceso síncrono que está bloqueando a los trabajadores.

Si un único punto final va lento, mide el tiempo de la consulta a la base de datos por separado del controlador. El perfilador de Node indicará await pool.query(...) como el cuello de botella, pero el trabajo se está haciendo en PostgreSQL MySQL, no en tu código.

¿Qué capas de almacenamiento en caché marcan la mayor diferencia?

El almacenamiento en caché es la optimización con mayor retorno de la inversión que la mayoría de los equipos pasan por alto. Hay tres capas que son importantes para las cargas de trabajo de producción de Node.js:

  • Almacenamiento en caché a nivel de aplicación con Redis. Traslada el almacenamiento de sesiones, los contadores de limitación de velocidad y los resultados de las consultas a las que se accede con frecuencia de la base de datos a Redis, ya sea en el mismo VPS o en un servidor vecino de la red privada. Una ida y vuelta al Redis local dura menos de un milisegundo; la misma consulta en PostgreSQL la caché fría puede tardar entre 20 y 80 ms.
  • Almacenamiento en caché de respuestas HTTP en Nginx. En el caso de los puntos finales que devuelven respuestas idénticas para la misma URL, proxy_cache En Nginx atender miles de solicitudes por segundo desde el disco sin que Node tenga que intervenir en absoluto. Incluso una ventana de caché de 10 segundos en un punto de acceso muy visitado reduce drásticamente la carga en el servidor de origen.
  • Una CDN delante de tu VPS. Cloudflare, Bunny o cualquier CDN con proxy inverso absorbe el tráfico de recursos estáticos, termina el TLS en el borde y protege el servidor de origen del tráfico de bots. Para usuarios repartidos por todo el mundo, la mejora en la latencia suele ser mayor que cualquier ajuste a nivel de aplicación.

El orden en que hay que añadirlos es el que aparece en la lista. Primero Redis, porque cambia la estructura de tu aplicación. En segundo lugar, Nginx , porque no requiere cambios en el código, y en tercer lugar, una CDN, porque beneficia incluso a una aplicación que no esté optimizada.

¿Cómo se protege un servidor VPS con Node.js en entorno de producción?

El rendimiento y la seguridad están más relacionados de lo que los desarrolladores creen, ya que una aplicación expuesta está a solo un escaneo de botnet de quedar inaccesible. Medidas básicas de refuerzo de seguridad para un VPS con Node.js:

  • Ejecuta Node como usuario no root. Uso setcap 'cap_net_bind_service=+ep' $(which node) si necesitas conectarte a puertos por debajo del 1024 sin ser root, o terminar en Nginx dejar que Node escuche en el 3000.
  • Configura el cortafuegos del servidor. UFW en Ubuntu o firewalld En AlmaLinux, el servidor solo permite el acceso a los puertos que tú mismo hayas habilitado, normalmente el 22, el 80 y el 443.
  • Mantén las dependencias actualizadas. npm audit en CI y Dependabot o Renovate en el repositorio detectan vulnerabilidades CVE en las dependencias transitivas antes de que lleguen a producción.
  • Configura los encabezados de seguridad HTTP. Casco es el middleware estándar de Express para encabezados como Strict-Transport-Security, Content-Security-Policyy X-Frame-Options. Los encabezados mal configurados son uno de los problemas más habituales que se detectan en las auditorías de seguridad.
  • Rota las claves y usa variables de entorno. Nunca subas archivos .env. Herramientas como Doppler, Vault o incluso systemd EnvironmentFile= Las directivas evitan que las credenciales se guarden en el repositorio.

¿Cuándo conviene pasar de un solo VPS?

Una aplicación Node.js bien optimizada en un VPS de 4 a 8 vCPU con Nginx Redis puede gestionar sin problemas millones de solicitudes al día. El escalado horizontal suele ser necesario por una de estas tres razones:

  • Si el uso de la CPU se mantiene por encima del 70 % en todos los trabajadores, incluso después de los cambios en el perfilado y el almacenamiento en caché, significa que has superado la capacidad del sistema.
  • Los SLA de tiempo de actividad muy estrictos, que no admiten ni un solo fallo de un host, requieren al menos dos instancias VPS de la aplicación detrás de un equilibrador de carga.
  • La separación de recursos con estado merece la pena a pesar del coste operativo cuando tu base de datos, la caché y las cargas de trabajo de la aplicación empiezan a competir por la misma E/S de disco o RAM en un VPS compartido.

Tanto los planes Cloud VPS como los planes VPS gestionados de InMotion incluyen acceso root completo, asignación de vCPU dedicada y distribuciones de Linux como AlmaLinux 9, Ubuntu 22.04 LTS y Debian 12, que cubren los requisitos de ejecución de cualquier versión LTS actual de Node.js. El acuerdo de nivel de servicio (SLA) con un tiempo de actividad del 99,99 % y el acceso 24/7 al equipo de APS son lo más importante en el momento en que tu aplicación deja de ser un proyecto secundario y empieza a generar ingresos.

Si estás ejecutando una aplicación de Node.js en producción en un alojamiento compartido o en un VPS que no se ha optimizado más allá de la configuración predeterminada, es probable que los cambios que se describen en este artículo reduzcan a la mitad la latencia p95. Además, pueden duplicar el rendimiento sostenible de las solicitudes sin que tengas que gastarte ni un céntimo más en infraestructura. Empieza con el modo de clúster de PM2 y Nginx front-end, analiza el rendimiento restante y añade almacenamiento en caché donde los datos lo permitan.

Optimización práctica del rendimiento de Node.js para alojamiento VPS en producción, incluyendo clústeres, PM2, proxy Nginx , opciones de memoria y capas de almacenamiento en caché.¿Listo para ejecutar Node.js en producción?Los planes Cloud VPS y Managed VPS de InMotion te ofrecen acceso root, asignación de vCPU dedicada y la posibilidad de elegir entre AlmaLinux 9, Ubuntu 22.04 LTS o Debian 12. Con el respaldo de un servicio de asistencia humana 24/7 y un SLA de tiempo de actividad del 99,99 %.

¿Comparar planes VPS?
Comparte este artículo

Deja una respuesta

Tu dirección de correo electrónico no se publicará. Los campos obligatorios están marcados con *.