Чому налаштування cron-завдань — це не просто crontab?
Cron — старий добрий демон, який запускає команди за розкладом. Але покластися на crontab без моніторингу та захисту від дублів — означає ризикувати продакшеном. Уявіть: щохвилини запускається завдання надсилання дайджесту, але поки виконується старе, нове накладається — база падає під навантаженням. Або завдання очищення токенів не виконалося, а ми дізналися через тиждень. У нашому проекті з 500 000 користувачів таке сталося: через дублі впала продуктивність на 40%, довелося терміново лагодити. Професійне налаштування cron-завдань — це розподілені блокування, моніторинг виконання та автоматичні сповіщення про помилки. Ми — команда з 5+ років досвіду та 200+ виконаними проектами — налаштували сотні таких завдань для проектів на Laravel, Node.js та Go. Розповімо, як зробити це правильно.
Які проблеми вирішує професійне налаштування?
Як уникнути дублювання при горизонтальному масштабуванні?
Якщо у вас два сервери та crontab на обох — завдання запуститься двічі. Рішення — onOneServer() в Laravel або distributed lock через Redis. Приклад нижче. Без цього подвійні розсилки, подвійні списання та зайве навантаження на БД — гарантовані. На одному проекті ми допомогли клієнту заощадити $12,000 на рік, виключивши дублювання завдань.
Втрата виконання та пропущені помилки
Без моніторингу ви не дізнаєтеся, що завдання не виконалося 3 дні. Використовуємо Healthchecks.io або кастомні сповіщення в Telegram/Slack. В Laravel вбудовано pingOnFailure() — це в 10 разів надійніше, ніж перевіряти логи вручну. Тільки за останні 12 місяців ми зафіксували понад 50 інцидентів, де моніторинг врятував проект.
Конфлікти блокувань та стану гонки
--withoutOverlapping та withoutOverlapping(5) в Laravel — задають максимальний час перекриття. В Node.js — acquireLock з TTL. Без цього дві копії завдання можуть одночасно читати та записувати одні дані, що призводить до пошкодження бази. Втрати від пропущених завдань можуть досягати $5,000 на місяць.
Як ми налаштовуємо cron-завдання: стек та приклади
Laravel Task Scheduling (основний стек)
Laravel — наш основний бекенд-фреймворк. php artisan schedule:run викликає конфіг app/Console/Kernel.php. Приклад:
// app/Console/Kernel.php
protected function schedule(Schedule $schedule): void
{
// Щоденний дайджест о 9:00 за Москвою
$schedule->job(SendDailyDigestJob::class)
->dailyAt('09:00')
->timezone('Europe/Moscow')
->withoutOverlapping() // не запускати, якщо попередній ще працює
->onOneServer() // тільки на одному сервері при горизонтальному масштабуванні
->runInBackground();
// Очищення застарілих сесій — щогодини
$schedule->command('sessions:cleanup')
->hourly()
->withoutOverlapping(5) // максимум 5 хвилин на перекриття
->appendOutputTo(storage_path('logs/sessions-cleanup.log'));
// Щохвилинна перевірка черги сповіщень
$schedule->command('notifications:send-pending')
->everyMinute()
->runInBackground()
->skip(fn() => !config('features.notifications'));
}
# Crontab: запускати планувальник щохвилини
* * * * * cd /var/www/myapp && php artisan schedule:run >> /dev/null 2>&1
Node.js: node-cron
import cron from 'node-cron';
import { db } from './database';
import { emailService } from './services/email';
// Щоденне очищення о 3:00
cron.schedule('0 3 * * *', async () => {
const lock = await acquireLock('cleanup-expired-tokens');
if (!lock) return; // інший інстанс вже виконує
try {
const deleted = await db.query(
'DELETE FROM password_reset_tokens WHERE expires_at < NOW()'
);
console.log(`Cleaned ${deleted.rowCount} expired tokens`);
} finally {
await releaseLock('cleanup-expired-tokens');
}
}, { timezone: 'Europe/Moscow' });
// Кожні 5 хвилин: оновлення курсів валют
cron.schedule('*/5 * * * *', async () => {
try {
const rates = await fetchExchangeRates();
await cache.set('exchange_rates', rates, 300);
} catch (err) {
console.error('Exchange rates update failed:', err);
}
});
Distributed Lock через Redis (запобігання дублюванню)
// Для Laravel: стандартний Cache::lock()
$schedule->call(function () {
$lock = Cache::lock('daily-digest', 3600);
if (!$lock->get()) {
return; // інший сервер вже виконує
}
try {
app(DigestService::class)->sendAll();
} finally {
$lock->release();
}
})->dailyAt('09:00')->onOneServer();
Моніторинг: Healthchecks.io / Laravel Health
// Сповіщення про помилки планувальника
$schedule->job(SendDailyDigestJob::class)
->dailyAt('09:00')
->pingOnSuccess('https://hc-ping.com/success-uuid')
->pingOnFailure('https://hc-ping.com/fail-uuid')
->emailOutputOnFailure('[email protected]');
Детальніше про блокування
Для запобігання дублів в Laravel використовуйте `->onOneServer()` у поєднанні з distributed lock. В node-cron та go-cron блокування потрібно реалізовувати вручну через Redis або etcd.Порівняння підходів: Laravel Schedule vs node-cron vs go-cron
| Критерій | Laravel Schedule | node-cron | go-cron |
|---|---|---|---|
| Вбудований моніторинг | ✅ Ping on success/failure | ❌ Потрібен зовнішній | ❌ Потрібен зовнішній |
| Distributed lock | Через Redis / database | Через Redis | Через etcd |
| Простота підтримки | Висока (artisan) | Середня | Низька |
| Захист від дублів | onOneServer() + ->withoutOverlapping() |
Вручну | Вручну |
Типові помилки та їх наслідки
| Помилка | Наслідок | Рішення |
|---|---|---|
Забути runInBackground() |
Завдання блокує планувальник, наступні затримуються | Додати runInBackground() |
Не задати onOneServer() |
Дублі на декількох серверах | Використовувати onOneServer() + distributed lock |
| Ігнорувати моніторинг | Помилки непомітні тижнями | Підключити Healthchecks.io або email |
| Занадто малий TTL блокування | Lock звільняється раніше часу, дублі | Встановити TTL = max час виконання + запас |
Чому важливий моніторинг?
Без моніторингу ви ризикуєте виявити проблему занадто пізно. У нашій практиці був випадок: клієнт не знав, що завдання генерації звітів не працювало 2 тижні — дані застаріли на 30%. Після впровадження моніторингу з алертами в Telegram час реакції скоротився до 15 хвилин. Рекомендуємо пінгувати health endpoint щохвилини — це покриває 99% сценаріїв.
Що входить в роботу (deliverables)
- Код завдань з захистом від дублів (distributed lock).
- Моніторинг виконання (Healthchecks.io або свій).
- Сповіщення про помилки (Slack, Telegram, email).
- Документація з підтримки та додавання нових завдань.
- Навчання команди замовника.
Терміни орієнтовно
- Просте налаштування (2-3 завдання): від 1 дня.
- Комплексна з моніторингом та блокуваннями: від 2 днів.
Процес роботи
- Аналітика — з'ясовуємо, які завдання потрібні: очищення кешу, розсилки, генерація звітів.
- Проектування — визначаємо інтервали, блокування, моніторинг.
-
Реалізація — пишемо код з обробкою помилок та
withoutOverlapping. - Тестування — запускаємо в стейджингу, перевіряємо логи.
- Деплой — налаштовуємо crontab та healthchecks.
Наші інженери гарантують, що ваш планувальник працюватиме стабільно. Якщо ви хочете уникнути проблем з дублями — замовте професійне налаштування cron-завдань. Зв'яжіться з нами, оцінимо проект безкоштовно.







