💀 Как один ORM-запрос съел 300 МБ навсегда
История из нашего проекта: неоптимальный запрос через ORM загрузил ~100К записей из базы. Worker в RoadRunner вырос с 80 до ~500 МБ... и больше никогда не вернулся обратно.
Звучит как утечка памяти? Спойлер: это не баг, а особенность архитектуры PHP.
Zend Memory Manager выделяет память большими блоками — чанки по ~2-4 МБ. Когда вы делаете
Чанки остаются закреплёнными за процессом до его завершения. Это сделано ради производительности: повторное использование уже выделенной памяти быстрее, чем постоянные запросы к ОС.
Почему это критично для RoadRunner, Laravel Queue, демонов?
В PHP-FPM каждый запрос = новый процесс, память автоматически очищается. В long-running процессах один worker обрабатывает тысячи задач — и каждый "пик" памяти становится новым baseline навсегда.
Ну а теперь про то, как себя обезопасить от всего этого?
- Использовать батчинг для выборок
- Ограничивать объём данных в памяти в рамках одной операции
- Обрабатывать данные потоково, без загрузки всего набора сразу
- Выносить тяжёлые операции в отдельные функции для автоматического освобождения локальных переменных
- Применять
- Анализировать участки кода, создающие единовременные всплески в десятки МБ. Лучше не игнорировать всплески памяти, надеясь что “само освободится”
Что можно сделать на уровне PHP воркеров:
1. Можно использовать ротацию по количеству задач, например в RoadRunner
2. В RoadRunner — при необходимости включать soft-лимиты по памяти, при достижении которого воркер перезапускается.
Куда ещё может утекать память:
- Статические переменные, накапливающие данные
- Глобальные массивы, в которых растёт состояние
- Singleton-сервисы с неограниченными кешами
- ORM-кеши, которые не сбрасываются
- Списки подписчиков/слушателей, которые не удаляются
- Циклические ссылки объектов друг на друга.
История из нашего проекта: неоптимальный запрос через ORM загрузил ~100К записей из базы. Worker в RoadRunner вырос с 80 до ~500 МБ... и больше никогда не вернулся обратно.
Звучит как утечка памяти? Спойлер: это не баг, а особенность архитектуры PHP.
💡 Неочевидный факт: PHP никогда не возвращает память ОС в long-running процессах
Zend Memory Manager выделяет память большими блоками — чанки по ~2-4 МБ. Когда вы делаете
unset($data) или переменная выходит из scope — память освобождается ВНУТРИ PHP, но операционная система об этом не узнаёт.Чанки остаются закреплёнными за процессом до его завершения. Это сделано ради производительности: повторное использование уже выделенной памяти быстрее, чем постоянные запросы к ОС.
Почему это критично для RoadRunner, Laravel Queue, демонов?
В PHP-FPM каждый запрос = новый процесс, память автоматически очищается. В long-running процессах один worker обрабатывает тысячи задач — и каждый "пик" памяти становится новым baseline навсегда.
Ну а теперь про то, как себя обезопасить от всего этого?
- Использовать батчинг для выборок
- Ограничивать объём данных в памяти в рамках одной операции
- Обрабатывать данные потоково, без загрузки всего набора сразу
- Выносить тяжёлые операции в отдельные функции для автоматического освобождения локальных переменных
- Применять
gc_mem_caches() для очистки внутренних пулов, и молиться, что поможет именно в вашем случае)- Анализировать участки кода, создающие единовременные всплески в десятки МБ. Лучше не игнорировать всплески памяти, надеясь что “само освободится”
Что можно сделать на уровне PHP воркеров:
1. Можно использовать ротацию по количеству задач, например в RoadRunner
pool.max_jobs=1000, Laravel Queue --max-jobs=1000. После выполнения 1000 задач, воркер будет перезапущен. Но не стоит полагаться на ротацию как на замену оптимизации, ведь пик может возникнуть в первых же запросах.2. В RoadRunner — при необходимости включать soft-лимиты по памяти, при достижении которого воркер перезапускается.
Куда ещё может утекать память:
- Статические переменные, накапливающие данные
- Глобальные массивы, в которых растёт состояние
- Singleton-сервисы с неограниченными кешами
- ORM-кеши, которые не сбрасываются
- Списки подписчиков/слушателей, которые не удаляются
- Циклические ссылки объектов друг на друга.
1🔥42🤯10 9🤔2
Розыгрыш проходок на подлодку 🌈
Для регистрации в конкурсе, нужно:
1. Перейти по этой ссылке с секретным токеном (возможно, потребуется включить ускоритель интернета)
2. Ввести имя своего github-аккаунта
3. Нажать кнопку УЧАСТВОВАТЬ на плашке события "На подлодку!"
Для повышения шанса на победу, нужно всего лишь поставить звёздочки на все репозитории из списка.
Для регистрации в конкурсе, нужно:
1. Перейти по этой ссылке с секретным токеном (возможно, потребуется включить ускоритель интернета)
2. Ввести имя своего github-аккаунта
3. Нажать кнопку УЧАСТВОВАТЬ на плашке события "На подлодку!"
Для повышения шанса на победу, нужно всего лишь поставить звёздочки на все репозитории из списка.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥8🤮5 3
Forwarded from Open Source: PHP (Dmitrii)
True Async PHP
Edmond Dantes призывает всех к обсуждению особенностей реализации нашумевшего True Async RFC.
Если вы делаете фреймворки или библиотеки, которые работают (или хотелось бы) с асинхронностью; пишите на других языках с поддержкой асинхронности и параллелизма или просто хотите что-то добавить дельного, то заходите в обсуждение.
Чем быстрее закроются все вопросы, опасения и корнер кейсы, тем быстрее PHP начнет нагинать Go!
Цитата Edmond’а:
👩💻 https://github.com/true-async/php-true-async-rfc/discussions/8
Edmond Dantes призывает всех к обсуждению особенностей реализации нашумевшего True Async RFC.
Если вы делаете фреймворки или библиотеки, которые работают (или хотелось бы) с асинхронностью; пишите на других языках с поддержкой асинхронности и параллелизма или просто хотите что-то добавить дельного, то заходите в обсуждение.
Чем быстрее закроются все вопросы, опасения и корнер кейсы, тем быстрее PHP начнет нагинать Go!
Цитата Edmond’а:
Итак господа все желающие, приглашаю вас к обсуждению. На текущем этапе считаем, что никакого RFC не было. В теме ключевой вопрос. Он видимо и будет обсуждаться ближайшее время.
Please open Telegram to view this post
VIEW IN TELEGRAM
GitHub
Stage 1: Memory model and its impact on refactoring · true-async php-true-async-rfc · Discussion #8
Stage 1 — Memory model and its impact on refactoring At this stage of the discussion, I propose focusing on one of the three main questions. 🔑 Key Questions 1. Choosing the Coroutine Model How coro...
1🔥22🤔6