Before You Go Live
Each line is a decision another page explains; the link goes to that page. Skip what does not apply: a single-node in-memory scheduler can skip the schema lines, and a worker with no web stack the security ones.
Configuration
InstanceNameis set, and the same on every node of the cluster. It is theSCHED_NAMEcolumn of every row: two nodes with different names sharing a database are two schedulers, not one cluster — Clustering.InstanceIdis unique per node, and stable across restarts if you use node affinity. A clustered node with noInstanceIdgenerates one at every start: unique, not stable. For containers, see Naming a node in a container. If nodes serve different execution groups, every group in use has a live node that serves it, and the health check is on.StoreJobDataAsStringsis on, unless your job data holds something that is not a string. It keeps a stored map readable and free of class-versioning problems — Storing job data as strings.MaxConcurrencyis a number you chose, derived from what the database can serve across every node, not left at the default of 10 — Sizing a cluster. It boundsMaxBatchSize, and startup warns when the two disagree:MaxBatchSize is 25, which is more than the thread pool's MaxConcurrency of 10. Triggers acquired beyond the number of threads available to run them are held by this node until the pool drains.The connection pool is at least
MaxConcurrencyplus three. The scheduling loop, the misfire handler and the cluster check-in each need a connection of their own — The connection pool is the thread pool plus three.CommandTimeoutis set. The provider default is usually thirty seconds and applies to a statement that has already started — CommandTimeout.WaitForJobsToCompleteandHostOptions.ShutdownTimeoutfit your longest job. A shutdown budget shorter than the job kills it mid-flight on every deploy — Shutdown has a budget.Every job and trigger has a name you chose. A generated name is a new row on every start, so a persistent schedule keeps growing — Persistent job stores.
Schema
SchemaProvisioningis left atValidate, and Quartz's schema is applied by whatever applies the rest of yours. Creating tables needs a permission production databases usually do not grant — Creating the schema.- The fresh-install script's drop switch is
0when you run one against a database that already has data. It defaults to drop — Schema first, then nodes. - Upgrading from 3.x: the cron audit was run, then the 4.0 migration was applied. The migration is mandatory even for a database that took every optional 3.x one — Database Schema Migration. A stored expression 4.x rejects fails the read of the trigger, not only its firing — Before you upgrade. The full sequence is Upgrading a running deployment.
- The listing and acquisition indexes are present once the schema is large enough for a scan to show — Indexes, and the acquisition index in particular.
Monitor
quartz.job.execution.durationis exported. Its count is the number of executions, and the part taggederror.typeis the number of failures — Metrics.- There is an alert on a job you expect to see regularly. The health check does not assert that anything is firing; a scheduler with an empty schedule is healthy — Health checks and probes.
quartz.trigger.misfireandquartz.cluster.recovery.triggerare alerted on. Both counters normally stay flat — Metrics.- Somebody watches the node listing —
QueryClusterNodes(),GET /schedulers/{name}/nodes, or the dashboard's Cluster page — Reading the cluster. - The name attributes are dropped in a view before they reach the backend, unless job and trigger names are a bounded set. A per-tenant trigger name is unbounded cardinality — Metrics.
- The event ids you alert on are written down. Ids are stable across releases; message wording is not — Log Events.
- Warning 3716 and
quartz.jobstore.lock.wait.durationare alerted on. A node blocked on the job store lock produces no other signal — Log Events and Metrics.
Secure
MapQuartzHttpApi()states its authorization,IncludeStackTraceInProblemDetailsis off, andMaxPageSizeis left set. A job scheduled through the API names its type in a string from the request — Production hardening.- The dashboard is behind a policy, read-only, or both. Its pages start, stand by, shut down, pause, resume, delete and trigger — Production hardening.
- No secrets are in a
JobDataMap. It is persisted, readable in the database, and on every listing the API and the dashboard serve — Keep job data small, string-safe and free of secrets.
Rehearse
- The schedules are asserted in the time zone they will run in, and across a daylight-saving transition if they cross one. This needs no scheduler and takes microseconds — Level 0: schedules, with no scheduler and Crossing a daylight-saving transition.
- One failover has been rehearsed. Kill a node mid-job, watch the recovery, and confirm the work did not run twice (a property of your job, not of the scheduler) — When a peer takes over and Assume the job will run more than once.
- One restore has been rehearsed on a copy, to see what a restore does to work that was in flight at backup time — Backup and restore.
- A node that vanishes while holding the lock has been survived. Cutting a node's network is a different test from killing it: only the first leaves the lock held — A Lock Held by a Connection That Is Gone.
See also
- Operating a Cluster — the day-two half of this list
- Best Practices — the decisions behind most of the lines above
- Troubleshooting — for when one of them was missed
