Skip to content

Bulkhead

Marco Breveglieri edited this page Jul 20, 2026 · 1 revision

Bulkhead Pattern Guide

The Bulkhead pattern limits the number of concurrent executions of an operation, isolating resources so that one overloaded workload cannot exhaust the whole system.

When to Use

  • Protecting a limited resource (a connection pool, a worker pool, a downstream service) from being overwhelmed
  • Isolating one workload from another, so a spike in one area does not starve the rest of the application
  • Capping parallel work submitted to a slow or expensive dependency

When NOT to Use

  • Operations that are cheap and cannot meaningfully contend for a shared resource
  • When you need calls to queue and wait for a free slot rather than fail immediately - Bulkhead rejects on saturation, it does not queue
  • As a substitute for a real connection/thread pool with its own admission control

Quick Start

uses Murphy.Policy.Bulkhead;

var
  Policy: IBulkheadPolicy;
begin
  Policy := TBulkheadBuilder
    .Handle([])
    .Limit(10)
    .Build;

  Policy.Execute(procedure begin ProcessRequest; end);
end;

Common Scenarios

Protecting a Connection Pool

procedure QueryDatabase;
var
  Policy: IBulkheadPolicy;
begin
  Policy := TBulkheadBuilder
    .Handle([])
    .Limit(5) // No more than 5 concurrent queries
    .Build;

  Policy.Execute(
    procedure
    begin
      Database.Execute('SELECT * FROM orders');
    end);
end;

Handling Rejection

var Policy: IBulkheadPolicy;
begin
  Policy := TBulkheadBuilder.Handle([]).Limit(20).Build;

  try
    Policy.Execute(procedure begin ProcessRequest; end);
  except
    on E: EBulkheadRejectedException do
      WriteLn('Rejected: too many concurrent requests');
  end;
end;

Monitoring Current Load

var Policy: IBulkheadPolicy;
begin
  Policy := TBulkheadBuilder.Handle([]).Limit(10).Build;

  WriteLn(Format('Currently executing: %d/10', [Policy.ExecutionCount]));
end;

Combined with Retry

Retry a rejected call after a short wait, giving the bulkhead a chance to free a slot:

uses Murphy.Policy.Retry, Murphy.Policy.Bulkhead, Murphy.Policy.Wrap;

var Policy: IPolicyWrap;
begin
  Policy := TPolicyWrapBuilder.Wrap([
    TRetryBuilder.Handle(EBulkheadRejectedException).Retry(3).Wait(TTimeSpan.FromMilliseconds(200)),
    TBulkheadBuilder.Handle([]).Limit(10)
  ]);

  Policy.Execute(procedure begin ProcessRequest; end);
end;

Best Practices

  1. Size the limit to the resource, not to guesswork - base it on pool size, thread budget, or measured capacity
  2. Rejection is immediate - the bulkhead never queues; pair with Retry if callers should wait and try again
  3. Use one bulkhead instance per resource - create a separate policy for each pool or workload you want to isolate
  4. Handle() is not used for filtering - every call counts against the limit regardless of exception type
  5. Monitor ExecutionCount - useful to tune the limit or expose it in health checks

See Also


Back to Index

Clone this wiki locally