Skip to main content

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) - default
  • low (-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);