Почему настройка 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'));
// Отчёт по понедельникам в 8:00
$schedule->job(WeeklyReportJob::class)
->weekly()
->mondays()
->at('08:00');
} # 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-задач. Свяжитесь с нами, оценим проект.







