Skip to content

Octane Speeds Laravel Up by Breaking an Assumption

Every request in a normal Laravel application starts from nothing. Octane keeps the application in memory between requests, which is where the speed comes from and where the bugs come from too.

4 min read

The thing that makes PHP forgiving is that it forgets. A request arrives, the framework boots, the work happens, the process exits, and every variable created along the way disappears. A memory leak lasts milliseconds. A static property polluted with one user's data is gone before the next user arrives.

Octane removes that. The application boots once and stays in memory, handling request after request in the same process. Skipping the boot is where the speed comes from - and the forgetting was load-bearing in ways most codebases have never had to think about.

What stops being true

Static properties persist. A static cache that was per-request is now shared by every request the worker handles. If it is keyed by user, it is a data leak.

Singletons outlive the request that created them. A service resolved once and registered as a singleton will hold whatever it captured at first resolution - including, in the worst case, a request or an authenticated user.

Globals persist. Anything written to a superglobal or a global variable survives.

Leaks accumulate. An array that grows a little on each request used to be free. Now the worker's memory climbs until something restarts it.

The pattern that actually bites

It is rarely a static array. It is a service that took a dependency it should have resolved later:

// AppServiceProvider
$this->app->singleton(ReportBuilder::class, function ($app) {
    return new ReportBuilder($app->make(Request::class));
});

Registered as a singleton, this captures the first request the worker ever handles and keeps it. Every subsequent request gets a builder holding somebody else's input.

Under normal PHP this is harmless - the process dies, the singleton dies with it, and nobody ever notices the latent bug. Under Octane it is a cross-user data leak that appears intermittently and is almost impossible to reproduce from a report.

The fix is to resolve per-request state when it is needed rather than when the service is built:

$this->app->singleton(ReportBuilder::class, fn () => new ReportBuilder());
 
// and inside the method that needs it
public function build(Request $request): Report { ... }

The same applies to anything that captures the authenticated user, the current tenant, or the locale at construction time. On a multi-tenant platform this moves from an embarrassment to a serious incident, because the boundary between tenants is exactly what is being crossed.

Finding them before production does

Read the service providers first. Every singleton binding, and every bind whose closure resolves something request-shaped, is a candidate.

Then search for static properties that are written to rather than only read:

grep -rn "protected static \|private static \|public static " app/ | grep -v "function\|const"

Most will be legitimate. The ones that hold data rather than configuration are the list to work through.

Laravel provides hooks to reset state between requests, and packages register their own. They cover the framework's own services well and they know nothing about yours.

The part that is not about correctness

Even with the state handled, a long-running process has a different operational shape.

Memory has to be watched over hours, not minutes - a leak of a hundred kilobytes per request is invisible in a test and fatal overnight. Workers need a maximum request count so they are recycled before they degrade. And a deploy has to restart them, which is the same lesson as with queue workers: a long-lived process holds the code it booted with, so new code does not reach it until something makes it exit.

Is it worth it

Sometimes, and less often than the benchmarks suggest.

The boot is what Octane removes, and a request whose time goes on a slow query or a third-party call gains nothing at all. Profile a real endpoint first. If framework boot is a meaningful share of your response time, the gain is real; if the database is, fix the database.

And if the application has singletons capturing request state, fix those regardless of what you decide about Octane. They are bugs today - they are just bugs that PHP has been quietly covering for.

On a multi-tenant platform, a captured tenant crosses the one boundary the product is sold on. It is the most expensive version of this bug. Restarting the workers on deploy closes the other one, for the reason set out here.

Whether the boot is actually costing you anything is a measurement, and it is the first thing we do.

Related questions

Is it safe to put an existing application on it?
Not without reading the application first. It is safe for most code and quietly wrong for a specific set of patterns - anything holding request-specific state on something that lives longer than a request. The failure is data belonging to one user appearing for another, which is the worst category of bug to find in production.
How much faster is it actually?
It depends entirely on what your requests spend their time doing. An application whose time goes on booting the framework gains a great deal; one whose time goes on a slow query gains nothing, because the query still takes as long. Measure where the milliseconds are before assuming this is the fix.
What is the safest way to adopt it?
Fix the unsafe patterns first, while still running normally - they are bugs in waiting either way. Then enable it in a non-production environment with a long-running soak test, watch memory over hours rather than minutes, and roll it out behind something you can turn off quickly.
Can we get the benefit without it?
Often. Much of what people hope for from Octane is available from caching configuration and routes, from opcode caching with preloading, and from fixing whatever is actually slow. Those are lower risk and they are worth exhausting first.

← Back to all articles

Call us+1 848 272 7583WhatsApp+90 850 308 5436Emailinfo@codefacture.comContact page