Contactar →
← Todas las entradas
Arquitectura2026.05.13

Workloads por cuello de botella, no por comodidad

Cuando se tiene un puñado de máquinas donde correr workloads, el movimiento por defecto es tratarlas como intercambiables. Darle a cada una la misma imagen base, los mismos límites de recursos, las mismas expectativas, y dejar que el planificador (o la propia memoria de qué está corriendo dónde) resuelva la ubicación de forma más o menos arbitraria. Es el camino de menor carga cognitiva, y a pequeña escala normalmente no rompe nada de forma visible. Todo tiene margen suficiente como para que una ubicación mediocre solo signifique que hay holgura sobrante en la máquina equivocada.

Eso deja de ser inofensivo en cuanto un cluster es lo bastante pequeño como para que el propio margen sea el recurso escaso, que en un cluster de tres nodes siempre es el caso. A ese tamaño, la uniformidad entre nodes no es una simplificación, es una decisión de ignorar información que ya se tiene. Se sabe cuál de las tres máquinas tiene el almacenamiento más rápido. Se sabe cuál tiene más RAM. Se sabe cuál recibe una paliza constante de escrituras pequeñas y cuál está la mayor parte del tiempo ociosa salvo por ráfagas ocasionales. Tirar esa información y ubicar los workloads en round-robin, o según lo que resulte más cómodo en el momento del deploy, significa emparejar un servicio con estado hambriento de RAM con el node que tocara en la rotación, no con el node que de verdad tiene RAM de sobra.

El cuello de botella es la clave de la ubicación

El replanteamiento que realmente ayuda es pequeño: dejar de preguntar «qué node tiene sitio para esto» y empezar a preguntar «con qué tiene realmente el cuello de botella este workload, y qué node encaja mejor con esa presión concreta». Todo workload tiene un eje de recursos dominante, aunque también use los otros dos. Un stack de observability que ingiere métricas constantemente está limitado por I/O: hace escrituras sostenidas, a menudo pequeñas y frecuentes, y su techo de rendimiento es el throughput y la latencia del disco, no la CPU ni la RAM. Un servicio con estado que mantiene un conjunto de trabajo grande en memoria está limitado por RAM: quiere el node con más memoria disponible, y le importa poco si el disco de ese node es rápido o su CPU es modesta. Un batch job que hace cómputo pesado está limitado por CPU y le resulta bastante indiferente dónde vivan sus datos, siempre que sean accesibles.

En cuanto se nombra el cuello de botella, la ubicación deja de ser una apuesta a ciegas. El workload limitado por I/O va al node que de verdad se le da bien la I/O sostenida, es decir, el que tiene el almacenamiento más rápido de los tres o el que tiene menos otros inquilinos compitiendo por la profundidad de cola de ese almacenamiento. El workload hambriento de RAM va allí donde la memoria esté menos disputada, aunque el almacenamiento de ese node sea mediocre, porque la velocidad de almacenamiento nunca fue su problema. Dicho así, esto suena casi demasiado obvio, y precisamente por eso es fácil pasarlo por alto: el instinto de mantener todos los nodes configurados de forma idéntica se siente como la opción responsable y de bajo mantenimiento, cuando en la práctica solo significa que cada workload recibe un node que le encaja de forma mediana en lugar de uno que le encaja de la mejor forma posible.

Tres nodes, cada uno con un perfil de recursos distinto, emparejados con workloads según su cuello de botella dominante en lugar de por rotación.

Por qué la configuración idéntica es en realidad la trampa

Configurar todos los nodes de la misma manera resulta atractivo porque reduce el número de cosas que hay que recordar. Una imagen base, un conjunto de límites de recursos, un modelo mental único: menos sorpresas al hacer SSH al node dos frente al node tres. Pero esa uniformidad es una elección que optimiza para la comodidad del operador a costa del encaje del workload, y en un cluster de tres nodes el coste no es hipotético. Si el stack de métricas, intensivo en I/O, acaba en el node con el disco más lento solo porque ahí es donde apuntó el script de deploy esta vez, se ha incorporado un cuello de botella que no tiene nada que ver con el techo real de recursos del workload y todo que ver con una decisión de ubicación arbitraria tomada meses antes.

La asimetría también funciona al revés: sobreaprovisionar un node para un workload que no lo necesita desperdicia lo único que un cluster pequeño no se puede permitir desperdiciar, que es la capacidad libre en otro sitio. Un servicio utilitario ligero de CPU y de RAM no necesita estar en el node con más memoria: esa memoria está mejor invertida en algo que realmente vaya a usarla, y al servicio utilitario le va igual de bien en la máquina más modesta.

Esto no es un argumento a favor de ajustar a mano la ubicación de cada workload para siempre, ni un alegato a favor de una lógica de scheduling exótica en tres máquinas: eso sería resolver un problema a una escala que no se tiene. Es un hábito más pequeño y más barato: antes de decidir dónde corre algo, nombrar qué es lo que de verdad le presiona, y ubicarlo donde esa presión concreta se absorba mejor. En un cluster de este tamaño, ese único hábito hace más por la capacidad efectiva de los tres nodes que cualquier cantidad de CPU o RAM que se les añadiera individualmente.

Leer el cuello de botella en lugar de darlo por hecho

Nombrar el cuello de botella de un workload a partir de su descripción es una primera aproximación razonable, pero la versión más fiable de este ejercicio mira cómo se comporta el workload de verdad una vez que está corriendo en algún sitio, en lugar de fiarse de un modelo mental de una línea sobre qué tipo de cosa es. «Stack de observability» suena a etiqueta limpia de limitado-por-I/O, pero la presión real depende de detalles que no aparecen en esa etiqueta: cuánto del pipeline de ingesta hace buffer en memoria antes de volcar a disco, con cuánta agresividad indexa en la escritura frente a diferir ese trabajo, si los ajustes de retención hacen que esté constantemente compactando datos antiguos en segundo plano por encima de ingerir datos nuevos. Dos stacks de métricas con la misma descripción de trabajo pueden tener perfiles de cuello de botella significativamente distintos según cómo estén configurados, y la única forma de saber cuál se tiene en realidad es observarlo.

Por eso la ubicación basada en cuello de botella funciona mejor como un bucle que como una clasificación de una sola vez. Ubicar el workload en el node que parezca la mejor apuesta, y luego observar qué hacen de verdad los recursos de ese node bajo carga real. Si la apuesta de limitado-por-I/O era correcta, el throughput o la profundidad de cola del disco deberían ser lo que más cerca esté de su techo, mientras la CPU y la memoria se quedan cómodamente por debajo del suyo. Si en cambio resulta que la CPU es el recurso ajustado (porque, por ejemplo, el pipeline de ingesta hace más parsing y transformación de lo esperado antes de que nada toque el disco), la ubicación se construyó sobre la premisa equivocada, y ninguna cantidad de velocidad de almacenamiento en ese node lo iba a arreglar. Detectar eso exige mirar de verdad, no simplemente fiarse de la etiqueta que sugiere la categoría de un workload.

Dónde la configuración uniforme sigue teniendo sentido

Nada de esto es un argumento contra la uniformidad en todas partes. La versión base del sistema operativo, la cadencia de parcheo, la configuración del agente de monitorización, las herramientas de backups: no hay ninguna razón derivada del cuello de botella para dejar que eso diverja entre nodes, y dejar que se desvíe sin motivo solo añade superficie operativa sin ningún beneficio a cambio. La distinción que importa es entre la configuración que trata genuinamente de la capacidad del hardware (cuánta RAM hay físicamente presente, cómo de rápido es realmente el almacenamiento, cuántos núcleos hay disponibles) y la configuración que trata de la consistencia operativa, como qué stack de monitorización corre o cómo se envían los logs. La primera categoría es exactamente donde emparejar el workload con el hardware da sus frutos, porque las diferencias de hardware son reales y fijas. La segunda categoría es donde la uniformidad sigue siendo el valor por defecto correcto, porque no hay ninguna razón específica de workload para que un node se parchee de forma distinta a otro.

Confundir las dos cosas es, en sentido contrario, su propia trampa: tratar cada parámetro de configuración como sagrado y específico del workload convierte tres máquinas en tres configuraciones únicas, ajustadas a mano, cada una más difícil de entender por separado y, en conjunto, más difícil de mantener parcheadas y consistentes. El objetivo no es la máxima divergencia entre nodes. Es divergencia exactamente donde el hardware subyacente ya diverge, y uniformidad en todo lo demás.

Lo que esto no resuelve

Nada de esto sustituye a la monitorización, y en concreto a tener claro qué tipo de señal responde qué pregunta. Nombrar de antemano el cuello de botella de un workload es una apuesta educada basada en lo que hace el workload (un ingester de métricas probablemente esté limitado por I/O, un índice grande en memoria probablemente esté limitado por RAM), pero «probablemente» está haciendo un trabajo de verdad en esa frase. La única forma de saber si la apuesta de ubicación era correcta es observar el node después: si el disco está realmente saturado, si la presión de memoria está de verdad donde se esperaba, si el job limitado por CPU está de verdad limitado por CPU en la práctica en lugar de bloqueado por algo que no se tuvo en cuenta. La ubicación por cuello de botella es una hipótesis de partida, no un hecho asentado, y en un cluster de tres nodes es lo bastante barata de revisar cada vez que la forma de un workload cambia, algo que, en un sistema que crece como suele crecer la infraestructura a escala homelab, acabará pasando.

El otro límite es que este razonamiento se degrada en cuanto los workloads dejan de tener un único cuello de botella limpio. Un workload que a la vez es intensivo en I/O e intensivo en memoria no tiene una respuesta ordenada: se vuelve a las decisiones de criterio sobre qué presión importa más para ese servicio concreto, en ese node concreto, ahora mismo. Con tres nodes, eso sigue siendo una decisión manejable y ocasional. Es el tipo de cosa que deja de ser manejable a mano en cuanto crecen tanto el número de nodes como el de workloads, lo cual es una buena señal de cuándo esto necesita convertirse en una política documentada (o en el trabajo de un scheduler de verdad) en lugar de algo que vive en la cabeza de una sola persona.

Por ahora, tres nodes y un puñado de workloads es exactamente la escala en la que hacer esto a mano, de forma deliberada, gana a los dos extremos: gana a la ubicación uniforme en round-robin porque de verdad usa la información disponible sobre cada node, y gana a construir o adoptar un scheduler consciente de los recursos porque eso sería resolver un problema de coordinación que este montaje todavía no tiene. La cantidad correcta de proceso aquí es «pensar en el cuello de botella antes de decidir dónde corre algo, anotar por qué», no un sistema de scheduling con su propia superficie de configuración que mantener. El momento en que eso deje de ser suficiente (porque hay demasiados workloads como para razonar sobre ellos individualmente, o porque los perfiles de los nodes cambian con la frecuencia suficiente como para que la decisión de ubicación de ayer esté obsoleta) es una señal legítima para invertir en algo más pesado. Hasta entonces, añadir esa maquinaria pronto solo estaría cambiando un tipo de complejidad por otro, sin un número de workloads que lo justifique.

← Todas las entradas