Налаштування планувальника завдань (Cron-like scheduler) для фонових процесів

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

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

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Налаштування планувальника завдань (Cron-like scheduler) для фонових процесів
Середня
від 1 робочого дня до 3 робочих днів
Часті питання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Налаштування планировщика задач (Cron-like scheduler) для фоновых процесів

Системний cron — стандартний інструмент для періодичних завдань, але має обмеження: складність управління, відсутність історії виконання, відсутність обробки помилок, не працює в контейнерах без додаткового налаштування. Фреймворкові планировщики вирішують ці проблеми, додаючи керування через код.

Laravel Task Scheduler

Принцип роботи: одна запись у системному cron викликає планировщик кожну хвилину, а він сам вирішує, які завдання запустити:

# /etc/cron.d/laravel
* * * * * www-data php /var/www/artisan schedule:run >> /dev/null 2>&1

Всі розписання визначаються у routes/console.php (Laravel 9+) або app/Console/Kernel.php:

// routes/console.php
use Illuminate\Support\Facades\Schedule;

// Artisan-команди
Schedule::command('reports:daily')->dailyAt('02:00');
Schedule::command('sitemap:generate')->hourly();
Schedule::command('cache:clear-expired')->everyFifteenMinutes();

// Диспетчеризація Queue Job
Schedule::job(new CleanupOldUploadsJob())->weekly()->sundays()->at('03:00');
Schedule::job(new SyncExchangeRatesJob(), 'high')->everyThirtyMinutes();

// Довільний код
Schedule::call(function () {
    DB::table('sessions')->where('last_activity', '<', now()->subDays(30))->delete();
})->daily()->name('cleanup-sessions')->withoutOverlapping();

// Shell-команда
Schedule::exec('node scripts/process-queue.js')->everyFiveMinutes();

Важливі модифікатори

withoutOverlapping() — не запускати завдання, якщо попередній запуск ще не завершився. Критично для довгих завдань:

Schedule::command('import:products')
    ->hourly()
    ->withoutOverlapping(10); // блокування на 10 хвилин

runInBackground() — не чекати завершення команди перед наступною. Планировщик продовжує роботу, поки завдання виконується у окремому процесі:

Schedule::command('reports:generate')->daily()->runInBackground();

onOneServer() — при кількох серверах виконувати завдання лише на одному. Вимагає cache-драйвер з підтримкою атомарних блокувань (Redis, Memcached):

Schedule::command('newsletter:send')
    ->dailyAt('09:00')
    ->onOneServer()
    ->withoutOverlapping();

between() — обмежити часовий діапазон:

Schedule::command('process:orders')
    ->everyMinute()
    ->between('08:00', '22:00'); // лише в робочі години

when() / skip() — умовне виконання:

Schedule::command('sync:users')
    ->hourly()
    ->skip(fn() => app()->isDownForMaintenance());

Зберігання історії виконання

За замовчуванням Laravel не зберігає історію завдань. Додаємо через хуки onSuccess/onFailure:

Schedule::command('reports:daily')
    ->dailyAt('02:00')
    ->before(function () {
        ScheduleLog::create([
            'command'   => 'reports:daily',
            'status'    => 'started',
            'started_at'=> now(),
        ]);
    })
    ->onSuccess(function (\Illuminate\Foundation\Bus\PendingDispatch $pending) {
        ScheduleLog::where('command', 'reports:daily')
            ->latest()
            ->first()
            ?->update(['status' => 'success', 'finished_at' => now()]);
    })
    ->onFailure(function () {
        ScheduleLog::where('command', 'reports:daily')
            ->latest()
            ->first()
            ?->update(['status' => 'failed', 'finished_at' => now()]);

        Http::post(config('services.slack.webhooks.alerts'), [
            'text' => ":x: Scheduled task `reports:daily` failed",
        ]);
    });

Або використовуємо пакет spatie/laravel-schedule-monitor, який робить це автоматично для всіх завдань та інтегрується з Oh Dear для зовнішнього моніторингу.

Моніторинг через Healthcheck URL

Паттерн heartbeat: при успішному виконанні завдання пингує зовнішній сервіс (Healthchecks.io, Better Uptime, Dead Man's Snitch). Якщо пінг не прийшов — сервіс відправляє алерт:

Schedule::command('backup:run')
    ->daily()
    ->onSuccess(function () {
        Http::get('https://hc-ping.com/' . config('services.healthchecks.backup_uuid'));
    })
    ->onFailure(function () {
        Http::get('https://hc-ping.com/' . config('services.healthchecks.backup_uuid') . '/fail');
    });

Динамічні розписання з бази даних

Розписання з конфіга — це статика. Якщо потрібно керувати розписаннями через інтерфейс (наприклад, у кожного клієнта своя час відправки звіту):

// routes/console.php
use App\Models\ScheduledTask;

ScheduledTask::where('is_active', true)->each(function (ScheduledTask $task) {
    $event = Schedule::call(function () use ($task) {
        dispatch(new DynamicScheduledJob($task->id));
    })
    ->cron($task->cron_expression)
    ->name("dynamic-task-{$task->id}")
    ->withoutOverlapping();

    if ($task->only_on_weekdays) {
        $event->weekdays();
    }
});

Таблиця scheduled_tasks:

CREATE TABLE scheduled_tasks (
    id              BIGSERIAL PRIMARY KEY,
    name            VARCHAR(255) NOT NULL,
    cron_expression VARCHAR(100) NOT NULL,  -- '0 9 * * 1-5'
    job_class       VARCHAR(500) NOT NULL,
    payload         JSONB,
    is_active       BOOLEAN DEFAULT true,
    last_run_at     TIMESTAMP WITH TIME ZONE,
    created_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

Node.js: node-cron / agenda

Для Node.js-сервісів — node-cron (прості завдання) або agenda (з персистентністю у MongoDB):

// node-cron
import cron from 'node-cron';

cron.schedule('0 */2 * * *', async () => {
    console.log('Running every 2 hours');
    await syncExchangeRates();
}, {
    scheduled: true,
    timezone:  'Europe/Kiev',
});
// agenda з MongoDB — історія виконання з коробки
import Agenda from 'agenda';

const agenda = new Agenda({ db: { address: process.env.MONGODB_URI } });

agenda.define('send daily digest', async (job) => {
    await sendDailyDigest(job.attrs.data.userId);
});

await agenda.start();
await agenda.every('24 hours', 'send daily digest', { userId: 123 });

Supervisor для планировщика

У контейнерному середовищі (Docker) системний cron може бути недоступним або небажаним. Альтернатива — запускати schedule:work (з'явилася у Laravel 8):

php artisan schedule:work

Це процес, який сам стежить за розписанням без системного cron. У Dockerfile:

CMD ["php", "artisan", "schedule:work"]

Або у Supervisor рядом з queue worker:

[program:scheduler]
command=php /var/www/artisan schedule:work
autostart=true
autorestart=true
user=www-data
stdout_logfile=/var/log/scheduler.log

Сроки

Переведення існуючих cron-завдань на Laravel Scheduler, базові модифікатори — 2–3 години. Зберігання історії, алертинг, healthcheck-інтеграція — ще 3–4 години. Динамічні розписання з БД — окремо, 5–7 годин.