A bulkhead per dependency, sized from the worker count

Fifty PHP-FPM workers all waiting on one slow upstream is a site that is down because of a service most of its pages do not use.

// workers ÷ upstream latency = the maximum request rate.
// 50 workers, a 20s upstream: 2.5 req/s before exhaustion.

// a semaphore in Redis, capping concurrent calls to ONE dependency
if (! $this->semaphore->acquire('payments', $limit = 10, $ttl = 5)) {
    throw new UpstreamBusy('payments');
}

try {
    return $this->gateway->authorise($card);
} finally {
    $this->semaphore->release('payments');
}

Rejecting immediately when the cap is reached is the point — a queue behind the bulkhead is the worker pool again with extra steps. The TTL on the semaphore entry is what recovers from a worker that died holding one, and getting it wrong in either direction is either a leak or a cap that does not cap. A timeout bounds one request and only a concurrency cap bounds the pool, which is the arithmetic people miss.