• Facebook
  • Twitter
  • Youtube
  • LinkedIn
  • RSS
  • Docs
  • Comparisons
  • Blogs
  • Try NCache
  • Contact Us
Try NCache
Show / Hide Table of Contents

NCache as Quartz.NET Job Store

NCache provides a Quartz.NET integration through the NCache.OSS.Quartz package. It implements the Quartz.NET IJobStore interface and persists jobs, triggers, calendars, firing records, paused groups, and scheduler cluster information in NCache.

Using NCache as the Quartz.NET job store enables multiple application instances to share scheduling state while continuing to use Quartz.NET's standard IScheduler APIs.

Prerequisites

  • .NET
  • Install the following NuGet package in your application:
    • NCache Open Source: NCache.OSS.Quartz
  • Include the following namespaces in Program.cs, as appropriate:
    • NCache.OSS.Quartz
    • NCache.OSS.Quartz.Extensions
    • Quartz
  • Ensure the application references the compatible Quartz hosting integration when using AddQuartzHostedService.
  • Ensure the target cache is created and running in the NCache cluster.
  • Ensure the application can connect to the target NCache cache.

Configure NCache as Quartz.NET Job Store

NCache supports three configuration approaches through the Quartz builder: configuration binding, an options object, and a short overload accepting the cache name.

Method 1: Using appsettings.json

Add the Quartz:NCache section to appsettings.json:

{
  "Quartz": {
    "NCache": {
      "CacheName": "defaultCache",
      "SchedulerName": "orders-scheduler",
      "InstanceId": "node-1",
      "KeyPrefix": "ncache:quartz:",
      "MisfireThreshold": "00:01:00",
      "ClusterCheckinInterval": "00:00:10",
      "ClusterCheckinMisfireThreshold": "00:00:07.5",
      "LockExpiration": "00:00:30",
      "RepairRegistriesOnStart": true
    }
  }
}

Register the NCache job store in Program.cs:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddQuartz(q =>
{
    q.UseNCacheStore(builder.Configuration);
});

builder.Services.AddQuartzHostedService(options =>
{
    options.WaitForJobsToComplete = true;
});

var app = builder.Build();
app.Run();

By default, UseNCacheStore(builder.Configuration) reads the Quartz:NCache section. To use a different section, pass its name:

builder.Services.AddQuartz(q =>
{
    q.UseNCacheStore(builder.Configuration, "MyApplication:QuartzStore");
});

UseNCacheStore registers a job-store factory and does not start the scheduler. The factory opens the NCache connection through CacheManager.GetCache when Quartz creates the job store. This can happen before IScheduler.Start() is called.

Method 2: Using NCacheJobStoreOptions

Configure the job store directly in code:

builder.Services.AddQuartz(q =>
{
    q.UseNCacheStore(new NCacheJobStoreOptions
    {
        CacheName = "defaultCache",
        SchedulerName = "orders-scheduler",
        InstanceId = Environment.MachineName,
        MisfireThreshold = TimeSpan.FromSeconds(30),
        ClusterCheckinInterval = TimeSpan.FromSeconds(5),
        LockExpiration = TimeSpan.FromSeconds(30)
    });

    q.UseDefaultThreadPool(10);
});

builder.Services.AddQuartzHostedService(options =>
{
    options.WaitForJobsToComplete = true;
});

Use a unique InstanceId for each running scheduler process; Environment.MachineName is suitable only when there is one participating process per machine.

Method 3: Using Cache and Scheduler Names

For a shorter setup, supply the cache name, with optional scheduler and instance identifiers:

builder.Services.AddQuartz(q =>
{
    q.UseNCacheStore("defaultCache", "orders-scheduler", "node-1");
});

Only the cache name is required:

q.UseNCacheStore("defaultCache");

When InstanceId is missing, blank, or AUTO, Quartz automatically generates an instance ID.

NCacheJobStoreOptions

All options except CacheName are optional. An explicitly supplied MisfireThreshold must be at least 1 ms, ClusterCheckinInterval must be at least 100 ms, ClusterCheckinMisfireThreshold must be nonnegative, LockAcquireTimeout must be positive, and LockExpiration must exceed 100 ms. Keep cluster-related values consistent across scheduler instances. The following table lists the //NCache job store options and their effective default values.

Property Default Description
CacheName Required Name of the running NCache cache used to persist Quartz scheduling data.
SchedulerName Registered scheduler name or Quartz default Logical scheduler name. Must match across nodes participating in the same scheduler.
InstanceId Generated by Quartz Unique identifier of the running scheduler process. Blank or AUTO causes automatic generation.
KeyPrefix ncache:quartz: Prefix applied to the job store's NCache keys. Must match across scheduler nodes.
LockExpiration 30 seconds Lifetime of an internal distributed lock before it expires. Must exceed 100 ms.
LockAcquireTimeout null Maximum wait for a distributed lock. null means wait until acquired or cancelled.
MisfireThreshold 60 seconds Default lateness threshold for treating triggers as misfired. Individual triggers may override it.
ClusterCheckinInterval 10 seconds Interval between scheduler check-ins and maintenance passes.
ClusterCheckinMisfireThreshold 7.5 seconds Additional allowance after a missed check-in before a scheduler is stale.
RepairRegistriesOnStart true Rebuilds stored registries at startup. The next-fire-time index is rebuilt on startup regardless.
ConfigureSerializer null Registers custom serializers and type resolvers programmatically. Cannot be configured through appsettings.json.

Configure a Quartz Scheduler Cluster

Each scheduler node uses the same NCache cache, logical scheduler name, and key prefix but a different instance identifier.

Node 1 — appsettings.json

{
  "Quartz": {
    "NCache": {
      "CacheName": "defaultCache",
      "SchedulerName": "orders-scheduler",
      "InstanceId": "node-1",
      "KeyPrefix": "ncache:quartz:"
    }
  }
}

Node 2 — appsettings.json

{
  "Quartz": {
    "NCache": {
      "CacheName": "defaultCache",
      "SchedulerName": "orders-scheduler",
      "InstanceId": "node-2",
      "KeyPrefix": "ncache:quartz:"
    }
  }
}

For one logical scheduler, use the same CacheName, SchedulerName, KeyPrefix, MisfireThreshold, ClusterCheckinInterval, and ClusterCheckinMisfireThreshold on all nodes. Each running process needs its own InstanceId.

A stable instance ID is useful across restarts: the node can recover its own leftover work immediately at startup. AUTO ensures uniqueness but does not preserve the same ID across restarts.

Configure Quartz Scheduler Settings

Quartz uses the thread-pool size and idle-wait settings to control scheduler behavior. The NCache job store applies the acquisition request's batch size, time window, execution-group routing, and execution limits. Cluster-scoped execution limits count acquired and executing firing records across all scheduler nodes. Configure them through the Quartz builder:

builder.Services.AddQuartz(q =>
{
    q.UseNCacheStore(builder.Configuration);

    q.UseDefaultThreadPool(10);
    q.RefuseExecutionGroups("reports");

    q.ConfigureScheduler(options =>
    {
        options.MaxBatchSize = 5;
        options.IdleWaitTime = TimeSpan.FromSeconds(1);
    });

    q.UseExecutionLimits(limits => limits
        .ForGroup("serial", 1, ExecutionLimitScope.Cluster)
        .ForGroup("emails", 3, ExecutionLimitScope.Node));
});

Custom Serialization

Built-in Quartz calendar types and the supported standard trigger types do not need additional serializers.

To persist application-defined calendar and custom trigger types, register the appropriate Quartz serializers on every scheduler node. JobDataMap supports built-in types and application-defined types registered through SystemTextJsonSerializerRegistry.AddTypeInfoResolver. In Quartz.NET 4.6.2, declared types containing list-like collection members (List<T>, arrays, HashSet<T>, or IEnumerable<T>) are rejected, while dictionaries and nested objects are supported. Custom values use default System.Text.Json serialization, so custom converters may not be supported. Ensure required types are available on all scheduler nodes. Unsupported values can cause a JobPersistenceException.

Logging

The integration logs through Microsoft.Extensions.Logging using the category NCache.OSS.Quartz.NCacheJobStore. Quartz.NET's scheduler logging remains separate under the Quartz categories.

Configure logging in appsettings.json:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "NCache.OSS.Quartz": "Information",
      "Quartz": "Information"
    }
  }
}

Set NCache.OSS.Quartz to Debug when investigating trigger acquisition and distributed-lock contention. The job store logs failures, retries, and recovery activity at appropriate levels; it does not log a message for every successful job at Information level.

Failure Recovery

Every active scheduler node periodically stores a check-in record in NCache. A node is considered stale when the time since its last check-in exceeds its recorded check-in interval plus ClusterCheckinMisfireThreshold. Recovery can include:

  • Returning acquired but unfired triggers to an eligible state.
  • Creating recovery triggers for jobs configured with Quartz's RequestRecovery().
  • Releasing trigger state blocked by executions on a failed node, subject to a grace period for non-concurrent jobs.
  • Repairing stale acquisitions and rebuilding job store registries during startup.

With the default 10-second check-in interval and 7.5-second additional threshold, a node is considered stale once more than 17.5 seconds have elapsed since its last check-in. Recovery occurs during a subsequent maintenance pass.

The NCache job store also releases stale trigger acquisitions when their age exceeds StaleAcquiredTriggerThreshold, even if the owning scheduler instance is active. By default, this threshold is the greater of twice MisfireThreshold and two minutes.

Troubleshooting

Problem Possible Cause Suggested Action
Scheduler fails to start with a cache exception The cache is missing, stopped, or unreachable. Ensure the cache is running and client connectivity is configured.
CacheName configuration error The required cache name is missing or empty. Supply CacheName in the selected configuration approach.
Scheduler name configuration error A named Quartz registration does not match SchedulerName. Use the same name for the registration and job store configuration.
Duplicate or unexpectedly recovered jobs More than one running process has the same InstanceId. Assign a unique ID to each running scheduler instance.
JobPersistenceException reports an expired lock The lock section ran longer than LockExpiration, or lock ownership was lost. Investigate delays and consider increasing LockExpiration for the workload.
Operations fail with a lock TimeoutException LockAcquireTimeout is too short under contention. Increase the timeout or leave it unset when waiting is acceptable.
A trigger enters the Error state Its job class cannot be loaded, or its referenced job is missing. Restore the required job type or state, then use Quartz's error-state reset operation where appropriate.

Important Considerations

  • Start the NCache cache before starting the Quartz scheduler.
  • Keep CacheName, SchedulerName, and KeyPrefix consistent across one scheduler cluster.
  • Assign a unique InstanceId to each active process.
  • Keep cluster timing options consistent across nodes.
  • Leave RepairRegistriesOnStart enabled unless startup scan cost requires a deliberate tradeoff.
  • Keep LockAcquireTimeout unset unless the application needs bounded lock waits.
  • Use WaitForJobsToComplete = true for normal graceful shutdown.
  • Capacity-plan the cache for job, trigger, and index data. The store marks its entries as NotRemovable; eviction-policy behavior still depends on the NCache cache configuration.

Limitations

  • UpdateTriggerDetails is not supported; use IScheduler.RescheduleJob, which replaces a trigger.
  • Custom triggers require a compatible Quartz trigger serializer on every node.
  • Quartz's cross-node interruption (RequestInterrupt) is not implemented by this store.
  • CancelPrevious cannot interrupt a firing on another scheduler node. Once the policy is settled, a new firing waits for an earlier remote firing of the same trigger to finish. When an existing trigger is changed to CancelPrevious while an earlier firing remains active, the transition can allow another firing alongside it.
  • Scheduling and firing require access to NCache. If the cache becomes unavailable, store operations cannot continue normally.
  • New triggers scheduled by a different node are discovered on a subsequent acquisition pass rather than via direct node-to-node notifications.

See Also

Quartz.NET
.NET Integrations

Contact Us

PHONE

+1 214-619-2601   (US)

+44 20 7993 8327   (UK)

 
EMAIL

sales@alachisoft.com

support@alachisoft.com

NCache
  • Edition Comparison
  • NCache Architecture
  • Benchmarks
Try NCache
Pricing
NCache Playground

Deployments
  • Cloud
  • Docker
  • Kubernetes
  • On-Premises
Technical Use Cases
  • ASP.NET Sessions
  • ASP.NET Core Sessions
  • Pub/Sub Messaging
  • SignalR Backplane
  • Internet of Things (IoT)
  • NoSQL Database
  • Stream Processing
  • Microservices
Resources
  • Blogs
  • Foundations
  • Videos
  • Comparisons
  • Shows
  • Whitepapers
  • Articles
  • Tech Talks
  • Magazine Articles
  • Third-Party Articles
Customer Case Studies
  • Testimonials
  • Customers
Support
  • Docs
  • Schedule a Demo
  • Tips
Company
  • Leadership
  • Partners
  • News
  • Events
  • Careers
Contact Us

  • EnglishChinese (Simplified)FrenchGermanItalianJapaneseKoreanPortugueseSpanish

  • Terms of Use
  •  
  • Privacy Policy
  •  
  • Cookie Policy
© Copyright Alachisoft 2002 - . All rights reserved. NCache is a registered trademark of Diyatech Corp.
  • Facebook
  • Twitter
  • Youtube
  • LinkedIn
  • RSS