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

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] або обмежують за часом.

Докладніше в документації: Middleware завдань

Розвести їх по різних чергах і сказати воркеру, яку брати першою.

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, але не масиви її даних: інакше воркер працюватиме із застарілою копією.

Докладніше в документації: Зв'язки в черзі