Skip to content
Muhammet Şafak
tr
Asked by: Yiğit Answered:

How do I debug memory leaks under Laravel Octane (FrankenPHP)?


Question

We run Laravel Octane with the FrankenPHP driver at `max-requests=500`. Under heavy traffic the workers' memory climbs fast, RAM balloons, and the server gets close to locking up. PHP-FPM clears memory after every request, but Octane is stateful — singletons, static variables, and third-party packages cause this leak. How do I debug it, and how do I optimize memory while keeping workers persistent?

Answer

Short answer: don’t guess, measure — in Octane the worker is long-lived, so anything that accumulates between requests leaks; track what’s piling up first, then reset the stateful side in the hooks.

The real issue: PHP-FPM assumes “a fresh process per request,” but Octane keeps the worker alive. Everything that breaks that assumption — growing static arrays, singletons holding request state, listeners/bindings registered per request, global stores (Log, Auth, the current request) that never reset — quietly inflates memory.

  1. Measure first, talk later. Log memory_get_usage(true) every N requests; if memory climbs monotonically you have a leak, if it oscillates and drops it’s normal. Drop a counter into Octane’s RequestTerminated hook and look at the shape of the curve. If it trends upward, move to the next step.
  2. Bisect to find the culprit. Disable service providers and suspect packages one at a time and re-measure; wherever the growth stops, that’s your leak. The offender is usually a third-party package that assumes a fresh process per request — invisible under PHP-FPM, explosive under Octane. Use a snapshot diff (the delta of memory_get_usage between two points) to narrow which type is accumulating.
  3. Reset stateful singletons in the hooks. Clear bindings that hold request state in the RequestReceived/RequestTerminated hooks, or via the flush list in config/octane.php. Leaving the singleton in the container but resetting the state inside it closes most leaks in a single line.
  4. Kill static accumulation, rebind services per request. Ever-growing static $cache = [] arrays, connections that never close, collections that swell per request — all classic. Don’t make a request-scoped service a singleton; rebind it each request so the data it carries dies with the request.
  5. Treat max_requests as a safety valve, not a cure. Recycling the worker after a set number of requests caps RAM but hides the root cause — lowering 500 just moves you from crashing fast to crashing late. Keep it on, but do the actual work by closing the leak.

Bottom line: if it were me, I’d order it like this — plot the curve with memory_get_usage first, narrow the culprit by bisecting service providers/packages, then reset stateful singletons in Octane’s hooks and clear static accumulation. Keep max_requests as a net but don’t lean on it. If you want Octane’s speed, you have to accept “the process doesn’t die per request” in every layer of your code.

Related Reading

Tags: #performance#laravel#octane
Share:

Comments

Sign in with your GitHub account to join the discussion. Comments are stored in GitHub Discussions.

More Questions

All questions

Search the site

Start typing to search posts, projects and pages.

Esc to close Powered by Pagefind