Senior: питання на співбесіді з теми «Планувальник»
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
2 питання
На одному сервері планувальник працює очевидно. Щойно серверів стає два, зʼявляються дві різні проблеми, які плутають між собою.
1. Завдання виконується двічі. Cron стоїть на обох серверах, тож щохвилинний schedule:run запускається двічі - і звіт розсилається двічі. Лікується onOneServer():
Schedule::command('reports:send')
->daily()
->onOneServer();
Перший сервер бере атомарне блокування в кеші, другий пропускає запуск. Драйвером за замовчуванням має бути database, memcached, dynamodb або redis, і всі сервери мусять дивитися в один центральний кеш; з file у кожного вузла кеш свій, тож блокування нічого не гарантує.
2. Завдання накладається саме на себе. Імпорт триває сім хвилин, а запускається щоп'ять - і другий екземпляр стартує поверх першого. Це вже про час виконання, а не про кількість серверів:
Schedule::command('import:run')
->everyFiveMinutes()
->withoutOverlapping();
За замовчуванням блокування тримається 24 години; якщо процес упав без звільнення, наступний запуск чекатиме. Тому варто задавати межу: withoutOverlapping(10).
Разом, коли потрібні обидві гарантії:
Schedule::command('import:run')
->everyFiveMinutes()
->withoutOverlapping(10)
->onOneServer();
Ще дві дрібниці, які кусаються. Часовий пояс планувальника береться з конфігурації застосунку - ->timezone('Europe/Kyiv') задає його явно. І завдання, що не пише жодного логу, мовчки не виконується місяцями; emailOutputOnFailure() або ->onFailure() роблять збій помітним.
Найнебезпечніший збій планувальника - тиша: cron зламався після міграції сервера, і резервні копії не робилися місяць.
Хуки за результатом:
Schedule::command('backup:run')
->dailyAt('3:00')
->onFailure(fn () => Log::critical('Резервна копія не вдалася'))
->pingOnSuccess('https://hc.example.com/ping/backup');
onSuccess() і onFailure() дивляться на код виходу команди - тож вона має повертати ненульовий код при збої.
Пінг «я живий» (dead man's switch): зовнішній сервіс чекає пінг за розкладом і тривожить, якщо його немає. Це ловить і збій команди, і те, що планувальник узагалі не запускався. pingBefore(), pingOnSuccess(), pingOnFailure().
Вивід: sendOutputTo() / appendOutputTo() у файл, emailOutputOnFailure() - листом лише при збої.
Сам планувальник:
- cron-рядок
* * * * * php artisan schedule:runчиschedule:workпід supervisor у контейнері; schedule:listпісля деплою - що й коли виконається;- події
ScheduledTaskFailed,ScheduledTaskFinished- для власного моніторингу; schedule:pauseна час обслуговування - і не забутиschedule:continue.
Pulse показує повільні й невдалі завдання черги, тож завдання, які планувальник лише ставить у чергу, видно і там.