// Resilience & Messaging · 06
Bulkhead
Separate resource pools, so one flooded feature can't sink the ship.
How it fits together
- Storefront uses / calls Pricing pool (1 GET price)
- Pricing pool fails / error path Storefront (503 at once)
- Pricing pool uses / calls Pricing service (2 forward)
- Storefront uses / calls Search pool (1 GET search)
- Search pool uses / calls Search service (2 forward)
Spot it
- One slow API stalls every other endpoint
- Thread pool or connection pool is exhausted
- A reporting job slows down checkout
It costs
Partitioned capacity sits idle in one pool while another is saturated.
In code
public sealed class PricingClient(IPricingApi api){ private readonly ConcurrencyLimiter _bulkhead = new(new ConcurrencyLimiterOptions { PermitLimit = 50, QueueLimit = 20, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, }); public async Task<decimal> GetPriceAsync(string sku, CancellationToken ct) { using var lease = await _bulkhead.AcquireAsync(1, ct); if (!lease.IsAcquired) { throw new InvalidOperationException("Pricing is saturated; fail fast."); } return await api.GetPriceAsync(sku, ct); }}Go deeperThe full explanation and what to ask for · 1 min readExpandCollapse
Named after a ship's watertight compartments: a breach floods one section, not the whole hull. In software the compartments are thread pools, connection pools, concurrency limits, queues or whole deployments.
The classic mistake is a shared resource nobody counted as one: a single database pool, the connection limit of one HTTP client, the thread pool under blocking calls. Size each bulkhead from measured concurrency (throughput × latency), and reject fast when full instead of queueing forever.
Bulkheads contain the damage, circuit breakers stop the calls, timeouts bound how long a slot stays taken. Without a timeout a slow dependency still fills its bulkhead; it just stops spreading.
Ask for it
- isolation
- concurrency limiter
- queue limit
- resource pool
- blast radius
- load shedding
Isolate the pricing and search clients with a concurrency limiter each (pricing 50 in flight, queue 20; search 20, queue 10), rejecting the rest at once with 503. Move the reporting consumer to its own deployment with its own database connection pool.