DEV Community

Cover image for Laravel async vs Octane
Edmond
Edmond

Posted on

Laravel async vs Octane

How much can you speed up Laravel if you handle requests in coroutines instead of blocking workers? We benchmarked TrueAsync (native PHP coroutines) against three Laravel Octane configurations.

The Benchmark

Environment

  • OS: WSL2 (Linux 5.15), 16 cores, 7.8 GB RAM
  • DB: PostgreSQL 16 (max_connections=500)
  • Load tool: k6, constant-arrival-rate, 1,000 req/s for 30 seconds
Parameter TrueAsync FrankenPHP Octane Swoole (NTS) Octane Swoole (ZTS) Octane FrankenPHP
PHP 8.6.0-dev (ZTS) 8.5.4 (NTS) 8.5.4 (ZTS) 8.5.4 (NTS)
Server FrankenPHP (true-async fork) Swoole 6.2.0 Swoole 6.2.0 FrankenPHP (official)
Laravel 13.2.0 13.2.0 13.2.0 13.2.0
Model Coroutines (libuv) Processes (fork) Threads (ZTS) Processes (fork)

Note: Swoole runs without coroutine mode here because Laravel is not adapted for it. In a pure synthetic test with coroutines, Swoole shows slightly better numbers than FrankenPHP + TrueAsync. Both servers reach ~10,000 req/s with 12 workers on synthetic loads.

Full benchmark repository: github.com/YanGusik/ta_benchmark

Workload

The /bench endpoint executes 10 sequential SQL queries against PostgreSQL: user lookup, post listing, INSERT a view record, UPDATE a counter, aggregations, TOP-N selections. Database: 100 users, 1,000 posts, growing post_views table.

This is a realistic workload, not a synthetic "Hello World".

Results

Throughput (req/s)

Workers TrueAsync Swoole NTS Swoole ZTS FrankenPHP Octane
4 989 183 185 189
8 993 342 341 346
12 990 483 476 489
16 987 599 601 556

With 16 workers, TrueAsync handles 987 req/s. The best Octane result is 601 req/s (Swoole ZTS), 64% less with the same worker count.

We gave blocking servers 16 workers to be generous. TrueAsync doesn't need them. Four workers handle 989 req/s, the same as sixteen. Coroutines yield on every PDO::query(), so one worker runs dozens of requests concurrently. While one coroutine waits for PostgreSQL, others keep working.

Throughput and Memory

Median Latency (P50)

Workers TrueAsync Swoole NTS Swoole ZTS FrankenPHP Octane
4 28 ms 5,440 ms 5,320 ms 5,240 ms
8 27 ms 2,870 ms 2,900 ms 2,800 ms
12 28 ms 2,040 ms 2,050 ms 1,990 ms
16 29 ms 1,640 ms 1,660 ms 1,780 ms

29 ms vs 1,640 ms at 16 workers. 56x difference. Where do those seconds come from?

Latency

Phase TrueAsync (4w) Swoole (4w)
PHP execution ~5 ms ~5 ms
SQL I/O wait (10 queries) ~23 ms ~23 ms
Queue wait ~0 ms ~5,400 ms
Total ~28 ms ~5,440 ms

PHP and SQL run at identical speeds. The entire difference is queue wait: a blocking server can't start your request until the current one finishes. With TrueAsync, CPU utilization is higher because coroutines yield during I/O instead of blocking the worker.

No magic. Just better resource utilization.

Memory (under load)

Workers TrueAsync Swoole ZTS FrankenPHP Octane
4 277 MB 508 MB 401 MB
8 286 MB 600 MB 417 MB
16 308 MB 765 MB 403 MB

In the blocking model, each worker is a separate process with a full Laravel copy: container, configuration, router, middleware, database manager. With TrueAsync, coroutines share a common bootstrap. Only per-request data (request, session, auth) is duplicated. Hence 308 MB vs 765 MB at 16 workers.

Key Takeaways

Tests are not something you should fully trust. Different scenarios are possible. However, thereโ€™s no magic here.

Even at maximum workers, TrueAsync wins by 30-40%. You could spin up 22-27 blocking workers to match throughput, but you'd still lose on latency and memory. And why use 22 workers when 4 will do?

The bottom line: for IO-bound workloads (which is most web apps), TrueAsync serves the same traffic with 5-6x fewer workers, 56x lower latency, and half the memory.

Top comments (0)