Dimensiona un pool de hilos de aplicación según cuánto tiempo pasan las peticiones trabajando frente a esperando.
Cada hilo que toca la base de datos retiene una conexión mientras espera, así que este pool marca el suelo de tu pool de conexiones.
Por qué el pool de hilos correcto es más pequeño de lo que crees, hasta que de pronto no lo es
Un pool de hilos tiene una sola misión: mantener la CPU ocupada sin dejar que el trabajo se acumule dentro del proceso. El tamaño que lo consigue depende casi por completo de una proporción: cuánto pasa una petición esperando a otra cosa frente a cuánto pasa realmente calculando. Esta herramienta aplica la fórmula estándar de esa proporción, la contrasta con la ley de Little y te dice cuándo el resultado significa que necesitas otro modelo de concurrencia en vez de un número más grande.
Cómo funciona
- Aplica la fórmula clásica: núcleos × utilización objetivo × (1 + espera ÷ servicio).
- Usa la ley de Little para deducir la concurrencia que tu rendimiento objetivo implica de verdad.
- Informa del rendimiento que el pool recomendado puede sostener, para ver si cubre tu objetivo.
- Avisa cuando la aritmética se ha pasado de lo que un modelo un-hilo-por-petición puede hacer con sensatez.
hilos = núcleos x utilización x (1 + tiempo_espera / tiempo_servicio) ley de Little concurrencia = tasa_llegada x latencia techo de rendimiento max_rps = hilos / (tiempo_servicio + tiempo_espera)
Ejemplo resuelto
Un servicio en 8 núcleos, con objetivo del 80% de CPU, donde una petición calcula 20 ms y espera 80 ms a la base de datos, en un pico de 300 peticiones por segundo.
- espera ÷ servicio = 80 ÷ 20 = 4
- hilos = 8 × 0,8 × (1 + 4) = 32
- latencia = 20 + 80 = 100 ms
- ley de Little: 300 peticiones/s × 0,1 s = 30 peticiones simultáneas
- 32 hilos ≥ 30, así que el pool cubre el objetivo
- techo: 32 ÷ 100 ms = 320 peticiones por segundo
Treinta y dos hilos en ocho núcleos. El pool es cuatro veces el número de núcleos no porque los hilos sean gratis, sino porque cada uno está inactivo el 80% del tiempo: guarda un sitio en la cola de la base de datos, no usa CPU. Pon la espera a cero y la misma fórmula devuelve 6, que es la respuesta correcta para trabajo que nunca se bloquea.
Cómo leer el resultado
- La fórmula vale lo que valgan los dos tiempos que le des. El tiempo de servicio es tiempo de CPU, no tiempo de reloj, y se confunden con facilidad: el reloj incluye la espera, que es la otra variable. Un perfilador los separa; un cronómetro no.
- Cada hilo que consulta la base de datos retiene una conexión mientras espera. Eso convierte al pool de hilos en el suelo del pool de conexiones, y es el motivo habitual por el que se alcanza un límite de conexiones: nadie dimensionó los dos juntos.
- Pasados unos cientos de hilos, el problema es el modelo mismo. Cada hilo de plataforma cuesta cerca de un megabyte de pila y un hueco en el planificador, así que un pool de 1.300 pasa más tiempo cambiando de contexto que trabajando. Con esa proporción la respuesta es E/S no bloqueante o hilos virtuales, y por eso la herramienta lo dice en vez de imprimir el número y dejarte solo.
- Una cola delante del pool no es capacidad extra. Si las llegadas superan lo que el pool puede despachar, la cola crece sin límite y la latencia con ella: las peticiones fallan igual, solo que más tarde y tras consumir memoria. Acota la cola y descarta carga en vez de fingir que la profundidad es rendimiento.
- Los objetivos de utilización por encima de en torno al 80% dejan de ser seguros. La teoría de colas es despiadada cerca de la saturación: según la utilización se acerca al 100%, el tiempo de espera crece sin límite, así que el último 20% de un núcleo cuesta mucha más latencia de la que devuelve en rendimiento.
Preguntas frecuentes
- ¿De dónde viene la fórmula (1 + espera/servicio)?
- Es el resultado estándar para mantener N núcleos ocupados cuando cada tarea solo ocupa un núcleo durante parte de su vida, popularizado por Brian Goetz en Java Concurrency in Practice. Sin espera se reduce a núcleos × utilización, que es lo correcto para trabajo ligado a CPU; la proporción solo contabiliza los hilos aparcados en E/S en cada momento.
- ¿El pool de hilos debe igualar al de conexiones?
- El pool de conexiones debe ser al menos tan grande como el número de hilos que pueden estar dentro de una llamada a la base a la vez, pero no mayor de lo que el límite de la base soporte para toda tu flota. Si ambos chocan, la salida es menos hilos o más instancias de aplicación, no un pool de conexiones mayor, que solo traslada la contención a la base.
- ¿Los hilos virtuales dejan esto obsoleto?
- Cambian qué número importa. Los hilos virtuales son lo bastante baratos para tener uno por petición sin dimensionar un pool, así que la proporción de espera deja de limitarte. Queda el límite aguas abajo: el pool de conexiones, el límite de tasa de una API externa, la propia CPU. Esos siguen necesitando dimensionado, y este cálculo sigue diciéndote dónde aprietan.
- ¿Cómo mido el tiempo de servicio y el de espera?
- El de servicio, con perfilado de CPU o con la diferencia entre el tiempo de reloj y la suma de tus llamadas externas instrumentadas. El de espera, con los spans que tu trazado ya emite para llamadas a base de datos y HTTP. Si no tienes ninguno, parte de una estimación informada y comprueba la predicción: si el pool sostiene mucho menos rendimiento del que dice la fórmula, tu estimación de espera se quedó corta.