Home/Docs/bulkhead
BH

bulkhead

v0.2.1

@backendkit-labs/bulkhead

Limit concurrency. Queue the overflow. Shed excess load cleanly.

Overview#

Borrowed from naval architecture: isolate compartments so one breach does not sink the ship. The bulkhead pattern limits the number of concurrent calls to a downstream service — preventing a slow dependency from exhausting your thread pool or connection pool and bringing down unrelated features.

Unlike simple concurrency limiters (like p-limit), the bulkhead adds a wait queue with optional timeout: calls beyond maxConcurrent enter a FIFO queue and are executed as slots free up. If a call waits longer than queueTimeoutMs, it is rejected with a BulkheadTimeoutError. When even the queue is full, excess calls are shed immediately with { type: 'bulkhead_full' } — a clean, predictable signal your callers can act on.

How It Works#

Each Bulkhead instance maintains a counter of in-flight executions. When execute(fn) is called:

  • If in-flight < maxConcurrent → execute immediately.
  • If in-flight ≥ maxConcurrent and queue < maxQueue → enter FIFO queue.
  • If queued > queueTimeoutMs → reject with BulkheadTimeoutError.
  • If queue is full → return fail({ type: 'bulkhead_full' }) without waiting.

The BulkheadService aggregates metrics across all registered instances and automatically emits warnings when utilization is high — no manual monitoring setup required.

Configuration#

PropTypeDescription
namestringIdentifier for logs and metrics.
maxConcurrentnumberMaximum simultaneous in-flight executions.
maxQueuenumberMaximum callers waiting when all slots are busy. 0 = reject immediately (no queue).
queueTimeoutMsnumberMaximum time a call can spend waiting in the queue. Exceeded calls fail with BulkheadTimeoutError.

NestJS Integration#

The @WithBulkhead decorator wraps any service method. The BulkheadRegistry provides pre-tuned factory methods for the four most common patterns, eliminating trial-and-error threshold tuning.

app.module.ts
import { BulkheadModule } from '@backendkit-labs/bulkhead/nestjs';

@Module({ imports: [BulkheadModule.forRoot()] })
export class AppModule {}

@Injectable()
export class ExternalApiService {
  @WithBulkhead({ name: 'external-api', maxConcurrent: 10, maxQueue: 50, queueTimeoutMs: 5_000 })
  async fetch(payload: FetchPayload): Promise<Result<ApiResponse, BulkheadError>> {
    return this.http.post<ApiResponse>('/api/data', payload);
  }

  // Stack with circuit breaker for full resilience
  @WithCircuitBreaker({ name: 'external-api', failureThreshold: 30 })
  @WithBulkhead({ name: 'external-api', maxConcurrent: 10 })
  async criticalFetch(id: string): Promise<Result<ApiResponse, ResilienceError>> {
    return this.http.get<ApiResponse>(`/api/data/${id}`);
  }
}

BulkheadRegistry — pre-tuned factories

PropTypeDescription
getForClient(name)Bulkhead5 concurrent, queue 20, timeout 2 s — for per-client isolation.
getForService(name)Bulkhead20 concurrent, queue 200, timeout 10 s — for internal microservices.
getForDatabase(name)Bulkhead15 concurrent, queue 150, timeout 5 s — for database connection pools.
getForHttpExternal(name)Bulkhead8 concurrent, queue 50, timeout 10 s — for third-party HTTP APIs.

Examples#

From basic to production-grade — copy and adapt.

api.service.ts
import { Bulkhead } from '@backendkit-labs/bulkhead';

const bh = new Bulkhead({
  name: 'external-api',
  maxConcurrent: 10,
  maxQueue: 50,
});

const result = await bh.execute(() => externalApi.fetch(payload));

if (!result.ok) {
  if (result.error.type === 'bulkhead_full') {
    throw new TooManyRequestsException('Downstream overloaded');
  }
}
return result.value;

⚖️ vs. Alternatives#

Comparing against p-limit and bottleneck — the most common concurrency-limiting libraries in Node.js.

Feature@backendkit-labs/bulkheadp-limitbottleneck
Concurrency limit
Wait queue✅ maxQueue + rejection✅ Unlimited✅ Advanced
Explicit queue rejection✅ fail(bulkhead_full)❌ Never rejects⚠️ via options
Result<T,E> integration✅ Native❌ Returns promise❌ Callbacks
NestJS decorator✅ @UseBulkhead
Named instances (registry)
Rate limiting
Zero runtime deps
Weekly downloadsGrowing~50M~1M

✅ Supported  ·  ❌ Not supported  ·  ⚠️ Partial / workaround needed. Download counts are approximate weekly npm averages.