Beyond Circuit Breakers: Why the Bulkhead Pattern Matters
The Bulkhead Pattern is a resilience pattern that isolates failures so that a problem in one part of a system doesn't bring down the entire application.
The name comes from ships. A ship is divided into watertight compartments called bulkheads. If one compartment floods, the bulkheads prevent water from spreading to the rest of the ship, allowing it to stay afloat.
The same idea applies to software.
Without Bulkheads
Imagine an e-commerce application:
Order Service
│
┌─────────────┼─────────────┐
│ │ │
Payment API Inventory API Email API
Suppose the Email API becomes very slow.
If all outbound requests share the same thread pool:
Email requests occupy all available threads.
Payment requests must wait.
Inventory requests must wait.
Eventually, the entire Order Service becomes unresponsive.
A failure in one dependency cascades to everything else.
With Bulkheads
Each dependency gets its own isolated resources.
Order Service
│
┌──────────┬──────────┬──────────┐
│ │ │
Payment Pool Inventory Pool Email Pool
10 threads 10 threads 5 threads
If the Email API hangs:
Only the Email Pool is exhausted.
Payment requests continue.
Inventory requests continue.
The application remains largely functional.
Types of Bulkheads
1. Thread Pool Isolation
Each external service has its own thread pool.
Payment API
↓
Thread Pool A (20)
Inventory API
↓
Thread Pool B (15)
Notification API
↓
Thread Pool C (5)
One pool cannot consume another pool's threads.
2. Connection Pool Isolation
Instead of sharing one database connection pool:
Shared Pool (100)
Use separate pools:
Customer DB
Pool: 40
Order DB
Pool: 40
Reporting DB
Pool: 20
A spike in reporting queries won't starve order processing.
3. Resource Isolation
You can isolate:
CPU
Memory
Network bandwidth
Kubernetes pods
Containers
Virtual machines
For example, in Kubernetes:
Payment Service
3 Pods
Recommendation Service
20 Pods
Search Service
5 Pods
If the Recommendation Service has issues, it doesn't directly consume the resources allocated to Payment.
Java Example (Resilience4j)
Resilience4j provides a bulkhead implementation.
BulkheadConfig config = BulkheadConfig.custom()
.maxConcurrentCalls(10)
.maxWaitDuration(Duration.ofMillis(100))
.build();
Bulkhead bulkhead = Bulkhead.of("paymentService", config);
Supplier<String> decoratedSupplier =
Bulkhead.decorateSupplier(bulkhead, paymentClient::pay);
String result = Try.ofSupplier(decoratedSupplier)
.recover(ex -> "Service Busy")
.get();
In this example:
Only 10 concurrent calls to the payment service are allowed.
Additional requests wait up to 100 ms.
If capacity is still unavailable, the call fails quickly rather than consuming more resources.
Bulkhead vs Circuit Breaker
These patterns are often used together, but they solve different problems.
PatternPurposeBulkheadPrevent one failing dependency from exhausting shared resources.Circuit BreakerStop calling a dependency that is already failing.
For example:
The Email service becomes slow.
Bulkhead confines the impact to the email resources.
Circuit Breaker detects repeated failures and temporarily stops sending requests to the Email service.
The rest of the application continues operating.
Bulkhead vs Rate Limiter
BulkheadRate LimiterLimits concurrent work.Limits requests over time (e.g., 100 requests per second).Protects internal resources.Protects services from excessive request rates.
You can combine them:
Rate Limiter controls incoming traffic.
Bulkhead isolates resource usage.
Circuit Breaker avoids repeatedly calling unhealthy services.
Real-World Example
An online shopping platform has three downstream services:
Payment Gateway
Inventory System
Recommendation Engine
During a flash sale:
The Recommendation Engine slows dramatically due to high demand.
Without bulkheads, recommendation requests consume shared threads, delaying payment processing and causing checkout failures.
With bulkheads, only the recommendation requests are affected. Customers can still browse products and complete purchases, while recommendations may be delayed or temporarily unavailable.
This prioritizes the most important business functions.
Best Practices
Allocate separate thread or connection pools for critical dependencies.
Size each pool according to expected traffic and business priority.
Combine bulkheads with timeouts, retries, circuit breakers, and fallback mechanisms.
Monitor metrics such as concurrent calls, queue lengths, rejected requests, and latency.
Ensure that low-priority services cannot starve mission-critical ones.
Key Takeaway
The Bulkhead Pattern is about isolation. Instead of allowing every component to compete for the same resources, you partition resources so that failures remain contained.
Think of it this way:
Timeout: "Don't wait forever."
Retry: "Try again if appropriate."
Circuit Breaker: "Stop calling a service that's failing."
Bulkhead: "Don't let one failure consume everyone else's resources."
Rate Limiter: "Control how much traffic enters the system."
Together, these patterns form the foundation of resilient microservice architectures.
