Перший крок - виміряти. memory_get_usage() показує поточне споживання, memory_get_peak_usage() - максимум за весь час. Розставивши їх між етапами, швидко видно, де стрибок:
$before = memory_get_usage();
$rows = $repository->all();
logger()->info('rows loaded', ['mb' => (memory_get_usage() - $before) / 1024 / 1024]);
Далі - профайлер, який покаже пам'ять у розрізі функцій: Xdebug-профайлер, Blackfire, SPX, Tideways. Вони відповідають на питання «яка функція виділила ці 300 МБ».
Звичайні винуватці:
- Завантаження всього одразу:
Model::all(),file()для великого файлу,json_decodeвеличезної відповіді. Лікується порціями:chunkById(),lazy(), генератори, потоковий парсер. - Накопичення в довгому процесі: статичний масив-кеш, логер, що тримає записи,
DB::enableQueryLog()у воркері. - Циклічні посилання між об'єктами, які звільняє лише збирач сміття, а не лічильник посилань.
У довгоживучих процесах (воркери черг, Octane) навіть невеликий витік на кожному завданні з часом вбиває процес. Тому воркерам ставлять --max-jobs чи --memory, щоб вони періодично перезапускалися.