Before You Go Live
For a scheduler that works in development and is about to carry production work. Nothing here is new — every line is a decision one of the other pages already explains, and the link goes to that page. The assembly is the point: these are the things that are cheap to get right on Friday and expensive to discover on Monday.
Skip what does not apply. A single-node in-memory scheduler owes you nothing on the schema lines; a worker with no web stack owes you nothing on the security ones.
Configuration
InstanceNameis set, and is the same on every node of the cluster. It is theSCHED_NAMEcolumn of every row, so two nodes with different names sharing a database are two schedulers rather than one cluster — Clustering.InstanceIdis unique per node, and stable across restarts if you use node affinity. The container case has its own answer — Naming a node in a container.StoreJobDataAsStringsis on unless something in your job data genuinely is not a string. It is the setting that 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 rather than from the default of 10 — Sizing a cluster. It boundsMaxBatchSize, and startup says so 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 one that is not a job's — 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.ShutdownTimeoutagree with your longest job. A shutdown budget shorter than the job is a job killed 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, and with a persistent store that is a schedule that grows — Persistent job stores.
Schema
SchemaProvisioningis left atValidate, and whatever applies the rest of your schema applies Quartz's — creating tables needs a permission a production database is usually right not to grant — Creating the schema.- The fresh-install script's drop switch is
0if you run one against a database that already has data. It defaults to drop — Schema first, then nodes. - Upgrading from 3.x: the 4.0 migration is applied, and the cron audit was run first. The migration is mandatory even for a database that took every optional 3.x one — Database Schema Migration — and a stored expression 4.x rejects fails the read of the trigger, not only its firing — Before you upgrade. The whole ordered sequence is Upgrading a running deployment.
- The listing and acquisition indexes are present if 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 of that count taggederror.typeis the number of failures, so one instrument answers both — Metrics.- There is an alert on a job you expect to see regularly. The health check does not assert that anything is firing, and a scheduler with an empty schedule is healthy by its definition — Health checks and probes.
quartz.trigger.misfireandquartz.cluster.recovery.triggerare alerted on. Both are counters that should normally stay flat, which makes them cheap to watch — 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 your 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. An id is stable across releases where a message's wording is not — Log Events.
Secure
MapQuartzHttpApi()says what it means about authorization,IncludeStackTraceInProblemDetailsis off, andMaxPageSizeis left set. A job scheduled through the API names its type as a string the request supplies — Production hardening.- The dashboard is behind a policy, or 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, it is readable in the database, and it is 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 really run in, and across a daylight-saving transition if they cross one. This costs microseconds and needs no scheduler — 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 — which is a property of your job rather than 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, because what a restore means for work that was in flight when the backup was taken is not obvious — Backup and restore.
See also
- Operating a Cluster — the day-two half of this list, in full
- Best Practices — the decisions behind most of the lines above
- Troubleshooting — for when one of them was missed
