Published
Memory growth under FrankenPHP, and the move to Swoole
Two Laravel applications I ran on FrankenPHP grew in memory and CPU use over time with no matching rise in traffic. The cause was not a bug in the application code, but a mismatch between how I had built it and the persistent-worker model FrankenPHP uses instead of the traditional PHP-FPM request cycle.
- PHP
- FrankenPHP
- Swoole
- Laravel
- memory management
- production reliability
The symptom: growth with no matching load
The affected applications used noticeably more RAM and CPU over time than the traffic they served would explain — the kind of climb that looks less like normal usage and more like something not being released. There was no single moment it broke; it just kept climbing until it started causing service interruptions.
Why PHP-FPM hides this and FrankenPHP does not
On PHP-FPM, each request runs in a fresh worker process and its memory is released when the request ends, so most accumulated state or forgotten references disappear automatically. FrankenPHP instead keeps the application booted in a long-running worker process across many requests, closer to how a persistent server in another language behaves. Anything that quietly accumulates between requests — a static property, a growing array on a long-lived service, a container binding holding on to a reference — keeps growing instead of resetting.
I had not designed the applications with that distinction in mind when I containerised them; the difference only became clear once I went looking for the cause of the memory growth.
The fix: moving to Swoole
I migrated the affected applications from FrankenPHP to Swoole. Swoole runs Laravel the same persistent way, but with a lower memory overhead in this case, at the cost of somewhat lower request throughput than FrankenPHP. For these applications, avoiding the recurring memory growth mattered more than the throughput difference, so the trade-off was worth making.
What this changes when I use a persistent PHP runtime now
The practical lesson generalises beyond this one runtime pair: moving a Laravel application from PHP-FPM to any persistent worker model means auditing it for state that used to be reset for free on every request. I now treat that audit as part of adopting a persistent runtime, not an optional afterthought, and I check memory behaviour under sustained load before relying on the deployment in production.