Bulkhead
Qué es
Bulkhead limita cuánto de un recurso compartido puede consumir una categoría de trabajo o una dependencia. Busca que una sobrecarga o falla localizada no agote toda la capacidad que necesitan otras categorías. La analogía de compartimentos de un barco explica el nombre; en software lo decisivo es identificar el recurso y hacer cumplir el límite.
capacidad compartida -> partición / límite
falla en A -> A ocupa su cuota
trabajo para B -> conserva capacidad protegidaLa descripción de Azure Architecture Center incluye separar recursos del consumidor por dependencia y separar instancias por grupos de consumidores. El aislamiento es relativo a esos límites: compartir una base de datos, disco o proceso todavía puede permitir otros caminos de propagación.
Qué problema resuelve
Un servicio de pedidos consulta pagos, inventario y notificaciones. Si pagos empieza a colgarse, sus llamadas retienen recursos. Un pool compartido puede quedar ocupado enteramente por pagos, aunque inventario esté saludable. El fallo de una dependencia termina impidiendo trabajo que no la necesita.
Un diseño ilustrativo reserva límites diferentes:
Order Service
├── Payment calls -> max 20 concurrent
├── Inventory calls -> max 50 concurrent
└── Notification calls -> max 10 concurrentLos números no son recomendaciones. El objetivo es que pagos no pueda ocupar todos los slots destinados a inventario. Esto requiere proteger el recurso realmente escaso: limitar llamadas después de agotar un pool global de workers no rescata a los demás consumidores.
Si completar un pedido exige confirmar el pago, el aislamiento no permite omitir esa condición de negocio. Puede conservar consultas de stock y otras operaciones independientes, mientras la compra sigue indisponible. Preservar capacidad no garantiza que todos los flujos produzcan resultados completos.
Recursos y mecanismos
| Recurso | Forma de aislamiento | Límite que debe quedar claro |
|---|---|---|
| Llamadas en vuelo | Semáforo o contador por dependencia | Cuántas ejecuciones simultáneas admite. |
| Workers y threads | Worker pools o thread pools separados | Cuántos ejecutores y tareas pendientes puede ocupar cada grupo. |
| Conexiones | Connection pools por destino | Cuántas conexiones se reservan y cuánto cuesta esperar una. |
| Trabajo pendiente | Colas separadas y bounded queues | Cuánto backlog admite cada categoría y quién la atiende. |
| Tenants | Cuotas y pools por tenant o grupo | Qué capacidad conserva cada consumidor bajo contención. |
Resilience4j ofrece un bulkhead mediante semáforos y otro mediante un thread pool con cola acotada. Son mecanismos concretos, no requisitos del patrón. Un modelo asíncrono puede limitar concurrencia sin dedicar un thread por llamada; eso tampoco vuelve ilimitados memoria, conexiones o trabajo pendiente.
Dividir colas sin distribuir workers puede dejar el mismo cuello de botella. Separar conexiones sin limitar las solicitudes que esperan por ellas puede trasladar el agotamiento a memoria. El diseño debe seguir la vida completa del recurso, desde la admisión hasta su liberación efectiva.
Aislamiento lógico y físico
El aislamiento lógico separa permisos, cuotas o pools dentro de recursos compartidos. Es relativamente económico, pero depende de que todos los caminos respeten la contabilidad. Un límite local por proceso tampoco es un límite global: diez instancias con veinte slots pueden enviar doscientas llamadas concurrentes.
El aislamiento físico coloca trabajo en procesos, instancias o hosts distintos, con recursos reservados o límites de plataforma. Puede reducir la propagación de fallos de proceso y contención, a cambio de costo y operación adicional. Dos contenedores en el mismo host todavía comparten componentes; no representan automáticamente dominios de falla independientes.
La elección depende del fallo que se quiere contener. Aislar tenants puede proteger de un consumidor ruidoso, pero un pool por cada tenant pequeño puede desperdiciar capacidad y complicar la administración. Agruparlos reduce ese costo y amplía el radio de impacto dentro del grupo.
Asignación, fairness y starvation
Una reserva rígida protege capacidad aun durante una sobrecarga, pero puede quedar ociosa mientras otro grupo rechaza trabajo. Permitir préstamo mejora utilización; necesita una política para recuperar capacidad y evitar que el prestatario monopolice lo reservado. No siempre es posible desalojar operaciones en curso de manera segura.
Un límite por grupo no garantiza fairness entre solicitudes del mismo grupo. Trabajos largos pueden ocupar todos los slots y provocar starvation de trabajos cortos; una prioridad absoluta también puede dejar sin servicio a otra clase. Conviene observar tiempo de espera, ocupación y rechazos por categoría, además del total.
Los límites deben reflejar costos: dos llamadas concurrentes pueden consumir cantidades muy distintas de memoria o CPU. Una asignación basada solo en conteo necesita comprobar que esos otros recursos permanecen dentro del margen previsto. quality-attribute-trade-offs examina la contención frente a fragmentación, capacidad ociosa y costo operacional; architectural-tactics conecta esa decisión enfocada con la respuesta de calidad esperada.
Saturación y backpressure
Al llenarse una partición, la política debe decidir entre rechazar, esperar en una cola acotada o reducir la admisión aguas arriba. Una cola ilimitada conserva la apariencia de aceptación mientras acumula latencia y memoria. Esperar un slot también consume parte de deadline.
La señal de saturación puede alimentar backpressure: el productor reduce el ritmo en respuesta a la capacidad disponible. Bulkhead delimita el consumo; backpressure comunica y ajusta el flujo. admission-control puede hacer cumplir el límite antes de comprometer el recurso, con rechazo o espera acotada cuando no quedan permisos.
timeout puede acotar una espera, pero vencer no demuestra que el recurso remoto se liberó. Liberar un permiso local mientras la ejecución subyacente sigue activa permite que la concurrencia real supere el número esperado. La contabilidad debe aclarar qué trabajo cuenta y cómo termina.
Diferencias e interacción
Bulkhead no equivale a circuit-breaker. El breaker decide si continuar llamando según evidencia de salud; Bulkhead limita recursos ocupados incluso cuando la dependencia está sana. Un circuito cerrado puede admitir demasiado trabajo; una partición llena no demuestra que la dependencia haya fallado.
Bulkhead tampoco equivale a rate-limiting. El límite de tasa controla admisión por intervalo; el límite de concurrencia controla ocupación simultánea. La misma tasa puede retener muchos más slots si aumenta la duración. Pueden combinarse, pero no se sustituyen automáticamente.
Cada intento de retry necesita contabilizar su consumo. Reintentar inmediatamente un rechazo por saturación agrega contención, sin crear capacidad. El orden de los controles debe especificar qué protege cada uno y cuánto espera el caller.
Usarlo cuando exista capacidad que proteger frente a un grupo identificable. Puede limitar blast-radius de cascading-failures por agotamiento del recurso protegido; una dependencia común fuera de la partición todavía puede propagar impacto. Revisar ocupación, colas, rechazo, duración y recursos compartidos que escapan del aislamiento. resilience-patterns sitúa esta decisión junto a las de tiempo, repetición y respuesta alternativa.