Escalar Node.js, la versión aburrida
La mayoría de los artículos sobre "escalar Node.js" los escribe gente que escaló un servicio a un millón de peticiones al día y asume que eso generaliza. Nosotros hemos operado varios sistemas por encima de 10 millones de peticiones al día, y las lecciones no son glamorosas.
Este es el manual que nos hubiera gustado tener hace cuatro años.
Deja de discutir sobre microservicios
El error más costoso que hemos visto cometer a los equipos es dividir en microservicios antes de necesitarlo. Un monolito con límites de módulo bien definidos escala mucho más de lo que la mayoría de los ingenieros espera, y es un orden de magnitud más barato de operar.
Nuestra regla: mantenerse monolítico hasta que se cumpla una de estas condiciones.
- Una ruta específica tiene características de escalado distintas al resto (por ejemplo, un endpoint pesado de inferencia de ML).
- Una ruta específica tiene un ritmo de despliegue distinto (por ejemplo, código sujeto a cumplimiento normativo que solo puede publicarse mensualmente mientras el resto se publica a diario).
- El equipo supera los 25 ingenieros y la contención de merges es un problema real.
Si ninguna de esas condiciones se cumple, dividir en servicios añade costo operativo — service discovery, tracing distribuido, orquestación de despliegues, modos de fallo de red — sin aportar nada que no pudieras obtener ya de un monorepo bien organizado.
El cuello de botella real es la base de datos
En todos los proyectos de escalado de Node.js que hemos hecho, el cuello de botella termina moviéndose a la base de datos. La CPU y la memoria en los servicios de Node.js suelen estar bien porque Node es bueno en concurrencia de I/O. Los problemas son:
- Pools de conexiones sin límite que derriten Postgres
- Un único índice caliente que se convierte en cuello de botella de escritura
- Consultas N+1 en los ORM que parecen inofensivas en el code review
- Transacciones de larga duración que retienen locks durante la lógica de negocio
- Tráfico de lectura sobre la instancia primaria en lugar de una réplica de lectura
Primeros pasos prácticos:
- PgBouncer o equivalente delante de Postgres, en modo transaction pooling, con un límite de conexiones basado en la CPU real de la base de datos, no en la intuición de Node.js.
- Réplicas de lectura con enrutamiento explícito. No dejes que el ORM decida. Ten dos clientes en el código:
db.readydb.write. Obliga a cada desarrollador a elegir. - Medición de tiempos de consulta en producción. Pino más un logger de consultas lentas. Quieres una alerta en Slack cuando cualquier consulta supere los 200ms.
- EXPLAIN ANALYZE en CI para consultas nuevas. Lo añadimos como check de PR en los cambios que tocan el schema.
Caché, en tres capas
La caché te salva en este orden: CDN, en memoria, Redis. Sáltate cualquier capa y te arrepentirás.
CDN. Todo lo que se pueda cachear en el edge debería estarlo. Esto incluye respuestas de API para lecturas sin autenticar. No hace falta ser ingenioso — las cabeceras Cache-Control y una CDN que las respete resuelven el 60% del tráfico de lectura gratis.
En memoria. Una pequeña caché LRU (usamos lru-cache) delante de Redis captura la cola larga de peticiones repetidas dentro de un mismo proceso. Suena trivial; en nuestros perfiles ahorra entre un 30% y un 50% del tráfico a Redis.
Redis. Para estado compartido y valores calculados que sobreviven entre procesos. Úsalo con intención — cada clave necesita un dueño, un TTL y una estrategia de invalidación documentada. Las claves de Redis sin control son la razón por la que las cachés de producción terminan sirviendo datos de hace 6 meses en el Black Friday.
Haz asíncrono todo lo que puedas
Las rutas de petición síncronas deben hacer el mínimo: validar, persistir, responder. Todo lo demás — correos, disparos de webhooks, procesamiento de imágenes, llamadas a APIs de terceros, indexado de búsqueda — va a una cola.
Usamos BullMQ sobre Redis para casi todo. Dos razones:
- Aislamiento de fallos. Una API de terceros inestable no puede tumbar tu ruta de petición si la llamada ocurre en un worker.
- Backpressure. Cuando el tráfico se dispara, la cola absorbe el pico y los workers lo drenan a un ritmo sostenible. El código síncrono bajo un pico simplemente muere.
Ejemplo concreto: un flujo de checkout que reconstruimos para un cliente disparaba antes 6 llamadas a terceros de forma síncrona (impuestos, fraude, fulfillment, correo, SMS, analítica). La mediana del checkout era de 1.4 segundos y el p99 llegaba a 8 segundos cuando algún proveedor se ponía lento. Tras mover todo excepto impuestos y fraude a workers de BullMQ, la mediana bajó a 220ms y el p99 se mantuvo por debajo de 600ms.
Observabilidad antes que arquitectura ingeniosa
El instinto a escala es diseñar sistemas ingeniosos. El movimiento correcto es instrumentar el sistema que ya tienes hasta poder ver exactamente dónde se va el tiempo.
Estandarizamos en:
- Tracing con OpenTelemetry, exportado a Honeycomb o Tempo. Cada petición tiene un trace ID. Cada llamada externa es un span.
- Logs estructurados, en formato JSON, con el trace ID. Pino funciona bien.
- Métricas RED por endpoint — Rate, Errors, Duration. Dashboards en Grafana. Alertas sobre el p99, no sobre el p50.
- Chequeos sintéticos desde fuera de tu red, ejecutándose cada minuto contra las rutas críticas.
No vas a predecir correctamente tus cuellos de botella. Vas a medirlos. El equipo que lanza primero la observabilidad se gana los siguientes seis meses de trabajo de escalado.
Los errores que hemos visto repetirse
Una lista corta de cosas que hemos visto tumbar producción:
- Registrar demasiado, tumbar el pipeline de logs, y quedarse sin logs.
- Olvidar configurar
--max-old-space-sizede Node. El valor por defecto es 1.5GB. Tu contenedor tiene 8GB. Haz cuentas. - Escribir en una base de datos dentro de un bucle Promise.all sin acotar la concurrencia. Agotamiento del pool de conexiones en el peor momento.
- Health checks que pasan cuando el servicio en realidad está roto. Prueba la cadena de dependencias, no solo el proceso.
- Confiar en que un SDK de terceros configure los timeouts. Envuélvelo siempre con los tuyos propios.
La verdad poco atractiva
A 10M peticiones al día, la diferencia entre un sistema que funciona y uno que no rara vez es genio arquitectónico. Es un equipo que vigila sus dashboards, arregla las consultas lentas la semana en que aparecen, hace revisiones de incidentes con honestidad, y resiste la tentación de refactorizar cuando debería estar instrumentando.
Consigue esa cultura y Node.js aguantará mucho más de lo que sugieren las charlas de conferencias. Este es exactamente el tipo de trabajo que nuestros equipos de backend y cloud y DevOps asumen para productos en etapa de crecimiento.



