Basic Concepts
Understanding these core concepts will help you make the most of Chronos for job scheduling and background processing.
What is a Job?
A job in Chronos is a unit of work that can be scheduled to run at a specific time or interval. Each job consists of:
- Name: A string identifier for the type of job
- Data: Arbitrary data passed to the job function
- Scheduling Information: When and how often the job should run
- Processing Function: The code that executes when the job runs
// Job definition (what to do)
scheduler.define('send email', async (job) => {
const { to, subject, body } = job.attrs.data;
await emailService.send({ to, subject, body });
});
// Job creation (when to do it)
await scheduler.now('send email', {
to: 'user@example.com',
subject: 'Welcome!',
body: 'Thank you for signing up'
});
Job States
Jobs progress through several states during their lifecycle:
graph LR
A[Created] --> B[Scheduled]
B --> C[Running]
C --> D[Completed]
C --> E[Failed]
B --> F[Cancelled]
- Created: Job exists but not yet scheduled
- Scheduled: Job is waiting to run at its scheduled time
- Running: Job is currently being processed
- Completed: Job finished successfully
- Failed: Job encountered an error
- Cancelled: Job was cancelled before execution
Scheduling Types
Chronos supports several scheduling patterns:
Immediate Execution
Run a job right now:
await scheduler.now('process payment', { orderId: '12345' });
One-time Scheduled
Run a job once at a specific time:
// Using relative time
await scheduler.schedule('in 30 minutes', 'send reminder', { userId: '123' });
// Using absolute time
await scheduler.schedule('2024-01-15 14:30:00', 'generate report', {});
// Using natural language
await scheduler.schedule('tomorrow at 9am', 'morning briefing', {});
Recurring Jobs
Run a job repeatedly:
// Using intervals
await scheduler.every('5 minutes', 'health check', {});
await scheduler.every('1 day', 'daily backup', {});
// Using cron expressions
await scheduler.every('0 9 * * 1-5', 'weekday morning report', {}); // 9 AM, Mon-Fri
await scheduler.every('0 0 1 * *', 'monthly cleanup', {}); // 1st of each month
Job Priority
Control the order in which jobs are processed:
scheduler.define('high priority task', async (job) => {
// Critical job logic
}, { priority: 'high' });
scheduler.define('low priority task', async (job) => {
// Less urgent job logic
}, { priority: 'low' });
Priority levels:
highest
(20)high
(10)normal
(0) - defaultlow
(-10)lowest
(-20)
Concurrency Control
Limit how many jobs can run simultaneously:
Global Concurrency
const scheduler = new Chronos({
db: { address: mongoUrl },
maxConcurrency: 10 // Max 10 jobs running at once
});
Job-Specific Concurrency
scheduler.define('resource intensive job', async (job) => {
// Heavy processing
}, {
concurrency: 2 // Max 2 of these jobs at once
});
Job Data and Context
Jobs receive data and context through the job object:
scheduler.define('user notification', async (job) => {
// Access job data
const { userId, message, type } = job.attrs.data;
// Access job metadata
const jobId = job.attrs._id;
const scheduledAt = job.attrs.nextRunAt;
const priority = job.attrs.priority;
// Update job progress (for long-running jobs)
await job.touch(); // Extends lock timeout
console.log(`Processing ${type} notification for user ${userId}`);
});
Error Handling
Handle job failures gracefully:
scheduler.define('api call job', async (job) => {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
// Process data
} catch (error) {
console.error('Job failed:', error.message);
// Mark job as failed with reason
job.fail(error.message);
// Re-throw to ensure job is marked as failed
throw error;
}
});
Event System
Monitor job execution with events:
// Listen to all job events
scheduler.on('start', (job) => {
console.log(`Job "${job.attrs.name}" started`);
});
scheduler.on('complete', (job) => {
console.log(`Job "${job.attrs.name}" completed`);
});
scheduler.on('fail', (err, job) => {
console.error(`Job "${job.attrs.name}" failed:`, err);
});
// Listen to specific job events
scheduler.on('success:send email', (job) => {
console.log('Email sent successfully!');
});
Job Persistence
All jobs are automatically persisted to MongoDB, which means:
- Durability: Jobs survive application restarts
- Reliability: Failed jobs can be retried
- Monitoring: Job history is preserved
- Scaling: Multiple application instances can share the same job queue
Lock Management
Chronos uses locks to prevent duplicate job execution across multiple instances:
scheduler.define('exclusive job', async (job) => {
// This job won't run simultaneously on multiple instances
await performExclusiveOperation();
}, {
lockLifetime: 60000 // Hold lock for 60 seconds max
});
Best Practices
1. Keep Jobs Idempotent
Jobs should be safe to run multiple times:
scheduler.define('update user stats', async (job) => {
const { userId } = job.attrs.data;
// Check if already processed today
const today = new Date().toDateString();
const user = await User.findById(userId);
if (user.lastStatsUpdate === today) {
return; // Already processed
}
// Update stats
await updateUserStats(userId);
await User.updateOne({ _id: userId }, { lastStatsUpdate: today });
});
2. Handle Timeouts
For long-running jobs, extend the lock:
scheduler.define('long job', async (job) => {
for (let i = 0; i < 1000; i++) {
await processItem(i);
// Extend lock every 100 items
if (i % 100 === 0) {
await job.touch();
}
}
});
3. Use Meaningful Names
Choose descriptive job names:
// Good
scheduler.define('send-welcome-email', handler);
scheduler.define('generate-monthly-report', handler);
scheduler.define('cleanup-expired-sessions', handler);
// Avoid
scheduler.define('job1', handler);
scheduler.define('process', handler);