Новий пакет Laravel Chores від Amr Lotfy Saleh розв'язує типову проблему масових операцій з даними: коли обробка мільйонів записів перервалася через перезапуск сервера, важко визначити, на якому саме записі зупинилися. Пакет обгортає одноразові операції з даними в пачки (batches), які зберігають прогрес у базі даних після кожної пачки. Якщо процес переривається, наступний запуск продовжить роботу з того місця, де зупинився, замість того щоб починати спочатку. Дизайн пакету натхненний гемом maintenance_tasks для Rails від Shopify.
Ключові можливості
Пакет надає такий функціонал:
- Збереження прогресу (checkpointed progress) - ID останнього обробленого запису записується в таблицю
chore_runs після кожної пачки, тому аварійне завершення або Ctrl+C втрачає максимум одну пачку прогресу
- Keyset pagination - пачки обробляються за первинним ключем, а не за offset, що уникає класичної помилки, коли оновлення рядків зміщує їх з частини, яку ви ітеруєте
- Ізоляція помилок - запис, що викликає виняток, логується в таблицю збоїв і пропускається, а виконання продовжується
- Без додаткової інфраструктури - стан зберігається у вашій базі даних, без потреби в Redis, queue workers або зовнішніх сервісах
- Шість Artisan-команд - створення шаблону, запуск, список, пауза, перегляд збоїв та повторна спроба
- CI-friendly вивід - режим JSON-виводу та чіткі коди виходу:
0 для чистого завершення, 1 для завершення зі збоями, 2 для фатальної помилки
Створення Chore
Chore - це клас з двома методами: collection() повертає запит для записів, які потрібно обробити, а process() обробляє один запис. Створити шаблон можна командою php artisan make:chore:
namespace App\Chores;
use AmrLotfy\Chores\Chore;
use App\Models\User;
use Illuminate\Contracts\Database\Eloquent\Builder;
class NormalizePhoneNumbers extends Chore
{
public int $batchSize = 500;
public function collection(): Builder
{
return User::whereNotNull('phone')
->where('phone', 'not like', '+%');
}
public function process($record): void
{
$record->update([
'phone' => PhoneNumber::parse($record->phone, 'EG')->toE164(),
]);
}
}
Це весь клас. Розбиття на пачки, відстеження прогресу та логування помилок відбуваються автоматично. Розмір пачки за замовчуванням становить 500 записів через конфігураційний файл, якщо ви не вказали його в класі.
Запуск, пауза та відновлення
Команда chore:run виконує chore з відображенням прогресу в реальному часі в терміналі:
php artisan chore:run NormalizePhoneNumbers
Прогрес зберігається після кожної пачки, тому та сама команда відновлює виконання, яке було перерване через deploy, аварію або Ctrl+C. Команда chore:pause зупиняє виконання на межі наступної пачки, а chore:list показує доступні chores разом з історією їх виконання.
Одне застереження щодо гарантій: збереження прогресу відбувається після пачки, а не після окремого запису, тому записи всередині пачки, яка виконувалась під час переривання, можуть бути повторно перевірені після відновлення. По можливості пишіть process() ідемпотентним - наведений вище приклад з номерами телефонів безпечний, оскільки вже нормалізовані номери більше не відповідають запиту collection().
Для регулярних завдань, таких як очищення застарілих записів, можна використовувати команду разом з планувальником завдань Laravel:
$schedule->command('chore:run PurgeExpiredRecords')->monthly();
Обробка помилок
Запис, що викликає виняток, не зупиняє виконання. Пакет логує запис і виняток у таблицю збоїв, пропускає його та продовжує роботу. Коли виконання завершується, ви можете переглянути та повторно обробити записи зі збоями як окрему операцію:
php artisan chore:failures NormalizePhoneNumbers
php artisan chore:retry NormalizePhoneNumbers
Такий підхід важливий для тривалих операцій: сотня некоректних номерів телефонів з десяти мільйонів не повинна зупиняти чотиригодинне завдання, а повторна обробка сотні записів після виправлення набагато дешевша, ніж повторне виконання всього процесу.
Технічні особливості та обмеження
Chores виконуються на передньому плані з одним воркером на chore, а колекція потребує упорядковуваного первинного ключа, такого як auto-increment або ULID. Виконання в черзі та паралельні воркери знаходяться в дорожній карті; якщо ви хочете розподілити масове оновлення між queue workers вже сьогодні, пакет Queue-SQL використовує такий підхід.
Встановлення
Laravel Chores вимагає PHP 8.2+ та Laravel 12 або 13, підтримує MySQL, PostgreSQL та SQLite:
composer require amrlotfy/laravel-chores
php artisan vendor:publish --tag=chores-migrations
php artisan migrate
Конфігураційний файл дозволяє змінити місце розташування класів chore (за замовчуванням app/Chores), розмір пачки за замовчуванням, назви таблиць та інтервал паузи між пачками для зменшення навантаження на завантажену базу даних.
Вихідний код, документацію та дорожню карту можна знайти в репозиторії Laravel Chores на GitHub.