Production Checklist
Before deploying, check how your workers handle retries, shutdown, and jobs left running after a crash.
Numeric scheduler options must be finite and non-negative. Concurrency, retry counts,
and payload byte limits must be safe integers; durations accept fractional milliseconds.
Polling, heartbeat, and retention check intervals must be at least 1 ms; these timers and shutdownTimeout
cannot exceed 2,147,483,647 ms (about 24.8 days). Invalid options throw MonqueError
when constructing Monque or registering a worker, including through Ts.ED.
Zero remains valid for limits and durations outside those repeating timers: for example,
statsCacheTtlMs: 0 disables statistics caching, zero concurrency prevents new claims,
and a retention age of zero makes terminal jobs eligible at the next cleanup.
Quick Reference
Section titled “Quick Reference”| Topic | Key Point |
|---|---|
| Idempotency | Workers must handle duplicate execution safely |
| Deduplication | Use uniqueKey to prevent duplicate jobs |
| Replica Set | Required for Change Streams |
| Shutdown | Configure shutdownTimeout appropriately |
| Recovery | Choose absolute locks or renewable leases; allow for slow runs and renewal delays |
1. Design for Idempotency
Section titled “1. Design for Idempotency”Jobs may be executed more than once due to:
- Failed attempts retry with exponential backoff.
- Recovery can return jobs with expired claims to pending, even while the original handler is still running.
- A network failure can prevent Monque from recording a completed run.
Idempotency Patterns
Section titled “Idempotency Patterns”For an external side effect, pass a stable business-operation key to a provider that
supports idempotency. The following paymentProvider is an application adapter: it must
forward the key to the provider and handle its response when an identical request is
already in flight. A local status check before calling the provider cannot prevent two
handlers from charging at once, or a second charge after a lost response.
Reuse the same key and immutable payment details on every attempt. Keep retries within
the provider’s key-retention period; after it expires, reconcile the payment before
sending another charge. A new Job ID or claimId is not a stable payment key.
For MongoDB-only effects, commit the deduplication record and the mutations in one
transaction. This example assumes validated string account and transfer identifiers, plus a positive
amount in integer minor units. Use collections from the same MongoClient that owns the session.
Create the collections and unique indexes before starting workers:
Account identifiers must also be unique. Keep completed transfer records for as long as the same transfer can be submitted again. If concurrent attempts race to insert the record, a duplicate-key error can escape the transaction; let the Job retry and read the committed record. Do not treat every duplicate-key error as a successful transfer. The record and both balance changes either commit together or roll back together.
MongoDB transactions require a replica set or sharded cluster. The driver can rerun the
transaction callback, so keep external calls outside it and pass session to every
operation. See the Node.js transaction guide.
See Idempotent Operations for more patterns.
2. Use uniqueKey for Deduplication
Section titled “2. Use uniqueKey for Deduplication”Prevent duplicate jobs from being enqueued using uniqueKey:
Always Use uniqueKey for Scheduled Jobs
Section titled “Always Use uniqueKey for Scheduled Jobs”Without uniqueKey, calling schedule() multiple times (e.g., on each app restart) creates duplicate scheduled jobs.
3. Use a Replica Set for Change Streams
Section titled “3. Use a Replica Set for Change Streams”Without a replica set:
- Monque falls back to polling only
- Job pickup latency increases to
pollInterval(default: 1000ms) - Real-time job notifications are unavailable
Local Development with Replica Set
Section titled “Local Development with Replica Set”Use Docker Compose for local development:
Initialize the replica set:
4. Configure Graceful Shutdown
Section titled “4. Configure Graceful Shutdown”Give running handlers time to finish before your application exits:
Kubernetes Considerations
Section titled “Kubernetes Considerations”Set terminationGracePeriodSeconds higher than shutdownTimeout:
5. Tune lockTimeout for Your Workload
Section titled “5. Tune lockTimeout for Your Workload”By default, lockTimeout is an
absolute limit measured from lockedAt. With recoverStaleJobs: true, initialization
returns expired processing jobs to pending. Heartbeats update liveness information but
do not extend an absolute lock. Expiry alone does not trigger recovery on a running
scheduler that has leases disabled.
Set the timeout above your longest expected run, with headroom for delays. Twice that duration can be a starting point, but it cannot guarantee that a slow handler will finish before recovery. Another initializing scheduler can recover an expired claim while its original handler still runs.
Renewable leases
Section titled “Renewable leases”Enable leaseDuration for jobs with variable runtimes or recovery without restarting a
surviving scheduler. Upgrade every scheduler sharing the collection before enabling it.
Older versions do not recognize renewed leases.
Heartbeats renew leaseExpiresAt using MongoDB’s clock. An expired lease cannot renew or
record a late result. A running scheduler with leases and recovery enabled checks for
expired claims after each heartbeat, including legacy absolute locks. Existing claims
keep their original mode, so retain a suitable lockTimeout during migration.
Allow for renewal delays, database outages, and event-loop pauses when sizing leases. Recovery does not cancel the original handler or undo its side effects. See Heartbeat Mechanism for recovery and shutdown details.
Diagnose delayed or duplicate execution
Section titled “Diagnose delayed or duplicate execution”| Symptom | Check |
|---|---|
| Overlapping executions | Absolute lock expired during a slow run, or a lease could not renew |
Jobs remain in processing after a crash | Recovery disabled, claim not expired, or no initialization or lease-enabled recovery pass |
| Slow recovery | Claim deadline, heartbeat interval, database availability, and event-loop delays |
6. Set Appropriate Concurrency
Section titled “6. Set Appropriate Concurrency”Configure workerConcurrency and per-worker concurrency based on your workload:
Concurrency limits in-flight work in one Scheduler Instance. It does not enforce a requests-per-second or requests-per-minute limit: fast jobs can start many times per second, and each additional instance has its own concurrency allowance. Monque has no built-in shared rate limit. Use a limiter shared by all callers before each external request, including retries inside a handler.
Instance-level Throttling
Section titled “Instance-level Throttling”Use instanceConcurrency to limit the total jobs processed by a single instance across all workers:
Choose a limit that fits the CPU, memory, and database connections available to one process.
See Concurrency Strategy for guidelines.
7. Consider Producer-Consumer Architecture
Section titled “7. Consider Producer-Consumer Architecture”Run API servers and workers in separate processes when they need different resources or deployment schedules:
Benefits
Section titled “Benefits”You can add worker capacity or deploy new handlers without redeploying the API servers. Workers also get their own CPU and memory, reducing contention with HTTP requests.
Implementation
Section titled “Implementation”Ts.ED Integration:
Core Package:
See Ts.ED Producer-only Mode for details.
8. Monitor Job Events
Section titled “8. Monitor Job Events”Log job failures and recovery events:
See MonqueEventMap for all available events.
9. Configure Job Retention
Section titled “9. Configure Job Retention”Prevent unbounded database growth by configuring retention:
10. Ensure Index Permissions
Section titled “10. Ensure Index Permissions”Monque creates MongoDB indexes during initialize().
Grant index creation permissions to that connection, or use skipIndexCreation: true and
manage the full index set yourself. Precreating indexes alone does not skip index creation.
See Indexes for listing and retention indexes.
Priority claims and deadline discovery need separate compound indexes. Keep both when managing indexes yourself, in addition to the other Monque indexes:
Use your configured collection name and preserve its collation. The priority index does not replace the deadline index. Existing deadline indexes remain usable; upgrading with automatic index creation adds the priority index without dropping them. With managed indexes, install the new index as part of the upgrade.
Index cost depends on the backlog. A priority scan can skip a group of future Jobs sharing one priority, but many distinct future priorities increase the index work. MongoDB may instead scan the due range through a deadline index and sort its candidates by priority. No one plan is cheapest for every distribution.
The MongoDB 8 integration test measures scheduler queries. After an urgent Job was claimed,
the test collection held 100 overdue Jobs at priority -1 and 1,000 Jobs due far in the future.
When the future Jobs shared priority 100, the next claim examined 2 keys and 1 document.
With 1,000 distinct future priorities, MongoDB sorted the due Jobs to select the first one,
examining 100 keys and 100 documents. With skipIndexCreation: true and only the existing
deadline index, that same distinct-priority case also selected the correct due Job, examining
100 keys and 100 documents. Deadline discovery examined 1 key and no documents in all three cases.
These results describe the test data; costs vary with your backlog. Inspect execution
statistics on your own data. To reproduce the test, run this from packages/core:
Initialization also sets missing Job priorities to 0, even when index creation is disabled.
Grant write access for this automatic upgrade. It preserves existing priorities, timestamps,
lifecycle state, and ownership metadata. Upgrade all producers and claiming Scheduler Instances
before relying on consistent priority ordering.
11. Configure Management and Dashboard Access
Section titled “11. Configure Management and Dashboard Access”If you serve the dashboard, mount authentication middleware before both the dashboard and
Management routers. Set API permissions with authorize or readOnly, and redact payload
fields that operators should not see. Protect the OpenAPI route with the same middleware.
Set a refresh interval appropriate for the number of open dashboards and your database load. Configure compression at the proxy or in Express, and preserve dashboard mount paths for direct links. See Dashboard configuration.
Pre-Deployment Checklist
Section titled “Pre-Deployment Checklist”- Workers are idempotent (safe for duplicate execution)
- Scheduled jobs use
uniqueKey - MongoDB is a replica set (for Change Streams)
-
SIGTERMhandler callsmonque.stop() - Absolute locks allow for slow runs, or renewable leases have enough renewal headroom
- Every scheduler sharing the collection supports leases before they are enabled
- External API rate limits are enforced across all callers separately from concurrency
- Concurrency matches workload characteristics
-
instanceConcurrencyconfigured if running multiple workers - Producer-consumer separation considered for high-scale deployments
- Event handlers for
job:failandstale:recovered - Job retention is configured
- Database user has index creation permissions
- Managed indexes include both priority claiming and deadline discovery coverage
- All producers and claiming Scheduler Instances support Job priority
- Dashboard, Management API, and OpenAPI routes require the intended authentication
- Payload redaction and action permissions are configured for operators
API Reference
Section titled “API Reference”Monque- Main scheduler classMonqueOptions- Configuration optionsMonqueEventMap- Available eventsEnqueueOptions- Job enqueue optionsWorkerOptions- Worker configuration