¿Qué es el patrón bulkhead (compartimentos estancos) y cómo evita que una dependencia que falla derribe todo tu servicio?
Un proveedor de pagos lento no debería poder congelar todo tu flujo de checkout — los bulkheads aíslan los recursos de cada dependencia para que un fallo quede contenido.
APRENDE HACIENDOPon a prueba tu intuición3 pasos
¿Qué es el patrón bulkhead (compartimentos estancos) y cómo evita que una dependencia que falla derribe todo tu servicio?
Usa los ejemplos y la explicación de la lección para comprobar tu idea.
Reflexión personal. No se envía a ningún servicio de IA.#El barco que no se hunde por un solo agujero
Los "bulkheads" (mamparos) toman su nombre de los compartimentos estancos del casco de un barco. Si un torpedo perfora un compartimento, ese compartimento se inunda, la tripulación sella las puertas a su alrededor, y el resto del barco sigue a flote y navegando. Nadie construye un barco como una única sala gigante bajo cubierta, porque una sola perforación en cualquier punto hundiría todo el navío. La mayoría de los sistemas backend, dejados a su arquitectura por defecto, están construidos exactamente como esa sala gigante.
#El modo de fallo del que te proteges
Imagina un servicio de pedidos que llama a tres dependencias externas — pagos, inventario y un motor de recomendaciones — y las tres llamadas pasan por el mismo cliente HTTP compartido, respaldado por un único pool de 50 threads. En condiciones normales esto funciona bien: cada llamada es rápida, los threads vuelven al pool enseguida, y 50 threads sirven cómodamente a las tres dependencias.
Ahora el motor de recomendaciones empieza a dar timeout — quizás por un pico de carga, quizás por una consulta a base de datos que se degradó. Cada petición hacia él ahora tarda 30 segundos en vez de 30 milisegundos. Los threads que llaman al motor de recomendaciones dejan de volver al pool porque quedan bloqueados esperando una respuesta. En cuestión de segundos, los 50 threads están atascados esperando a la dependencia lenta. En ese momento, las peticiones a pagos e inventario — ambas completamente sanas — ya no tienen ningún thread disponible para ejecutarse. Se encolan y también empiezan a fallar por timeout. Una sola dependencia lenta acaba de derribar llamadas a dos servicios sin ninguna relación entre sí, y perfectamente sanos. Esto es un fallo en cascada por agotamiento de recursos, y es una de las formas más comunes en que un incidente "menor" en una dependencia se convierte en una caída total.
#Qué hace realmente un bulkhead
El patrón bulkhead particiona el recurso compartido — normalmente un pool de threads, un pool de conexiones, o un semáforo que limita la concurrencia — de modo que cada dependencia externa recibe su propia porción acotada en lugar de tomar de una única bolsa compartida. Si el cliente de pagos recibe 15 threads, inventario recibe 15, y recomendaciones recibe 15 (más una pequeña reserva compartida), entonces que recomendaciones dé timeout puede agotar sus propios 15 threads, pero los otros 30 siguen atendiendo a pagos e inventario sin interrupción. El fallo queda contenido en el compartimento donde se originó.
#Dos formas de construir el muro
| Enfoque | Cómo funciona | Compromiso |
|---|---|---|
| Aislamiento por pool de threads | Cada dependencia externa recibe su propio executor/pool de threads dedicado y su propia cola | Aislamiento fuerte, incluso frente a una llamada que bloquea el propio thread, pero cuesta más memoria y overhead de cambio de contexto al tener muchos pools |
| Aislamiento por semáforo | Un semáforo con contador limita cuántas llamadas concurrentes a una dependencia están en vuelo, reutilizando el propio thread del llamador | Barato — no requiere threads extra — pero una llamada que realmente cuelga el thread llamador lo sigue ocupando, dando un aislamiento más débil |
El aislamiento por semáforo suele bastar cuando la librería cliente respeta los timeouts de forma fiable y nunca bloquea el thread llamador indefinidamente; el aislamiento por pool de threads es la opción segura por defecto cuando no confías del todo en eso.
#Dimensionar los compartimentos
Un bulkhead demasiado pequeño se convierte en un cuello de botella artificial incluso cuando la dependencia externa está perfectamente sana — te autolimitas. Un punto de partida razonable viene de la Ley de Little: el número de slots concurrentes que necesitas es aproximadamente el throughput esperado (peticiones por segundo) multiplicado por la latencia esperada (en segundos), más margen para la varianza y los reintentos. Una dependencia llamada 100 veces por segundo con una latencia p99 de 50ms necesita aproximadamente 5 slots concurrentes en régimen estable — pero se dimensiona para la cola de la distribución, no para el promedio, y se añade margen para los picos de tráfico y las tormentas de reintentos, que son justo el momento en que los bulkheads importan más.
#Los bulkheads se combinan con los circuit breakers, no los sustituyen
Un bulkhead limita cuántos de tus recursos puede consumir una dependencia que falla; no impide que sigas llamando a esa dependencia. Si recomendaciones está completamente caída, seguirás quemando tus 15 threads asignados en cada petición, agotando el timeout de cada una individualmente. Un circuit breaker colocado delante de esa misma llamada detecta la tasa de fallos elevada y empieza a cortocircuitar las peticiones de inmediato — fallando rápido o devolviendo una respuesta cacheada o por defecto — en lugar de dejar que cada uno de esos 15 threads agote un timeout completo. Usados juntos, el bulkhead limita el radio de la explosión y el circuit breaker corta la hemorragia en cuanto el patrón queda claro.
#La misma idea, en varios puntos de tu stack
El principio del bulkhead aparece mucho más allá de los pools de threads: un pool de conexiones a base de datos dedicado por servicio externo para que una consulta de reporting cargada de locks no le quite conexiones al camino de checkout, límites de tasa o colas por tenant en un sistema multi-tenant para que un cliente ruidoso no degrade a todos los demás, y dedicar node pools o pods separados a cargas sensibles a la latencia frente a trabajos batch en Kubernetes. El mecanismo cambia, pero el objetivo es siempre el mismo: un fallo en un compartimento debe ser observable, contenido y recuperable — no algo que drene silenciosamente un recurso del que depende también el resto del sistema.
🧠 Check your understanding
0/3 · 0/3 answered1. En el escenario del pool de threads compartido, ¿por qué las dependencias sanas empiezan a fallar cuando solo una dependencia está lenta?
2. ¿Cuál es el principal compromiso del aislamiento por semáforo frente al aislamiento por pool de threads?
3. ¿Por qué se usan juntos los bulkheads y los circuit breakers en lugar de que uno sustituya al otro?