Debounced Queued Event Listeners у Laravel: як працює нова функція для оптимізації черг

Перекладено ШІ 0 Laravel News 22 серпня, 2026

Laravel 13.26 пропонує елегантний спосіб уникнути перевантаження черг завдяки новому атрибуту `#[DebounceFor]`. Тепер замість десятків зайвих операцій система автоматично групує події в один запуск, суттєво заощаджуючи ресурси сервера.

Під час імпорту товарів один і той самий запис може оновлюватися сорок разів на хвилину. Це провокує сорок запусків події ProductUpdated та стільки ж запусків listener, який перебудовує пошуковий індекс. Черга сумлінно виконує завдання, хоча 97% цієї роботи — марна трата ресурсів, адже кожне наступне оновлення просто перезаписує попередній стан. Оптимальне рішення — об'єднати серію ідентичних подій в один запуск listener із актуальними даними наприкінці циклу.

Laravel 13.6 представив механізм об'єднання для queued jobs у вигляді debounceable queued jobs. Тепер Laravel 13.26 розширює цю функціональність: завдяки внеску @stevebauman у #61169, атрибут #[DebounceFor] став доступним для queued event listeners. Це дозволяє оптимізувати подієво-орієнтований код без переписування listener у звичайні джоби, що викликаються вручну.

Раніше цю прогалину заповнювали сторонні пакети, як-от Laravel Debounce, але тепер фреймворк пропонує власне нативне рішення.

# Як працює Debouncing для Listener

Просто додайте атрибут до queued listener та вкажіть час затримки (debounce window) у секундах:

use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\Attributes\DebounceFor;

#[DebounceFor(30, maxWait: 120)]
class UpdateProductSearchIndex implements ShouldQueue
{
    public function debounceId(ProductUpdated $event): string
    {
        return (string) $event->product->getKey();
    }

    public function handle(ProductUpdated $event): void
    {
        ProductIndexer::index($event->product->fresh());
    }
}

Тепер усі події ProductUpdated для товару №42 протягом 30-секундного вікна призведуть лише до одного виклику handle() для останньої події в серії. Події для товару №43 обробляються окремо, оскільки метод debounceId() створює унікальний ідентифікатор для кожного продукту. Якщо debounceId не вказано, усі виклики listener ділитимуть одне вікно затримки — це зручно для таких задач, як «перебудувати мапу сайту», що не залежать від конкретного ресурсу. Також можна використовувати звичайну властивість $debounceId, якщо вона статична.

Механізм працює за принципом «перемагає останній». Кожна нова подія додає listener у чергу з Delay, що дорівнює вікну затримки, і записує токен власника у кеш (ключ формується з назви класу та debounce ID). Новий виклик перезаписує токен. Коли старі копії в черзі нарешті доходять до виконання, вони бачать, що більше не володіють токеном, і завершуються. Важливо враховувати нюанс: навіть поодинока подія для неактивного товару все одно чекатиме повне вікно затримки (у прикладі — 30 секунд), перш ніж виконатися.

# maxWait та проблема «голодування»

Чистий debouncing має слабке місце: якщо потік подій не переривається довше за вказане вікно (наприклад, 30 секунд), виконання listener може відкладатися нескінченно. Для запобігання цьому існує параметр maxWait. З налаштуванням #[DebounceFor(30, maxWait: 120)], якщо події постійно зсувають вікно протягом 120 секунд, наступний виклик виконається без затримки. Таким чином, при тривалому імпорті оновлення індексу відбуватиметься приблизно раз на дві хвилини, замість сорока разів або жодного.

На затримку впливають два фактори: явний Delay, встановлений на рівні listener, має пріоритет над розрахованим debounce-часом. Також listener може перевизначити сховище кешу для токенів через метод debounceVia() — це корисно, якщо дефолтний кеш не є спільним для всіх серверів, що генерують події.

# Основні правила

Перед використанням варто знати про три обмеження:

  • Ніякого ShouldBeUnique. Ці функції мають протилежну логіку (перший виграв проти останній виграв). Поєднання їх призведе до LogicException під час відправки події.
  • Область дії — listener, а не подія. Інші обробники події ProductUpdated працюватимуть як зазвичай; об’єднується лише той listener, де вказано атрибут. Debouncing — це властивість конкретної роботи, а не всього потоку подій.
  • Обробник бачить тригерну подію, тому оновлюйте дані. Об’єкт події, що вижив після debouncing, — це останній відправлений екземпляр. Але навіть він може застаріти до моменту виконання. Саме тому в прикладі використовується $event->product->fresh(). Сприймайте подію як вказівник на ресурс, а не як остаточне джерело даних.

Саме перечитування даних із бази робить debouncing безпечним: якщо listener заново отримує стан із БД, об’єднання сорока запусків в один змінює лише вартість операції, а не її результат.

# Що ще почитати

Популярні

Інше, що варто прочитати

17 Оновлено 26 червня, 2026

Обробка геопросторових даних за допомогою Laravel Magellan

Ви готові відкрити нові горизонти у роботі з геопросторовими даними в Laravel? Дізнайтеся, як за допомогою PostGIS та пакету Laravel-Magellan можна легко зберігати, запитувати та маніпулювати інформацією про розташування, перетворюючи ваші проекти на вражаючі рішення у сфері картографії та геолокації!

15 Оновлено 26 червня, 2026

Локальні моделі та їх скоупи в Laravel за допомогою атрибута Scope

В Laravel 12 ми отримали можливість використовувати новий підхід для визначення локальних скоупів у моделях Eloquent. Дізнайтеся, як новий атрибут #[Scope] спрощує цей процес і зберігає ваші назви методів незмінними

19 Оновлено 26 червня, 2026

Використання штучного інтелекту для управління перекладами в Laravel

Досліджуйте нові можливості локалізації вашого Laravel-додатку з пакунками, які використовують штучний інтелект, такими як ChatGPT та Claude. Які рішення можуть спростити ваш процес перекладу та зробити його більш точним? Читайте далі, щоб дізнатися більше!