Skip to main content

Frequently Asked Questions

Common questions and answers about Chronos job scheduling.

General Questions​

What is Chronos?​

Chronos is a lightweight job scheduling library for Node.js, originally forked from Agenda. It provides reliable job scheduling using MongoDB as a persistence layer with a modern Promise-based API.

Why should I choose Chronos over other job queues?​

  • MongoDB Native: If you're already using MongoDB, Chronos integrates seamlessly
  • Active Maintenance: Unlike the original Agenda, Chronos is actively maintained
  • Production Ready: Used in 100+ production projects
  • Promise-based API: Modern async/await support
  • Long-running Jobs: Better support for jobs that take a long time to complete

How is Chronos different from Bull or Bee?​

Chronos uses MongoDB instead of Redis, making it ideal if you're already using MongoDB in your stack. It's optimized for jobs rather than messages and supports long-running jobs better than Redis-based alternatives.

Can I migrate from Agenda to Chronos?​

Yes! Chronos maintains API compatibility with Agenda. Most Agenda code works with minimal changes:

// Before (Agenda)
const Agenda = require('agenda');
const agenda = new Agenda({db: {address: mongoUrl}});

// After (Chronos)
const Chronos = require('chronos-jobs');
const scheduler = new Chronos({db: {address: mongoUrl}});

// API is nearly identical!

Job Execution​

What is the order in which jobs run?​

Jobs are processed with priority in a first-in-first-out (FIFO) order, with respect to priority levels.

Example scenario:

  • Two "send-email" jobs are queued with the same priority
  • First job queued at 3:00 PM, second at 3:05 PM
  • When processing starts at 3:10 PM, the first job runs first

Priority takes precedence:

  • If the first job has priority 5 and second has priority 10
  • The second job (higher priority) will run first at 3:10 PM

The default MongoDB sort is { nextRunAt: 1, priority: -1 } and can be customized via the sort option:

const scheduler = new Chronos({
sort: {
priority: -1, // High priority first
nextRunAt: 1 // Then by scheduled time
}
});

What's the difference between lockLimit and maxConcurrency?​

  • lockLimit: Maximum number of jobs that can be locked (reserved) by a scheduler instance
  • maxConcurrency: Maximum number of jobs that can actually run simultaneously
const scheduler = new Chronos({
maxConcurrency: 10, // Run max 10 jobs at once
lockLimit: 50 // But lock up to 50 jobs in advance
});

This allows schedulers to "reserve" work while having limited execution capacity.

Architecture & Scaling​

Can I use multiple Chronos instances?​

Yes! Chronos is designed for horizontal scaling across multiple machines or processes.

// Instance 1 - Web server
const webScheduler = new Chronos({
name: 'web-server-1',
db: { address: 'mongodb://shared-db:27017/jobs' }
});

// Instance 2 - Background worker
const workerScheduler = new Chronos({
name: 'worker-server-1',
db: { address: 'mongodb://shared-db:27017/jobs' }
});

Key features:

  • Shared database: All instances use the same MongoDB collection
  • Job locking: Prevents duplicate execution across instances
  • Automatic failover: If one instance dies, others pick up pending jobs
  • Load distribution: Jobs are distributed across available instances

How do I handle lost MongoDB connections?​

Chronos automatically handles connection recovery:

const scheduler = new Chronos({
db: {
address: 'mongodb://localhost:27017/scheduler',
options: {
// Automatic reconnection settings
maxPoolSize: 10,
serverSelectionTimeoutMS: 5000,
socketTimeoutMS: 45000,
bufferMaxEntries: 0,
useNewUrlParser: true,
useUnifiedTopology: true
}
}
});

// Handle connection events
scheduler.on('error', (error) => {
console.error('Database connection error:', error);
// Implement alerting, logging, etc.
});

scheduler.on('ready', () => {
console.log('Database connection restored');
});

For existing MongoDB clients, configure retry settings:

const client = new MongoClient(uri, {
retryWrites: true,
retryReads: true,
maxPoolSize: 50,
wtimeoutMS: 2500
});

const scheduler = new Chronos({
mongo: client.db('scheduler')
});

Database & Performance​

Why MongoDB instead of Redis?​

Chronos chooses MongoDB for guaranteed persistence:

MongoDB advantages:

  • Durability: Data survives server crashes and restarts
  • ACID transactions: Ensures data consistency
  • Rich queries: Complex job filtering and management
  • No special configuration: Works out-of-the-box
  • Persistence guarantee: Jobs won't be lost

Redis considerations:

  • Often used for non-essential data (sessions, cache)
  • Requires special configuration for persistence
  • Can lose data on crashes without proper setup
  • Better for high-throughput, temporary data

When to consider Redis: If you need extremely high throughput and can accept potential job loss, Redis-based solutions like Bull might be better suited.

How do I optimize MongoDB performance?​

Essential indexes for production:

// Core index (created automatically)
db.chronosJobs.createIndex({
"nextRunAt": 1,
"priority": -1
});

// For job management operations
db.chronosJobs.createIndex({ "name": 1 });

// For Agendash (if used)
db.chronosJobs.createIndex({
"nextRunAt": -1,
"lastRunAt": -1,
"lastFinishedAt": -1
});

// For high-volume job types
db.chronosJobs.createIndex({
"name": 1,
"disabled": 1,
"lockedAt": 1
});

Connection optimization:

const scheduler = new Chronos({
db: {
address: 'mongodb://localhost:27017/scheduler',
options: {
maxPoolSize: 100,
minPoolSize: 5,
maxIdleTimeMS: 30000,
serverSelectionTimeoutMS: 5000,
retryWrites: true
}
}
});

Development & Debugging​

How do I debug job issues?​

Enable debug logging:

# Set DEBUG environment variable
DEBUG="chronos:*" node your-app.js

# On Windows CMD
set DEBUG=chronos:*

# On Windows PowerShell
$env:DEBUG = "chronos:*"

Add comprehensive logging:

scheduler.on('start', (job) => {
console.log(`[${new Date().toISOString()}] Starting: ${job.attrs.name}`);
});

scheduler.on('complete', (job) => {
const duration = Date.now() - job.attrs.lockedAt.getTime();
console.log(`[${new Date().toISOString()}] Completed: ${job.attrs.name} (${duration}ms)`);
});

scheduler.on('fail', (err, job) => {
console.error(`[${new Date().toISOString()}] Failed: ${job.attrs.name}`);
console.error('Error:', err.message);
console.error('Stack:', err.stack);
});

Can I test jobs without starting the scheduler?​

Yes! Test job functions directly:

// Define the job
scheduler.define('send-email', async (job) => {
const { to, subject } = job.attrs.data;
return await emailService.send({ to, subject });
});

// Test without starting scheduler
const testJob = {
attrs: {
data: { to: 'test@example.com', subject: 'Test' }
}
};

// Get the job function and test it
const jobDef = scheduler._definitions['send-email'];
const result = await jobDef.fn(testJob);

How do I handle different environments?​

Use environment-based configuration:

const config = {
development: {
processEvery: '1 second',
maxConcurrency: 2,
db: { address: 'mongodb://localhost:27017/scheduler-dev' }
},
production: {
processEvery: '5 seconds',
maxConcurrency: 50,
db: {
address: process.env.MONGODB_URI,
options: { ssl: true, retryWrites: true }
}
}
};

const env = process.env.NODE_ENV || 'development';
const scheduler = new Chronos(config[env]);

Common Issues​

"Multiple order-by items are not supported"​

This error occurs when using Azure CosmosDB. Fix by using a single sort field:

const scheduler = new Chronos({
sort: { nextRunAt: 1 } // Remove priority from sort
});

Jobs not running​

Common causes and solutions:

  1. Scheduler not started:
await scheduler.start(); // Required before jobs will process
  1. No job definitions:
// Must define jobs before they can run
scheduler.define('my-job', async (job) => {
// Job logic here
});
  1. Database connection issues:
scheduler.on('ready', () => {
console.log('Scheduler ready to process jobs');
});

scheduler.on('error', (err) => {
console.error('Scheduler error:', err);
});

Memory usage growing​

Possible causes:

  1. Too many locked jobs:
const scheduler = new Chronos({
lockLimit: 100, // Limit locked jobs in memory
});
  1. Long-running jobs not extending locks:
scheduler.define('long-job', async (job) => {
for (let i = 0; i < 1000; i++) {
await processItem(i);

// Extend lock periodically
if (i % 100 === 0) {
await job.touch();
}
}
});
  1. No job cleanup:
// Regular cleanup of completed jobs
scheduler.define('cleanup', async () => {
const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
await scheduler.cancel({ lastFinishedAt: { $lt: thirtyDaysAgo } });
});

scheduler.every('1 day', 'cleanup');

Migration & Upgrades​

How do I migrate from Agenda?​

Chronos is largely compatible with Agenda. Main changes:

  1. Import statement:
// Old (Agenda)
const Agenda = require('agenda');

// New (Chronos)
const { Chronos } = require('chronos-jobs');
  1. Updated MongoDB drivers: Chronos supports latest MongoDB versions

  2. Enhanced TypeScript support: Better type definitions included

  3. New features: Fork mode, improved performance, active maintenance

Can I use the existing Agendash UI?​

Yes! Agendash works with Chronos since it uses the same database structure:

const Agendash = require('agendash');
const express = require('express');

const app = express();

// Mount Agendash
app.use('/agendash', Agendash(scheduler));

Project Structure​

How should I organize jobs in my project?​

Recommended structure:

project/
├── jobs/
│ ├── email/
│ │ ├── welcome.js
│ │ ├── newsletter.js
│ │ └── notifications.js
│ ├── payments/
│ │ ├── process.js
│ │ └── refunds.js
│ └── maintenance/
│ ├── cleanup.js
│ └── backups.js
├── lib/
│ └── scheduler.js
└── server.js

Example lib/scheduler.js:

const { Chronos } = require('chronos-jobs');
const path = require('path');
const fs = require('fs');

const scheduler = new Chronos({
db: { address: process.env.MONGODB_URI }
});

// Auto-load job definitions
const jobsDir = path.join(__dirname, '../jobs');
function loadJobs(dir) {
const items = fs.readdirSync(dir);

for (const item of items) {
const fullPath = path.join(dir, item);
if (fs.statSync(fullPath).isDirectory()) {
loadJobs(fullPath);
} else if (item.endsWith('.js')) {
require(fullPath)(scheduler);
}
}
}

loadJobs(jobsDir);

module.exports = scheduler;

How do I handle job dependencies?​

Use events to chain jobs:

scheduler.define('process-order', async (job) => {
const { orderId } = job.attrs.data;
await processOrder(orderId);
});

scheduler.define('send-confirmation', async (job) => {
const { orderId } = job.attrs.data;
await sendConfirmation(orderId);
});

// Chain jobs using events
scheduler.on('success:process-order', (job) => {
const { orderId } = job.attrs.data;
scheduler.now('send-confirmation', { orderId });
});

Contributing​

How can I contribute to Chronos?​

We welcome contributions! Here's how to get started:

  1. Report issues: GitHub Issues
  2. Submit pull requests: Fork, create branch, submit PR
  3. Improve documentation: Help expand these docs
  4. Share examples: Show how you use Chronos in production

Can I sponsor development?​

While financial contributions aren't necessary, they're appreciated. You can sponsor development through GitHub Sponsors or support the maintainer directly.

More importantly, you can contribute by:

  • Reporting bugs and issues
  • Submitting feature requests
  • Contributing code improvements
  • Helping other users in discussions
  • Sharing Chronos with others who might benefit

Database and Performance​

What MongoDB indexes should I create?​

Essential indexes for performance:

// Primary processing index
db.chronosJobs.createIndex({
"nextRunAt": 1,
"disabled": 1,
"lockedAt": 1
});

// For Agendash UI
db.chronosJobs.createIndex({
"nextRunAt": -1,
"lastRunAt": -1,
"lastFinishedAt": -1
});

// For job name queries
db.chronosJobs.createIndex({
"name": 1,
"disabled": 1,
"lockedAt": 1
});

How do I clean up old jobs?​

Create a cleanup job:

scheduler.define('cleanup old jobs', async () => {
const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);

const result = await scheduler.cancel({
$or: [
{ lastFinishedAt: { $lt: thirtyDaysAgo } },
{ failedAt: { $lt: thirtyDaysAgo } }
]
});

console.log(`Cleaned up ${result} old jobs`);
});

scheduler.every('1 day', 'cleanup old jobs');

Error Handling and Debugging​

How do I debug job processing issues?​

Enable debug logging:

# Enable all Chronos debug logs
DEBUG="chronos:*" node your-app.js

# Enable specific categories
DEBUG="chronos:job,chronos:database" node your-app.js

Why are my jobs not running?​

Common issues:

  1. Scheduler not started: await scheduler.start()
  2. Job definition missing: Check job name spelling
  3. Jobs are disabled: Check disabled field
  4. Database connection issues: Check MongoDB connectivity
  5. Lock issues: Jobs may be stuck in locked state

Debug checklist:

// Check scheduler status
console.log('Running:', scheduler._processInterval !== null);

// Check pending jobs
const pending = await scheduler.jobs({
nextRunAt: { $lte: new Date() },
disabled: { $ne: true },
lockedAt: null
});

// Check defined jobs
console.log('Defined:', Object.keys(scheduler._definitions));

Jobs are stuck in "locked" state​

This usually happens when a process crashes while processing jobs. Solutions:

  1. Proper graceful shutdown:
process.on('SIGTERM', async () => {
await scheduler.stop();
process.exit(0);
});
  1. Monitor and cleanup dead locks:
const cleanupStuckJobs = async () => {
const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000);

await scheduler.jobs({
lockedAt: { $lt: fiveMinutesAgo },
lastFinishedAt: { $exists: false }
}).forEach(job => {
job.attrs.lockedAt = null;
return job.save();
});
};

Integration Questions​

Can I use Chronos with TypeScript?​

Yes! Chronos includes TypeScript definitions:

import Chronos from 'chronos-jobs';

interface EmailJobData {
to: string;
subject: string;
body: string;
}

const scheduler = new Chronos({
db: { address: 'mongodb://localhost:27017/jobs' }
});

scheduler.define('send email', async (job: Chronos.Job<EmailJobData>) => {
const { to, subject, body } = job.attrs.data;
// Type-safe job data access
});

Is there a web UI for monitoring jobs?​

Yes! Use Agendash:

const Agendash = require('agendash');

app.use('/dash', Agendash(scheduler));

This provides a web interface to view and manage jobs.

Known Issues​

Azure CosmosDB compatibility​

When using Azure CosmosDB, you might encounter sorting limitations. Use a single sort field:

const scheduler = new Chronos({
db: { address: cosmosDbUrl },
sort: { nextRunAt: 1 } // Single field sort for CosmosDB
});

Memory usage keeps growing​

Common causes and solutions:

  1. Job result accumulation: Set shouldSaveResult: false or clean up results
  2. Large job data: Keep job payloads small, use references instead
  3. Too many locked jobs: Reduce lockLimit setting
  4. Completed jobs not cleaned up: Implement regular cleanup

Have a question not answered here?