MongoDB-backed job store implementation for Quartz.NET. Persist your scheduled jobs, triggers and calendar data in MongoDB so multiple scheduler instances can share state and recover after restarts.
Quartz.Store.MongoDb implements a persistent IJobStore for Quartz.NET backed by MongoDB. Use it to:
- Persist job and trigger metadata across restarts
- Share scheduler state between instances (clustering)
- Improve reliability for scheduled workloads
- Automatic retry with exponential backoff for transient MongoDB errors
- Atomic distributed locking with TTL-based expiry
- .NET 9
- Transient Error Retries: Automatic exponential backoff with jitter for connection timeouts and transient MongoDB failures
- Distributed Locking: Atomic lock acquisition using MongoDB
FindOneAndUpdatewith TTL-based expiry to prevent deadlocks - Cancellation Support: Full
CancellationTokenpropagation through all repository operations and MongoDB driver calls - UTC Timestamps: Consistent use of UTC for all scheduling operations to avoid timezone-related bugs
- Efficient trigger acquisition with MongoDB indexes on
NextFireTimeandState - Batch updates for pause/resume operations using
UpdateMany - Minimal round-trips to MongoDB for lock operations
NuGet Package Manager:
Install-Package quartz-store-mongodbdotnet CLI:
dotnet add package quartz-store-mongodbPaket:
paket add quartz-store-mongodbThis example shows how to configure Quartz to use the MongoDB job store with generic host. It reads connection configuration from the appsettings.json or an environment variable.
using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Quartz;
using Quartz.Impl;
using Quartz.Spi.MongoDbJobStore;
await Host.CreateDefaultBuilder()
.ConfigureServices((cxt, services) =>
{
services.AddHostedService<JobsScheduler>();
services.AddQuartz(q => {
q.UsePersistentStore<MongoDbJobStore>(options => {
var section = cxt.Configuration.GetSection("Quartz");
options.SetProperty(StdSchedulerFactory.PropertySchedulerInstanceName, section.GetValue<string>(StdSchedulerFactory.PropertySchedulerInstanceName));
options.SetProperty(StdSchedulerFactory.PropertySchedulerInstanceId, section.GetValue<string>(StdSchedulerFactory.PropertySchedulerInstanceId));
options.SetProperty("quartz.jobStore.connectionString", section.GetValue<string>("quartz.jobStore.connectionString"));
options.SetProperty("quartz.jobStore.collectionPrefix", section.GetValue<string>("quartz.jobStore.collectionPrefix"));
options.UseNewtonsoftJsonSerializer();
});
services.AddQuartzHostedService(opt => opt.WaitForJobsToComplete = true);
});
})
.Build()
.RunAsync();If you prefer environment variables, set MONGODB_CONNECTION and read it in place of the configuration section.
If your app uses the StdSchedulerFactory configuration approach (older frameworks), you can configure the store via NameValueCollection:
var properties = new NameValueCollection();
properties[StdSchedulerFactory.PropertySchedulerInstanceName] = "DefaultScheduler";
properties[StdSchedulerFactory.PropertySchedulerInstanceId] = $"{Environment.MachineName}-{Guid.NewGuid()}";
properties[StdSchedulerFactory.PropertyJobStoreType] = typeof(Quartz.Spi.MongoDbJobStore.MongoDbJobStore).AssemblyQualifiedName;
properties[$"{StdSchedulerFactory.PropertyJobStorePrefix}.{StdSchedulerFactory.PropertyDataSourceConnectionString}"] = "mongodb://localhost:27017/quartz";
properties[$"{StdSchedulerFactory.PropertyJobStorePrefix}.collectionPrefix"] = "quartz";
var scheduler = new StdSchedulerFactory(properties).GetScheduler().Result;Common properties (the job store prefix is quartz.jobStore):
| Key | Type | Default | Description |
|---|---|---|---|
quartz.jobStore.connectionString |
string | - | MongoDB connection string (e.g. mongodb://user:pass@host:27017/database) |
quartz.jobStore.collectionPrefix |
string | quartz |
Prefix for collections used to store jobs/triggers |
quartz.jobStore.useTls |
bool | false |
Enable TLS/SSL when required by MongoDB |
quartz.scheduler.instanceId |
string | auto | Scheduler instance id (use stable value for clustering) |
quartz.jobStore.misfireThreshold |
ms | 60000 | Misfire threshold in milliseconds (default 1 minute) |
quartz.jobStore.dbRetryInterval |
ms | 15000 | Delay between retries for transient MongoDB errors (default 15 seconds) |
Store your connection string in environment variables or a secrets manager for production. Example appsettings.json:
{
"Quartz": {
"quartz.jobStore.connectionString": "mongodb://localhost:27017/quartz",
"quartz.jobStore.collectionPrefix": "quartz",
"quartz.scheduler.instanceId": "AUTO",
"quartz.jobStore.misfireThreshold": "60000",
"quartz.jobStore.dbRetryInterval": "15000"
}
}The job store automatically retries transient MongoDB errors (connection timeouts, network issues) with exponential backoff and jitter:
- Initial backoff: 200ms
- Exponential growth: 2^attempt multiplier
- Max attempts: 3 (configurable)
- Jitter added to reduce thundering herd
This is applied to all write operations (insert, update, delete) on jobs, triggers, calendars, and scheduler state.
- Atomic Acquisition: Uses MongoDB
FindOneAndUpdatefor single-round-trip lock acquisition - Automatic Expiry: Expired locks are automatically cleaned up via MongoDB TTL index (30-second default)
- Ownership Verification: Prevents non-owner lock release and handles expired lock re-acquisition
- Non-Reentrant: Same thread cannot acquire the same lock twice (will block until first is released)
Lock documents use an ExpireAt field for TTL cleanup. If an instance crashes, its locks automatically expire after 30 seconds, allowing other instances to acquire them.
All async operations support CancellationToken for graceful shutdown and timeout control. Cancellation is properly propagated from the scheduler down through job store and repository methods to MongoDB driver calls.
All timestamps are stored and compared in UTC to avoid timezone-related bugs and misfire detection issues across different regions and locales.
- Cannot connect to MongoDB: verify the connection string, network, and authentication. Try
mongoshell or a MongoDB client. - Scheduler state not shared: ensure all instances use the same
connectionStringandcollectionPrefixand are pointed at the same database. - Duplicate key errors: caused by collection prefix changes or schema drift; ensure consistent
collectionPrefixand consider migration scripts. - Lock timeout (scheduler hangs): if a scheduler instance crashes while holding a lock, other instances can acquire it after ~30 seconds (lock TTL). If hangs persist, check:
- MongoDB connectivity and network latency
- Ensure all instances are using UTC time (check system clocks)
- Check logs for transient MongoDB errors that exceed retry threshold
- High latency on job acquisition: verify MongoDB indexes exist:
triggerscollection: index on(NextFireTime, State, InstanceName)triggerscollection: index on(JobKey, State)firedTriggerscollection: TTL index onCreatedAt(if using automatic cleanup)
The job store creates the following collections in MongoDB (collection names prefixed by collectionPrefix):
| Collection | Purpose | TTL Index |
|---|---|---|
jobs |
Stores JobDetail models |
None |
triggers |
Stores trigger configurations and state | None (but indexes on NextFireTime, State) |
calendars |
Stores calendar exclusion rules | None |
locks |
Distributed lock documents | Yes (30 seconds on ExpireAt) |
firedTriggers |
Tracks in-progress and recoverable triggers | Optional (on CreatedAt) |
pausedTriggerGroups |
Tracks paused trigger group names | None |
schedulers |
Tracks scheduler instance state | None |
Indexes are automatically created on first use by each repository.
Tests require a running MongoDB instance (default: localhost:27017). Start MongoDB before running tests:
# Using Docker
docker run -d -p 27017:27017 --name mongodb mongo:latest
# Run tests
dotnet test
# Clean up
docker stop mongodb && docker rm mongodb-
src/Quartz.Store.MongoDb/: Main libraryMongoDbJobStore.cs: MainIJobStoreimplementationLockManager.cs: Distributed locking with MongoDBRepositories/: CRUD repositories for each entity typeModels/: Domain models mirroring Quartz.NET interfacesSerializers/: Custom BSON serialization for complex types
-
tests/Quartz.Store.MongoDb.Tests/: Integration and contract tests -
examples/Quartz.Store.MongoDb.Examples/: Real-world usage examples
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure all tests pass:
dotnet test - Submit a pull request
- Repository: https://github.com/DhananjayNazare/Quartz.Store.MongoDb
- NuGet: https://www.nuget.org/packages/quartz-store-mongodb
Quartz.Store.MongoDb is released under MIT. See LICENSE for details.