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?