Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама
Переклади 02 листопада 2026

Воркери черг Laravel тримають у пам'яті 8 копій застосунку

Це переказ статті Ando Hakobyan Your Laravel queue workers keep 8 copies of your app in memory - зі збереженням його структури, цифр і коду.

Автор додавав воркери Horizon на 2 ГБ інстанс Laravel Cloud, доки пам'ять не заповнилася приблизно на 90%. Вийшло 27 воркерів. Потім він змусив воркери ділити між собою скомпільований код і повторив тест: той самий сервер, той самий застосунок, ті самі 600 завдань на хвилину. Цього разу сервер витримав 210 воркерів.

Це був тест пам'яті, а не швидкості: за такого темпу завдань процесор ніколи не ставав вузьким місцем, а пам'ять - ставала. Щоб запустити ті самі 210 воркерів у стандартному режимі, знадобився інстанс на 16 ГБ.

Усе почалося з того, що кілька серверів автора сиділи у swap, і чималу частину пам'яті на них займав саме Horizon.

Кожен воркер завантажує весь застосунок

Кожен queue:work - це окремий PHP-процес. Так само окремим процесом є кожен воркер Horizon і, що здивувало автора, кожен супервізор Horizon. Кожен із них завантажує Laravel і компілює код, якого торкається, у власну приватну пам'ять.

У прикладі конфігурації Supervisor з документації Laravel стоїть numprocs=8. Якщо ви його скопіювали, у вас вісім процесів, і кожен тримає власну копію того самого скомпільованого застосунку.

У бенчмарку автора в Docker стандартний воркер Horizon займав близько 63 МБ. Найцікавіше - супервізори. Супервізор ніколи не виконує завдань, він лише стежить за своїми воркерами, але все одно займав близько 56 МБ. Шість супервізорів - це приблизно 340 МБ ще до того, як виконалося перше завдання.

PHP-FPM розв'язав це багато років тому

FPM запускає один master-процес, налаштовує в ньому спільну пам'ять opcache і вже потім форкає воркери. Усі воркери FPM бачать ту саму спільну пам'ять, тож код, скомпільований одним воркером, повторно використовують усі інші.

Master-процес ніколи не завантажує застосунок - він лише володіє кешем. Як з'ясувалося, у цьому й полягає весь фокус.

Воркери черг нічого з цього не мають. Supervisord чи systemd запускає кожен php artisan queue:work як окремий, зовсім новий процес, і спільного батька, який володів би кешем, немає. Opcache у CLI за замовчуванням вимкнений. Якщо ввімкнути його через opcache.enable_cli=1, кожен процес отримає власний приватний кеш - і це все одно одна копія на воркер.

А opcache.file_cache, прогрітий у CI, економить час компіляції, але не пам'ять: кожен воркер однаково завантажує собі власну копію.

Перевірте свої сервери

Перш ніж вірити цифрам автора, подивіться на свої. Рахуйте PSS, а не RSS. RSS враховує спільну пам'ять повністю для кожного процесу, тож сума завищена. PSS ділить спільні сторінки між процесами, які їх використовують, і сума виходить чесною.

Запустіть скрипт від імені користувача, під яким працюють воркери (або через sudo):

for pid in $(pgrep -f '^[^ ]*php[^ ]* .*artisan (queue:work|horizon(:supervisor|:work)?)( |$)'); do
  awk '/^Pss:/ {print $2}' "/proc/$pid/smaps_rollup" 2>/dev/null
done | awk '
  { total += $1; count++ }
  END {
    if (count) printf "%d processes, %.0f MB total\n", count, total / 1024
    else print "No matching processes found"
  }'

Скрипт рахує також master і супервізори Horizon, бо пам'ять вони коштують так само. Якщо на одному сервері кілька застосунків, він складе їх усіх, тож за потреби звузьте шаблон.

Запишіть результат - це ваше «до». Яка його частина припадає на код, а яка на дані самих завдань, залежить від застосунку, і показати це може лише «після».

Чому не Go

Перенесення воркерів на Go розв'язало б проблему з пам'яттю - так зробили, наприклад, у Sentinel. Але в застосунках автора завдання і є застосунком: вони використовують ті самі моделі, сервіси й хелпери, що й усе інше. В одному застосунку 17 класів завдань тягнуть за собою приблизно половину кодової бази - близько 167 файлів із 400. Переписати це означає переписати пів застосунку, а потім тримати дві версії синхронними. Composer-пакет виявився дешевшим рішенням.

Спершу fork, потім завантаження

Очевидне рішення на PHP - завантажити застосунок один раз і форкати воркери з нього. Проблема в тому, що діти успадковують. Кожен отримує відкриті сокети батька до бази даних і Redis, тож два воркери починають говорити через одне з'єднання. Кожен також стартує з тим станом, який залишило завантаження, а будь-який випадковий стан, ініціалізований до fork, може повторитися в кількох дітей.

Тому queue-prefork робить те саме, що master FPM: форкає ще до того, як з'являється застосунок. Маленький процес-шаблон стартує з opcache і без жодного коду застосунку. Кожен воркер форкається з нього і вже потім сам завантажує застосунок через звичайну точку входу artisan, як будь-який стандартний воркер. Перший воркер компілює код у спільний opcache шаблону, а всі наступні знаходять його вже готовим.

Кожен воркер, як і раніше, відкриває власні з'єднання і будує власний контейнер і конфігурацію. Спільний у них лише скомпільований код.

Стандартний Horizon поруч із Horizon з queue-prefork: кожен стандартний процес тримає власну скомпільовану копію застосунку (разом 1279 МБ), а форкнуті процеси ділять одну копію (240 МБ)

Для звичайного queue:work процес, який запустив supervisord, залишається крихітною заглушкою /bin/sh з тим самим PID. Менеджер процесів і далі ним керує, а queue:restart працює як завжди. З Horizon master, супервізори й воркери стають легкими форками. Подробиці - у README.

Цифри

Автор запустив поруч два однакові Docker-стеки на тому самому потоці завдань: Laravel 13.38 з приблизно 45 продакшн-пакетами, PHP 8.4 і Horizon 5.50.1 на віртуальній машині з 2 CPU. Пам'ять - медіана сумарного PSS із вимірюванням щохвилини. Цифри prefork включають його власні шаблони, заглушки й fork-сервери.

Конфігурація Стандартно Prefork
8 × queue:work, типові завдання 645 МБ 192 МБ (-70%)
8 × queue:work, простій 455 МБ 102 МБ (-78%)
8 × queue:work, важкі завдання 1025 МБ 524 МБ (-49%)
Horizon, 6 супервізорів з автомасштабуванням 1279 МБ 240 МБ (-81%)

Horizon автомасштабувався в обох прогонах: у медіані до 14 воркерів у стандартному режимі і до 17 з prefork. Тобто prefork тримав більше воркерів у меншому обсязі пам'яті. Жодне завдання не впало, пропускна здатність була однаковою.

Навантаження на процесор теж знизилося - з 21,3% до 11,9% одного ядра для Horizon з типовими завданнями, бо новий воркер не перекомпільовує те, що вже є в кеші, а лише завантажує Laravel.

Важкі завдання економлять менше, бо їхні власні дані однаково приватні. Стандартний варіант працював з вимкненим opcache у CLI, як його постачає php.ini-production. На Laravel Cloud він увімкнений, тож там кожен стандартний воркер тримає ще й власний приватний кеш. Стенд для бенчмарку лежить у репозиторії, якщо захочете повторити.

Це тестове середовище. У реальному продакшені одна з конфігурацій Horizon автора зменшилася приблизно з 1,2 ГБ до приблизно 280 МБ.

Повернімося до сервера Laravel Cloud з початку статті. Там стандартний воркер коштував близько 56 МБ, а форкнутий - близько 7,4 МБ. Так 27 воркерів перетворилися на 210 на інстансі Flex 2 ГБ. Ті самі 210 воркерів у стандартному режимі зайняли 11 766 МБ, а найменший інстанс, куди вони вміщаються, - Pro 16 ГБ за $128 на місяць замість $24 (зате з 4 vCPU замість одного, хоча процесора за такого темпу завдань і так вистачало).

Чотири хвилини, які автор списав на свій код

На Cloud усі 210 воркерів з'являлися близько чотирьох хвилин, і автор був певен, що повільний його fork-сервер. Але стандартний Horizon поводиться так само - це стартовий розгін самого Horizon.

Супервізор стартує з floor((minProcesses + maxProcesses) / 2) воркерів. Далі він додає balanceMaxShift воркерів (за замовчуванням 1) кожні balanceCooldown секунд (за замовчуванням 3). А з balance => 'simple' параметр 'processes' => N задає лише максимум - мінімум лишається 1.

Тож супервізор на 150 воркерів стартує зі 75 і ще майже чотири хвилини добирає решту. Це поведінка Horizon 5.50.1, інші версії автор не перевіряв. Щоб отримати весь пул одразу:

'balance' => 'simple',
'processes' => 150,
'minProcesses' => 150,

З балансуванням auto натомість збільште balanceMaxShift.

Це працює і зі стандартними воркерами, але тоді кожен воркер компілює застосунок одночасно з іншими, а це чимале разове навантаження на процесор. З форкнутими воркерами це дешево. У тесті на 2 CPU 100 воркерів були готові приблизно через 1,7 секунди після старту супервізора проти 8-14 секунд у стандартному режимі. На великій багатоядерній машині, де стандартний варіант мав би показати себе краще, автор цього не перевіряв.

Як спробувати

Пакет розрахований на Linux-сервери з Laravel 11-13, PHP 8.2+ і, якщо ви його використовуєте, Horizon 5.23+. Він не допоможе, коли в кожному контейнері чи поді працює один воркер (зокрема керовані черги Laravel Cloud і Vapor): ділитися нема з ким. Воркер, що працює як PID 1 у Docker, лишається стандартним, тож дайте контейнеру init (init: true).

composer require tevkert/queue-prefork
php artisan queue-prefork:doctor    # на сервері воркерів: як працюватимуть ваші воркери і чому
php artisan queue-prefork:status    # пам'ять зараз, ваш стандартний стан «до»

Пакет вмикається одразу після встановлення для воркерів, запущених після цього. Розгорніть код, перезапустіть воркери через queue:restart або horizon:terminate, дайте їм прогрітися і запустіть status ще раз - це ваше «після».

Якщо якась перевірка безпеки не пройде, наприклад, розширення відкрило сокети чи потоки під час старту, воркери просто працюватимуть у стандартному режимі, а пакет запише в лог причину. Щоб вимкнути пакет, встановіть QUEUE_PREFORK=false, перекешуйте конфігурацію і перезапустіть воркери.

На Laravel Cloud запускайте Horizon як власний фоновий процес командою php -d grpc.enable_fork_support=1 artisan horizon.

Пакет поширюється під ліцензією MIT і доступний на GitHub.

Підсумок

Автор просить читачів поділитися своїми цифрами: скільки воркерів працює на сервері, що показує скрипт і які завдання вони виконують, - бо поки що він міряв лише власні застосунки.

Від нас: навіть якщо пакет вам не потрібен, скрипт підрахунку PSS і налаштування minProcesses для Horizon варто взяти собі вже зараз. А сам queue-prefork з'явився лише кілька днів тому (v1.0.x), тож спершу виміряйте своє «до», перевірте пакет на staging і лише потім вмикайте на продакшені.

Переклад оригіналу: dev.to
16

Читати в документації

Коментарі

Увійдіть, щоб залишити коментар

Будьте першим, хто залишить коментар!

Читайте також

Переклади 04 листопада 2026

Спільна мова для PHP: навіщо стандартній бібліотеці SortDirection і Time\Duration

Переказ колонки Тіма Дюстерхуса в блозі PHP Foundation: чому крихітні типи SortDirection і Time\Duration у PHP 8.6 важать більше за свій розмір, чому від'ємні тривалості заклали з першої версії і що дадуть Time\Instant та Time\Clock.

4
Переклади 03 листопада 2026

Як приймати вебхуки в Laravel і не обпектися: підписи, захист від повторів та ідемпотентність

Покроково будуємо надійний приймач вебхуків у Laravel: перевірка HMAC-підпису через hash_equals, вікно часу проти повторного відтворення, унікальний індекс замість перевірки «чи існує», обробка через чергу та ідемпотентне завдання з тестом на справжній підпис.

22

Вакансії за темою

Middle PHP developer

Middle PHP розробник для високонавантаженої веб-платформи Social Casino/Online Casino: розвиток продукту, перехід монолітного проєкту на мікросервіси (Symfony), оптимізація баз даних, написання unit та functional тестів. Стек: PHP 7.4, Symfony 4, Yii, PostgreSQL 14, Redis, RabbitMQ, NGINX. Вимоги: 3-4+ роки продуктового досвіду з PHP, Yii, Symfony, PostgreSQL, розуміння REST API, Git, ООП та SOLID. Досвід з брокерами повідомлень і DDD буде плюсом.

Tech Lead — Fintech

Tech Lead для фінтех-платформи бізнес-витрат і платежів: активна розробка, архітектурні рішення, стабільність продакшену та менторство команди. Ключовий стек: PHP, Laravel/Lumen, MySQL, Redis, Docker, знання JavaScript (Vue.js, React на базовому рівні). Вимоги: розуміння життєвого циклу карткових транзакцій і криптовалют, досвід побудови надійних фінансових систем (баланси, звірка, аудит, безпечні API), DevOps-навички на Linux.

Bewort Нова
Вчора

Senior Full-Stack Developer (PHP/Laravel, Vue)

Розробка та розвиток ключових функцій корпоративної платформи на Laravel з обробкою бекенду та фронтенду на Vue. Потрібні глибокі знання внутрішньої архітектури фреймворку, досвід застосування SOLID, DDD, подій і черг, а також досвід роботи зі складними проєктами. Обов'язкове активне використання AI-інструментів для написання коду та англійська не нижче B2. Переваги: досвід із Vue, мультитенантними SaaS, AWS, CI/CD та LLM-інтеграціями.

Пакети за темою

Kalimera

welldigit/kalimera

Interactive installer that scaffolds Laravel applications the Well Digit way.

v1.0.0 16

Bagisto

bagisto/bagisto

Bagisto — це платформа для електронної комерції, побудована на Laravel. Вона надає готове рішення для створення та управління інтернет-магазинами з підтримкою каталогу товарів, замовлень, платежів та клієнтів.

28,331 v2.5.0 13 28