Если вы переходите с PHP-FPM на RoadRunner, Laravel Queue или Symfony Messenger — вы увидите один и тот же эффект:
Память растёт ступеньками и никогда не падает.
40 → 200 → 350 МБ… и так до перезапуска.
Unset, GC, collect_cycles() — не помогают.
Это не утечка. Это архитектура PHP.
🧬 Почему так происходит
PHP использует Zend Memory Manager:
он выделяет память крупными чанками (2–4 МБ) и не отдаёт их ОС назад, даже если внутри всё освобождено.
Каждый пик — новый baseline.
Обработали 100k записей → память выросла → процесс будет держать этот объём до конца жизни.
🔥 Что вызывает «разбухание»
ORM
->all() или ->get() на десятки тысяч записейбольшой файл в
file_get_contents()накопление массивов
сложные eager-loading графы ORM
🛠 Как проектировать правильно
✔️ Стриминг вместо коллекций:
User::lazy()->each(fn($u) => processUser($u));
или:
Record::chunk(100, function ($rows) {
foreach ($rows as $r) processRecord($r);
});✔️ Doctrine:
foreach ($q->toIterable() as $u) {
process($u);
$em->detach($u);
}✔️ Изоляция тяжёлых операций в функцию — память освобождается при выходе из scope.
✔️ Ротация воркеров — обязательна:
Laravel:
php artisan queue:work --max-jobs=1000
RoadRunner:
pool:
max_jobs: 1000
Messenger:
messenger:consume --limit=1000
📌 Выводы
🔸 Память в долгоживущем PHP-процессе не уменьшится сама по себе.
🔸 Проектируйте под пиковое потребление.
🔸Используйте стриминг, чанки, detach().
🔸 Регулярно перезапускайте воркеры.
🔗 Medium
🐸 Библиотека пхпшника
