HU

Humano

← Blog

14 de marzo de 2026

Por qué agregar más instancias no resuelve tu problema de latencia

Un servicio con 16 instancias donde duplicar la capacidad solo mejora la latencia un 15%. El problema: el bottleneck no está en el cómputo, y escalar horizontalmente no puede eliminarlo.

escalamientolatenciaanti-patrón

El equipo tiene un servicio de procesamiento de eventos que satura a 800 RPS con una latencia p99 de 2.8 segundos. La presión de producto es clara: necesitan soportar 1,500 RPS para el lanzamiento del próximo trimestre. La decisión instintiva: duplicar instancias de 8 a 16.

Resultado después de desplegar: el throughput máximo sube a 1,100 RPS — no a 1,600 como esperaban. La latencia p99 baja de 2.8s a 2.4s. El costo de infraestructura se duplica. La mejora real fue 15% en latencia por un 100% más de gasto.

Esto no es un error de configuración. Es la consecuencia directa de Amdahl's Law aplicada a sistemas distribuidos.

Amdahl's Law en producción

Amdahl's Law dice que la mejora máxima de un sistema está limitada por la fracción que no se puede paralelizar. Si el 40% del tiempo de un request se gasta en una operación serializada (que no mejora al agregar instancias), entonces no importa cuántas instancias agregues — el p99 nunca bajará de ese 40%.

La fórmula es directa:

Speedup máximo = 1 / (S + (1 - S) / N)

S = fracción serializada (no paralelizable)
N = número de instancias

Si S = 0.40 (40% del request está serializado):

InstanciasSpeedup teóricoLatencia p99 teórica (si base = 2.8s)
82.1x1,330ms
162.3x1,217ms
322.4x1,167ms
642.4x1,152ms
2.5x1,120ms

De 8 a 16 instancias la mejora es 9%. De 16 a 32, 4%. Con infinitas instancias, la latencia nunca baja de 1,120ms. El 40% serializado es un techo absoluto.

Dónde está la fracción serializada

En un sistema distribuido, la "fracción serializada" no siempre es obvia. Rara vez es un mutex explícito en código. Las fuentes más comunes:

1. La base de datos como punto de serialización

Toda query que toca las mismas filas con SELECT ... FOR UPDATE, INSERT en una tabla con índices únicos, o cualquier escritura que dispare WAL logging, serializa a través del motor de la base de datos. Si 8 instancias y 16 instancias hacen la misma cantidad total de writes/segundo a la misma tabla, la base de datos es el cuello — no el servicio.

Cómo verificar: Medir pg_stat_activity con wait_event_type = 'Lock' o wait_event = 'WALWrite'. Si el porcentaje de queries esperando locks crece linealmente con las instancias, la base de datos es el punto de serialización.

2. Un servicio downstream con capacidad fija

El servicio de eventos llama a un servicio de validación que tiene 4 instancias con un rate limit interno de 500 RPS. No importa cuántas instancias tenga el servicio upstream — el downstream no puede procesar más de 500 RPS. El resto espera en cola o recibe 429.

Cómo verificar: En el distributed trace, buscar spans con duración bimodal: la mayoría toma 20ms pero un porcentaje creciente toma 200ms+. El salto indica cola o throttling en el servicio downstream.

3. Contención en un recurso compartido

Redis usado como lock distribuido (SETNX), un servicio de rate limiting centralizado, un message broker con un consumer group donde un solo consumidor procesa mensajes de una partición. Todos son puntos donde agregar instancias upstream no ayuda porque el cuello está en un recurso que no escala con ellas.

Cómo verificar: Correlacionar la latencia del servicio con la utilización del recurso compartido. Si la latencia sube cuando Redis o Kafka están saturados, el bottleneck no es el servicio que estás escalando.

4. CPU-bound work dentro del request

Si el servicio gasta 60% del request time en deserializar un payload JSON de 200KB o en ejecutar una transformación criptográfica, agregar instancias distribuye ese trabajo entre más cores — pero solo si la distribución es uniforme. Si hay un hot path que siempre toca un cache local invalidado o un singleton, el CPU-bound work no se distribuye.

Cómo verificar: Un flame graph de 30 segundos con async-profiler (JVM) o perf record (Linux) muestra exactamente qué funciones consumen CPU. Si una sola función aparece con >30% del CPU total, es candidata a optimización directa en lugar de escalamiento.

Qué hacer en lugar de agregar instancias

Paso 1: Medir la fracción serializada

Usar un distributed trace (Jaeger, Zipkin, Datadog APM) para descomponer un request en sus spans. Sumar el tiempo que pasan en operaciones que no escalan con las instancias: queries a la base de datos, llamadas a servicios downstream con capacidad fija, adquisición de locks distribuidos.

Si el 40% o más del request time está en spans serializados, escalar horizontalmente tiene retornos decrecientes dramáticos.

Paso 2: Atacar el componente serializado

Paso 3: Medir después

Reducir instancias al número anterior y verificar que la latencia mejoró. Si optimizaste la fracción serializada de 40% a 10%, el speedup teórico con 8 instancias pasa de 2.1x a 4.7x. Eso sí es una mejora real.

Cuándo sí conviene escalar horizontalmente

El escalamiento horizontal funciona cuando el bottleneck es cómputo paralelizable:

La pregunta correcta antes de agregar instancias: "¿el tiempo que quiero reducir se gasta en algo que mejora al agregar más instancias, o en algo que no?"

Si la respuesta es "no sé", el primer paso no es escalar — es medir.


Si tu servicio no mejora proporcionalmente al agregar instancias y quieres identificar dónde está la fracción serializada antes de gastar más en infraestructura, podemos revisarlo en una conversación de 15 minutos sin costo.

Referencias