Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Bulkhead

The bulkhead pattern isolates the resources used to call different dependencies, so that a slowdown or failure in one dependency cannot exhaust the resources needed to keep serving requests to healthy ones. The name is a nautical metaphor: a ship’s hull is divided into watertight compartments, so a breach in one compartment floods only that section instead of sinking the whole ship.

In practice, a bulkhead caps how many concurrent calls, connections, or workers may be in flight for a given dependency, using a semaphore, a dedicated connection pool, or a separate queue/worker group per dependency. When the cap is reached, further calls are rejected immediately, or shed to a fallback, rather than queued indefinitely behind an already-struggling dependency; this keeps one slow API or database from starving the resources that other, unrelated calls also depend on.

Bulkheads are often deployed alongside circuit breakers: the circuit breaker stops calling a dependency once it is clearly failing, while the bulkhead limits how much damage that dependency can do to the rest of the application while it is still being called.

PHP’s traditional one-request-per-process model already gives some bulkhead-like isolation between requests, but within a long-running process, such as a queue worker, a Swoole/RoadRunner server, or a script fanning out to several APIs, resources must be bounded explicitly: a semaphore-like construct such as a Swoole\Coroutine\Channel with a fixed capacity, a Redis counter with a TTL acting as a distributed semaphore, or a dedicated HTTP client with its own connection pool per dependency.

<?php

    // Bulkhead via a Swoole coroutine channel used as a counting semaphore
    $semaphore = new \Swoole\Coroutine\Channel(20); // capacity: 20 concurrent calls
    for ($i = 0; $i < 20; $i++) {
        $semaphore->push(true);
    }

    $semaphore->pop(); // acquire a slot (blocks if the pool is saturated)
    try {
        $response = callDependency();
    } finally {
        $semaphore->push(true); // release the slot
    }

?>

Documentation

See Also