Cron Scheduling
Cron Expression Syntax
┌─────── minute (0-59)
│ ┌────── hour (0-23)
│ │ ┌───── day of month (1-31)
│ │ │ ┌──── month (1-12)
│ │ │ │ ┌─── day of week (0-7, 0 and 7 = Sunday)
│ │ │ │ │
* * * * *
| Expression | Schedule |
|---|
0 * * * * | Every hour |
*/15 * * * * | Every 15 minutes |
0 9 * * 1-5 | Weekdays at 9:00 AM |
0 0 1 * * | First day of month, midnight |
0 */6 * * * | Every 6 hours |
Node.js (node-cron)
import cron from 'node-cron';
// Run every day at 2:00 AM
cron.schedule('0 2 * * *', async () => {
console.log('Running daily cleanup...');
await cleanupExpiredSessions();
}, {
timezone: 'America/New_York',
});
// Run every 5 minutes
cron.schedule('*/5 * * * *', async () => {
await syncExternalData();
});
With Distributed Locking (prevent duplicate runs)
import { Redlock } from 'redlock';
const redlock = new Redlock([redis]);
cron.schedule('0 * * * *', async () => {
let lock;
try {
lock = await redlock.acquire(['cron:hourly-report'], 60000);
await generateHourlyReport();
} catch (err) {
if (err instanceof Redlock.LockError) return; // Another instance has the lock
throw err;
} finally {
await lock?.release();
}
});
Spring @Scheduled (Java)
@Component
@EnableScheduling
public class ScheduledTasks {
@Scheduled(cron = "0 0 2 * * *") // Daily at 2 AM
public void dailyCleanup() {
sessionRepo.deleteExpired();
}
@Scheduled(fixedRate = 300000) // Every 5 minutes
public void syncData() {
externalService.sync();
}
@Scheduled(fixedDelay = 60000) // 60s after last completion
public void processQueue() {
queueService.processNext();
}
}
Distributed Scheduling (ShedLock)
@Scheduled(cron = "0 */10 * * * *")
@SchedulerLock(name = "syncInventory", lockAtLeastFor = "5m", lockAtMostFor = "10m")
public void syncInventory() {
inventoryService.syncAll();
}
Python (APScheduler)
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.triggers.cron import CronTrigger
scheduler = AsyncIOScheduler()
@scheduler.scheduled_job(CronTrigger(hour=2, minute=0))
async def daily_cleanup():
await cleanup_expired_sessions()
@scheduler.scheduled_job('interval', minutes=5)
async def sync_data():
await sync_external_data()
scheduler.start()
Anti-Patterns
| Anti-Pattern | Fix |
|---|
| Cron on multiple instances without locking | Use distributed lock (Redlock, ShedLock) |
| Long-running cron blocks next run | Use job queue for heavy work |
| No error handling in cron | Wrap in try-catch, log errors, alert |
| Hardcoded schedule | Use environment variable or config |
| No monitoring of cron execution | Log start/end times, track failures |
Production Checklist
- Distributed locking for multi-instance deployments
- Error handling with alerting
- Execution time monitoring
- Timezone explicitly set
- Graceful shutdown (finish current job)
- Cron expressions documented