How We Use Laravel Octane + Swoole to Handle Concurrent Load Without Rewriting Our App
Direct answer: We use Laravel Octane with Swoole to keep the Laravel application booted in memory across requests, then scale with Swoole workers and Octane::concurrently for parallel work — handling meaningfully higher concurrent load without throwing away the app and starting over in Go or Node.
Docs we treat as source of truth: laravel.com/docs/octane. At MarginTop Solutions, Octane is an ops and architecture decision — not a slide for impressing investors.
I want to be upfront about something before we go further: I'm not going to give you fabricated RPS numbers. Concurrent throughput depends on query complexity, payload size, worker count, database connection pool limits, and your specific hardware. Anyone publishing a single RPS claim without those variables is selling marketing, not engineering. What I will share is the reasoning, the architecture shift, and the real ops caveats we learned the uncomfortable way.
The actual problem Octane solves
In classic PHP-FPM, every single HTTP request triggers a full Laravel bootstrap cycle. That means loading the container, resolving service providers, parsing routes, and binding dependencies — from scratch — on every request. For simple apps this is invisible. Under concurrent load, it adds up fast.
Community benchmarks (see web-frameworks benchmarks and the Swoole team's own published results) consistently show Octane + Swoole handling 3–10× more requests per second compared to PHP-FPM on equivalent hardware for framework-heavy endpoints. The variance is enormous — a simple JSON endpoint might see 8× gain; a heavy Eloquent-relational endpoint with multiple joins might see 2×. The gain comes from one thing: eliminating framework boot time from the hot path.
How the request model changes
| Aspect | PHP-FPM (classic) | Octane + Swoole |
|---|---|---|
| Framework boot | Every request (~5–20ms per boot) | Once on startup |
| Service container | Rebuilt per request | Warm and reused |
| Concurrency model | Multiple FPM OS processes | Swoole coroutines + workers |
| Parallel in-request work | Not available; sequential only | Octane::concurrently for parallel coroutines |
| State risk | Low — process dies per request | High — must reset request-scoped state manually |
| Deployment complexity | Low — FPM restarts cleanly | Higher — must manage graceful reloads and worker recycling |
The Octane::concurrently feature deserves a specific mention. When a request needs three independent data lookups — say, a user's subscription status, notification count, and a live configuration value — you can run all three as parallel coroutines instead of chaining them sequentially. On a typical endpoint making 3 independent DB or cache calls at ~20ms each, concurrent execution can save 30–40ms of latency. That's meaningful for dashboards and client-facing APIs where p95 latency matters.
Why Octane beats a premature rewrite
The alternative we're always tempted to propose is "just rewrite it in Go or Node." I've seen that conversation go badly more than once. Here's the practical math:
- Domain code doesn't migrate for free. A medium-sized Laravel app with 80+ models, custom validation rules, permission matrices, and queue handlers takes 6–18 months to rewrite faithfully in another stack — and during that migration window, business logic keeps changing.
- Dual-stack maintenance is expensive. Running both stacks during transition means double the infra cost, double the on-call surface, and engineers split between shipping new features and porting old ones.
- You can still extract hot services later. A modular monolith with clear domain boundaries can have a high-throughput edge service peeled off when the evidence demands it — see why we stay a monolith. You don't need to blow up the whole thing on day one.
Octane buys you time. You get meaningfully better concurrency characteristics without abandoning years of accumulated domain knowledge baked into Laravel code.
The ops caveats nobody puts in the tutorial
Octane's power comes with real operational complexity. We learned some of these the hard way:
- Memory leaks and static state will find you. When your app lives across requests, any static property or singleton that accumulates data across calls will grow until the worker crashes or — worse — serves stale data to the wrong user. Common offenders: static collections in service classes, event listeners registering themselves again on each request, third-party packages that weren't designed for long-running PHP.
- In-memory singletons bleed between users. If a service resolves the authenticated user at boot and caches it on
$this, request B will see request A's user. We've encountered this with over-eager caching in custom service providers. The fix: mark what needs flushing inconfig/octane.phpunder theflusharray. - Deployments require discipline. You can't just push code and have Swoole pick it up. The old code lives in memory until you send a reload signal. Missing this during a hotfix at 2am while production is down is a bad time.
- Not every package is Octane-compatible. Some packages assume a stateless per-request lifecycle. Run
php artisan octane:checkand audit packages manually — particularly anything that caches to static properties or hooks into Laravel's request lifecycle in non-standard ways.
When to actually reach for Octane
We don't recommend Octane as a default starting point. The operational overhead is real and not worth paying before you have a measured problem. Our heuristic:
- Reach for Octane when profiling shows framework boot time is a meaningful share of p95 latency, or when your FPM worker pool saturates under normal traffic.
- Don't reach for Octane when the bottleneck is a slow query, missing index, N+1 Eloquent call, or third-party API timeout. Octane won't fix any of those — it'll just make them fail faster under higher concurrency.
- Consider horizontal FPM scaling first if ops simplicity matters more than squeezing concurrency. More FPM workers behind a load balancer is boring and it works.
For where PHP performance may go next with AOT compilation, see What TypePHP Means for PHP Performance.
Key takeaways
- Octane boots Laravel once and keeps it in memory; the main gain is eliminating per-request framework boot cost.
- Community benchmarks show 3–10× RPS gains over PHP-FPM on framework-heavy endpoints — workload-dependent, not a guarantee.
Octane::concurrentlymeaningfully reduces latency on endpoints making multiple independent calls in parallel.- Statefulness, package compatibility, and deployment discipline are the real ops cost. Audit before you ship.
- Profile first. A missing database index does more damage than PHP-FPM boot time.
Stack context: why we chose Laravel. Scope discipline: what not to build.
Evaluating Octane for a production Laravel product? Reach out — we've done this migration and have the scars to prove it.