For concurrent HTTP calls, should I reach for raw Fibers, ReactPHP/AMPHP, or Swoole?
Question
We run a Laravel service that, on a single request, has to call about 200 different upstream APIs and merge the results. Right now we do them sequentially and, unsurprisingly, the request latency is terrible. I want to make these concurrent, but I can't decide whether to use raw Fibers in PHP 8.3, an established async runtime like ReactPHP/AMPHP, or just move to Swoole. Do raw Fibers actually buy me anything over an established runtime?
Answer
Short answer: raw Fibers on their own buy you nothing here.
Short answer
For HTTP fan-out the real choice is curl_multi/Guzzle Pool if you stay in FPM, AMPHP v3 if you want ergonomic async, and Swoole only if you’re re-architecting.
Why
-
Raw Fibers are a primitive, not a solution. A Fiber only gives you cooperative scheduling; by itself it provides neither an event loop nor non-blocking I/O. To actually run HTTP calls in parallel you’d have to write the socket layer, the scheduler, and the promise logic yourself — which is exactly what AMPHP and ReactPHP already exist to do. Dropping to raw Fibers is reinventing those libraries.
-
AMPHP v3 = ergonomic async/await built on Fibers. It uses Fibers under the hood but gives you a clean
async/awaitAPI plusamphp/http-client. No callback hell. It runs on a standard PHP install and scales when things get complex (dependent calls, streaming). -
ReactPHP is mature but promise/callback-style. It does the same job and has been solid in production for years. But in new code AMPHP’s async/await reads far better; for a greenfield service I’d pick AMPHP.
-
Swoole is a different world. It needs the extension, hooks blocking I/O, and requires you to leave the FPM model for a long-running worker. Throughput is high, but so are the operational cost, the risk of state leaking between requests, and incompatibility with some extensions. I wouldn’t pay that price just for 200 calls.
What to do
-
The boring option is usually the right one: curl_multi / Guzzle Pool. If you just need N concurrent HTTP calls,
curl_multi(or Guzzle’s Pool that wraps it) does true parallel requests inside plain PHP-FPM. No new extension to install — it uses the curl extension that’s already there — no runtime swap, deployment stays identical. -
The real issue isn’t the runtime, it’s that number 200. 200 open connections at once punish both you and the upstreams. Set a concurrency cap of 20-50, put a timeout on every call, and handle partial failure (one bad call shouldn’t sink the batch).
use GuzzleHttp\Client; use GuzzleHttp\Pool; use GuzzleHttp\Psr7\Request; $client = new Client(['timeout' => 3.0]); $requests = static function (array $urls) { foreach ($urls as $u) { yield new Request('GET', $u); } }; $pool = new Pool($client, $requests($urls), [ 'concurrency' => 25, // at most 25 in flight at once 'fulfilled' => fn($res, $i) => $results[$i] = (string) $res->getBody(), 'rejected' => fn($reason, $i) => $errors[$i] = $reason, // partial failure ]); $pool->promise()->wait();
Bottom line: personally I’d stay in FPM and start with a Guzzle Pool, pin concurrency to 20-50, and handle timeouts and partial failures properly — it’s the simplest thing you can deploy today. If the codebase genuinely grows into async, I’d move to AMPHP v3. I’d only consider Swoole if moving the service to a long-running model is already on the table. And I wouldn’t reach for raw Fibers at all unless I were writing a library.
Comments
Sign in with your GitHub account to join the discussion. Comments are stored in GitHub Discussions.