Skip to content

Bulkhead

The bulkhead pattern (inspired by Polly's BulkheadPolicy and Resilience4j's Bulkhead) limits the number of concurrent calls to a service, preventing one slow dependency from monopolising all resources.


The problem

Without isolation, a single slow or failing dependency can consume all concurrency and block unrelated services:

Without bulkhead:
  slow-api: 50 concurrent calls (all waiting 30s)
  fast-api: 0 slots left ← blocked by slow-api

With bulkhead (maxConcurrent: 5):
  slow-api: max 5 calls at a time
  fast-api: completely unaffected ✓

Basic usage

typescript
client.bulkhead({ maxConcurrent: 20 })

Requests beyond maxConcurrent are rejected immediately by default.


With queue

typescript
client.bulkhead({
  maxConcurrent: 20,    // max in-flight at any moment
  maxQueue: 100,        // queue up to 100 excess requests
  queueTimeoutMs: 3_000 // reject queued requests after 3 s
})

When a slot frees up, the oldest queued request is promoted.


Configuration

OptionTypeDefaultDescription
maxConcurrentnumberMax in-flight requests
maxQueuenumber50Max requests in the waiting queue
queueTimeoutMsnumberundefinedReject queued request after this many ms

Error types

ErrorWhen
Error('Bulkhead queue full')active >= maxConcurrent AND queue.length >= maxQueue
Error('Bulkhead queue timeout')Queued request waited longer than queueTimeoutMs

Observability

typescript
client
  .on({ onBulkheadReject: ({ active, queued }) =>
    metrics.increment('bulkhead.rejected', { active, queued })
  })
  .bulkhead({ maxConcurrent: 20, maxQueue: 100 })

Sizing guide

Service typemaxConcurrentmaxQueue
Critical, fast API50–100200
Non-critical, slow API5–1020
Background jobs2–510
High-throughput microservice100–200500

TIP

Start conservative and tune up. It's better to queue than to let one service take down others.

Released under the MIT License.