Skip to the guide
All guides

Comparison with other libraries

A recorded comparison of Reqtide with six other PHP libraries on 21 behavior scenarios: the method, the results, the limits of the test and how to repeat it.

app Reqtide PHP 0.1.0checked reading 4 minutes

On this page

What was compared

The harness lives in benchmarks/comparison/ (opens in a new tab) in the repository. It has 21 scenarios written without the name of any library in them. Two examples are "the total deadline spans retries and waits" and "a late release cannot free someone else's permit".

Each scenario was run against a pinned release of six other libraries through a small adapter. It was also run against the public API of Reqtide. All time is virtual, so nothing sleeps and the results repeat.

A scenario is graded only for libraries whose scope can express it. A rate limiter is not graded on retries. That is why most cells say "not applicable".

Library (version tested)Scope as exercisedPassFailN/A or not expressible
Reqtide (working tree before 0.1.0)A pipeline of all policies, memory backends, virtual clock1810
symfony/rate-limiter 8.1.6Rate limiting3115
symfony/http-client 8.1.8Retry and throttling clients2611
caseyamcl/guzzle_retry_middleware 3.0.0Retry middleware3313
spatie/guzzle-rate-limiter-middleware 2.2.0Rate-limit middleware1216
ackintosh/ganesha 4.0.0Circuit breaker3412
matheus85/duat 0.2.0Callable pipeline667
Results of the 21 scenarios for each library, by version tested.

Two more scenarios are informational and are not counted. Pass and fail together do not add up to the same total in every row, so counts are not comparable across rows. A library with a narrow scope has few graded scenarios by design.

How to read the results

  • The same author wrote the scenarios, the adapters for the other libraries and the Reqtide adapter. The scenarios came from the gap research of the project itself, so they may favor what Reqtide is built to do.
  • Some Reqtide results rest on choices that the adapter made. Reqtide has no defaults, so the adapter picked its values. Six scenarios depend on such choices or on a lenient reading: retry budget size, POST not retried by default, abandoned probe, late release, typed rejections and clock regression. The harness flags each one. The other libraries ran on their own defaults.
  • S15 is the one Reqtide failure, and it follows from the design. A request cancelled after admission and before dispatch keeps its rate unit, because Reqtide never refunds a rate charge. The scenario counts that as a failure. The other libraries could not run the same sub-case, so this row is not like for like. Two of them under-count quota when the limiter sits outside the retry layer, and the harness grades them as composition-dependent.
  • S12 can be contested. The scenario says that peak concurrency must never exceed one. No lease-based bulkhead can satisfy that literally: once a lease expires, the first callback may still be running when the next one is admitted. The harness grades the cell on whether a third caller is let in while the second is running.
  • A passing cell is narrow. It means the harness saw the expected behavior in that one scenario with that configuration.
  • Only process-local memory and a virtual clock were exercised. The comparison says nothing about speed, shared state, real transports or production readiness. For ganesha only its APCu adapter ran, against an in-process stand-in for the APCu functions.
  • Versions move. Other libraries ship fixes. The results describe the versions in the table on the day of the run.

Using the comparison

  • If you need only one protection, use the library built for it. A dedicated rate limiter or circuit breaker may fit better than a pipeline.
  • Reqtide offers the combination: one fixed order of decisions, one deadline across all of them, typed results, and tests under virtual time.
  • If a difference in the table matters to you, run the scenario against your own configuration.

Reproduce the run

The harness needs PHP 8.4 or newer, because two of the compared libraries require it. From a clone, with Docker, install the pinned dependencies once. Then run the harness:

Bash
docker compose -f tools/docker/compose.yaml run --rm -e COMPOSER_VENDOR_DIR=/app/build/comparison/vendor php84 composer install -d benchmarks/comparison
docker compose -f tools/docker/compose.yaml run --rm php84 php benchmarks/comparison/run.php

The README of the harness has the details. See also Limits.