Rendimiento de la API de Node.js: Un marco de optimización ordenado por prioridad
Aprenda a clasificar las soluciones para mejorar el rendimiento de la API de Node.js según su nivel de esfuerzo e impacto, de modo que resuelva primero los problemas relacionados con el pooling de conexiones y las consultas N+1 antes de buscar optimizaciones más complejas.
La mayoría de los artículos sobre cómo acelerar una API de Node.js te presentan una lista simple con quince consejos, donde algo como un serializador JSON más rápido aparece junto al pooling de conexiones a la base de datos, como si cada elemento mereciera la misma atención. Eso es engañoso. Algunas soluciones toman diez minutos y reducen drásticamente los tiempos de respuesta. Otras requieren meses de esfuerzo para obtener un beneficio marginal. Comprender la diferencia es mucho más importante que memorizar cada elemento de la lista.
Nivel 1: Haz estos primero (alto impacto, bajo esfuerzo)
Estos cambios cuestan casi nada en implementación. Requieren poco código, conllevan poco riesgo y, en la práctica, son la verdadera causa de la lentitud con mucha más frecuencia que las soluciones exóticas a las que recurren otros.
Habilita el modo keep-alive para todas las solicitudes HTTP salientes. Por defecto, el cliente HTTP de Node abre una nueva conexión para cada llamada, por lo que cada solicitud a un servicio externo debe realizar nuevamente todo el proceso de establecimiento de conexión TCP más la negociación TLS. Reutilizar un mismo agente en distintas solicitudes elimina ese sobrecosto en cada llamada posterior al mismo host.
const agent = new https.Agent({ keepAlive: true, maxSockets: 50 });
Siempre comunícate con tu base de datos a través de un pool de conexiones, y no mediante una conexión única. Bajo una carga concurrente real, una sola conexión se convierte efectivamente en una cola detrás de la cual todo espera. Un pool de tamaño adecuado permite que tu API maneje el tráfico concurrente tal como está realmente estructurada, de forma paralela.
Deje que las llamadas asíncronas independientes se ejecuten en paralelo en lugar de una tras otra. Cuando dos instrucciones await no dependen del resultado de la otra, no hay razón para obligarlas a esperar en orden.
// costs the sum of both calls
const user = await getUser(id);
const orders = await getOrders(id);
// costs roughly the slower of the two
const [user, orders] = await Promise.all([getUser(id), getOrders(id)]);
Agregue índices a las columnas que sus consultas realmente filtran, unen o ordenan. De todo lo mencionado en esta lista, este es sin duda el cambio más efectivo para cualquier endpoint que lea de una tabla que sigue creciendo, y con frecuencia se trata de una migración que se puede realizar en una sola línea.
Elimine los patrones de consulta N+1. Obtener una lista y luego realizar otra consulta por cada elemento dentro de un bucle parece inofensivo con unos pocos registros de prueba, pero se convierte en un problema grave cuando se trabaja con miles de registros reales. Sustituya el bucle por una única consulta por lotes para los datos relacionados.
Limita cada consulta que de lo contrario podría devolver un conjunto de resultados ilimitado con un LIMIT. Un endpoint que devuelve “cada pedido que este cliente ha realizado alguna vez” sin límite funciona bien para una cuenta completamente nueva, pero falla estrepitosamente en el caso de una cuenta con años de historial de pedidos.
Nivel 2: Vale la pena una inversión real (alto impacto, esfuerzo considerable)
Los elementos de este nivel no son pequeños ajustes. Requieren un trabajo real de diseño y, a menudo, nueva infraestructura, pero resuelven categorías de problemas que ninguna solución del Nivel 1 puede abordar.
Introduce una capa de caché para las lecturas que son costosas y ocurren con frecuencia. Colocar Redis delante de una consulta agregada lenta o de una llamada costosa a una API de terceros puede reducir un tiempo de respuesta de 200 ms a aproximadamente 2 ms. La parte difícil no es establecer el caché, sino diseñar una estrategia de invalidación lo suficientemente sólida como para que el caché nunca proporcione resultados obsoletos sin problemas.
Saca del ciclo de solicitud-respuesta las tareas lentas y no críticas por completo. Enviar un correo de confirmación, generar un informe o actualizar datos analíticos no necesitan finalizarse antes de que respondas al cliente. Combinar una cola, como BullMQ o SQS, con un proceso de trabajo dedicado puede reducir una solicitud que antes tardaba 2 segundos a algo cercano a 80 milisegundos.
Pase de la paginación basada en offset a la paginación basada en cursor para conjuntos de resultados grandes o complejos. La paginación basada en OFFSET se vuelve progresivamente más lenta a medida que avanza en las páginas, ya que la base de datos aún debe escanear todas las filas anteriores. Un enfoque basado en cursor cuesta aproximadamente lo mismo, ya sea que se esté en la página 5 o en la página 5,000.
Escale horizontalmente con un balanceador de carga real y un estado centralizado de sesiones o caché. Por bien que lo haya ajustado, un único proceso Node eventualmente alcanza un límite. Al ejecutar varias instancias detrás de un balanceador de carga, cada una compartiendo un caché Redis y un pool de conexiones comunes, se eleva ese límite sin requerir que ningún proceso sea individualmente más rápido.
Perfil antes de seguir optimizando. Una vez que se resuelven los problemas obvios, adivinar qué es lento deja de ser una estrategia fiable. Utilizar un perfilador real o una herramienta APM, como clinic.js, un producto APM alojado, o incluso ejecutar EXPLAIN ANALYZE en una consulta sospechosa, le muestra dónde realmente se está gastando el tiempo en lugar de donde supone que debe serlo.
Nivel 3: Por lo general no vale la pena darle prioridad (bajo impacto, a menudo sobrevalorado)
Estos temas surgen constantemente en las discusiones sobre rendimiento, pero rara vez marcan una diferencia significativa en una API de producción real, principalmente porque se centran en partes del stack que nunca fueron el cuello de botella real desde un principio.
Ajuste fino de la serialización JSON. Existen bibliotecas JSON más rápidas que sí ayudan, pero solo a una escala a la que la inmensa mayoría de las APIs nunca llegan. Si tu verdadero problema es una consulta a la base de datos que tarda 300 ms, reducir unos pocos milisegundos en la serialización soluciona el problema equivocado.
Cambiar de framework únicamente para reducir la sobrecarga del mismo. La diferencia de rendimiento entre Express y una alternativa supuestamente más rápida es real, pero pequeña en comparación con lo que te cuesta una tabla sin indexar o una consulta N+1. La elección del framework puede ser importante por muchas otras razones; la velocidad bruta generalmente no es una de ellas.
Intentar utilizar el agrupamiento antes de abordar cualquier otra cosa. Ejecutar múltiples procesos Node para aprovechar núcleos de CPU adicionales ayuda realmente a las tareas limitadas por el procesador. No servirá de nada para un endpoint lento debido a que está esperando una consulta sin indexar, ya que la espera sigue siendo espera, independientemente de cuántos procesos estén inactivos realizándola.
Rescribir rutas de código con alto uso en un lenguaje de nivel más bajo únicamente por motivos de rendimiento. Existen casos legítimos para esto, cuando un cuello de botella específico y comprobado relacionado con el procesador lo justifica. Sin embargo, con mucha más frecuencia se intenta hacerlo antes de que alguien haya confirmado realmente dónde se está perdiendo el tiempo, convirtiendo una técnica válida en un esfuerzo inútil dirigido al objetivo equivocado.
Cómo utilizarlo realmente
Limpie primero todo lo relacionado con el Nivel 1, ya que es económico, de bajo riesgo y aborda la mayoría de los problemas de rendimiento en el mundo real. Solo pase al Nivel 2 para aquellos puntos específicos donde el análisis muestra que el Nivel 1 no fue suficiente, en lugar de tratarlo como una reescritura que aplicar en todas partes. Deje el Nivel 3 sin tocar hasta que tenga pruebas concretas, provenientes de un analizador y no de conjeturas, de que una de esas técnicas específicas es realmente el cuello de botella. La mayoría de las APIs que parecen lentas lo son debido a un problema del Nivel 1 sin resolver, no porque falte alguna optimización poco conocida sacada de algún artículo de blog.
Lecturas relacionadas
- Express vs Fastify en 2026: Un comparativo práctico de frameworks Node.js — Esta guía compara Express y Fastify en términos de rendimiento, validación, ecosistema y manejo de errores, además aborda los principales cambios que afectan a Express 5.
- Node.js, Deno y Bun comparados: Estudios de caso, compromisos y estrategia de migración — Explica las verdaderas diferencias arquitectónicas entre Node.js, Deno y Bun, lo que revelan los estudios de caso de 2025, y cómo decidir si y cuándo realizar una migración.