Middle: питання на співбесіді з теми «Черги»
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
5 питань
Queue дозволяє відкласти важку роботу (Job) у фон, щоб не змушувати користувача чекати.
class ProcessPodcast implements ShouldQueue
{
public function handle(): void { /* важка робота */ }
}
ProcessPodcast::dispatch($podcast)->onQueue('media');
- Драйвери черг:
database,redis,sqs(config/queue.php). - Воркер обробляє завдання:
php artisan queue:work. - Підтримка повторів (
$tries,backoff), затримок (->delay()), middleware для завдань.
Типове застосування: email, обробка зображень, виклики зовнішніх API, генерація звітів.
Обидва групують завдання, але з протилежною метою.
Ланцюжок (Bus::chain) - послідовність. Наступне завдання стартує лише після успіху попереднього; якщо одне впало, решта не виконується.
Bus::chain([
new ProcessPodcast($podcast),
new OptimizePodcast($podcast),
new ReleasePodcast($podcast),
])->catch(fn (Throwable $e) => report($e))->dispatch();
Підходить, коли кроки залежать один від одного: не можна публікувати те, що ще не оброблено.
Пакет (Bus::batch) - паралельність. Завдання виконуються незалежно, кількома воркерами одночасно, а колбеки then, catch, finally спрацьовують, коли пакет завершено.
Bus::batch($users->map(fn ($u) => new SendNewsletter($u)))
->then(fn (Batch $batch) => Log::info('Розсилку завершено'))
->allowFailures()
->dispatch();
Підходить для великої кількості однотипної роботи: розсилка, імпорт частинами. Пакет показує прогрес, але порядку не гарантує.
Деталі: пакетам потрібна таблиця job_batches (make:queue-batches-table), а завдання - з трейтом Batchable. Скасування пакета не зупиняє вже взяті завдання, тож кожне перевіряє $this->batch()->cancelled() на початку.
Це обгортки навколо handle(), як middleware маршрутів навколо контролера. Вони виносять з завдання повторювану логіку: обмеження, блокування, умови пропуску.
public function middleware(): array
{
return [
new WithoutOverlapping($this->user->id),
new RateLimited('external-api'),
];
}
Вбудовані, які найчастіше потрібні:
WithoutOverlapping- не дає двом завданням з тим самим ключем виконуватися одночасно (оновлення балансу одного користувача).RateLimited- тримає частоту звернень у межах ліміту, оголошеного черезRateLimiter::for(); зайві завдання повертаються в чергу.ThrottlesExceptions- якщо зовнішній сервіс почав падати, перестає його смикати на якийсь час.Skip::when(...)- видаляє завдання без виконання за умовою, наприклад якщо замовлення вже скасоване.
Пастка: повернення в чергу через WithoutOverlapping чи RateLimited теж рахується як спроба. З однією спробою за замовчуванням таке завдання одразу потрапить у failed_jobs - тому для них збільшують #[Tries] або обмежують за часом.
Розвести їх по різних чергах і сказати воркеру, яку брати першою.
SendPasswordReset::dispatch($user)->onQueue('high');
SendNewsletter::dispatch($user); // у default
php artisan queue:work --queue=high,default
Порядок у --queue - це пріоритет: поки в high є завдання, default чекає. Лист для скидання пароля більше не стоїть за п'ятдесятьма тисячами розсилки.
Що врахувати:
- Голодування. Якщо
highніколи не порожніє,defaultне отримає жодного воркера. Надійніше дати важливим чергам окремі воркери, а не лише пріоритет. - Маршрутизація. У Laravel 13
Queue::route(SendPasswordReset::class, queue: 'high')у провайдері задає чергу для класу, тож не треба писатиonQueue()в кожному місці. - Horizon дозволяє описати супервізорів з різною кількістю процесів на чергу й балансувати їх автоматично.
Не сама модель, а її клас і первинний ключ. Воркер, беручи завдання, перечитує модель з бази.
class ProcessOrder implements ShouldQueue
{
use Queueable;
public function __construct(public Order $order) {}
}
Що з цього випливає:
- Дані свіжі на момент виконання, а не на момент постановки. Якщо замовлення встигли змінити, завдання побачить зміни.
- Завантажені зв'язки теж перечитуються - і роздувають payload. Їх відкидають
->withoutRelations()або атрибутом#[WithoutRelations]на властивості. - Модель могли видалити. Тоді завдання впаде з
ModelNotFoundException- без повторів. Якщо це нормальна ситуація (користувач видалив акаунт), атрибут#[DeleteWhenMissingModels]просто видалить завдання. - Незбережені зміни губляться. Атрибути, змінені без
save()передdispatch(), у завдання не потраплять - воркер прочитає те, що лежить у базі.
Тому в завдання передають модель або ID, але не масиви її даних: інакше воркер працюватиме із застарілою копією.