Skip to content

MongoDB Deployment

Ratchet on MongoDB 6.0+.

Prerequisites

  • MongoDB 6.0 or later
  • WiredTiger storage engine (default since MongoDB 3.2)
  • A replica set or sharded cluster

Ratchet's MongoDB store uses atomic single-document operations for job claiming. State changes that also update a cross-collection invariant use MongoDB transactions, including active business-key ownership shared by queue jobs and recurring masters. Standalone mongod deployments do not support those transactions.

Lifecycle/status methods enter that transaction before changing the owner document, including for jobs without a business key; determining that no reservation exists outside the transaction would open a race with a concurrent save. Moving to this store version therefore requires a replica set or sharded cluster even when an application does not currently assign business keys.

Maven Dependency

xml
<dependency>
  <groupId>run.ratchet</groupId>
  <artifactId>ratchet-store-mongodb</artifactId>
  <version>0.4.0</version>
</dependency>

This pulls in the MongoDB sync driver. No ODM (Morphia, Spring Data) is required; the store uses the driver directly.

Collection Setup

You normally do not need a separate bootstrap step. The MongoDB store initializes collections and indexes from its own @PostConstruct, so they are created automatically when the store starts. The operation is idempotent and safe to run on every boot.

Collections

CollectionPurpose
scheduler_jobMain job store: status, payload, scheduling, priority
scheduler_business_key_reservationShared active business-key ownership for queue jobs and recurring masters
scheduler_recurring_jobRecurring job master records (cron/interval definitions)
scheduler_recurring_job_archiveArchived recurring job definitions
scheduler_batchBatch parent records and progress state
scheduler_batch_metricsBatch-level runtime metrics
scheduler_job_executionExecution history: start/end times, node, outcome
scheduler_job_logOptional per-job log storage if your application persists JobLogLine events
scheduler_job_archiveArchived completed/failed jobs
scheduler_lockDistributed advisory locks with TTL
scheduler_nodeCluster node heartbeats
scheduler_workflow_conditionWorkflow branch conditions
scheduler_resource_limitResource concurrency configuration
scheduler_resource_permitResource permit tracking for rate limiting

Key Indexes

The initializer creates these indexes for query performance:

scheduler_job

IndexFieldsNotes
idx_job_claim_execstatus, job_type, priority DESC, scheduled_time, _idExecutable claim candidate filtering
idx_job_poll_compositestatus, priority DESC, scheduled_timeGeneral due-job lookup
idx_job_idempotency_keyidempotency_keyUnique (global dedup)
idx_job_active_business_keybusiness_keyUnique partial defense within queue jobs (PENDING/RUNNING/PAUSED/WAITING only)
idx_job_tagstagsMultikey index for tag-based queries
idx_job_picked_bypicked_byFind jobs claimed by a specific node

scheduler_business_key_reservation

The reservation document's _id is the business key, so MongoDB's built-in _id_ index is the single unique serialization point across queue jobs and recurring masters. idx_bk_owner indexes owner_job_id for terminal and cancel cleanup. Reservation and owner changes commit in the same transaction.

scheduler_lock

IndexFieldsNotes
idx_lock_ttlexpires_atTTL index (MongoDB auto-deletes expired locks)

scheduler_job_execution

IndexFieldsNotes
idx_execution_job_idjob_idExecution history lookup
idx_execution_node_startednode_id, started_atPer-node execution queries

How It Differs from SQL Stores

MongoDB doesn't have SELECT ... FOR UPDATE SKIP LOCKED. Instead, the MongoDB store first ranks due candidates with an aggregation that computes effective priority, then claims each selected ID with an atomic findOneAndUpdate:

javascript
// Claim one selected candidate ID (atomic)
db.scheduler_job.findOneAndUpdate(
  { _id: candidateId, status: "PENDING" },
  { $set: { status: "RUNNING", picked_by: nodeId, picked_at: ISODate() } },
  { returnDocument: "after" }
)

This gives the same guarantee (no two nodes claim the same job) through MongoDB's document-level write lock rather than row-level SKIP LOCKED.

UUIDv7 Identifiers

Ratchet uses the same RFC 9562 §5.7 UUIDv7 identifiers on MongoDB as it does for SQL stores. Those IDs are generated in the application, stored in _id as BSON binary subtype 4 (UuidRepresentation.STANDARD), and remain time-ordered without a database counter collection. The MongoClientFactory enforces the STANDARD representation; a @PostConstruct probe in MongoJobStoreImpl fails fast if a caller-supplied client uses any other UUID encoding.

Tags

In SQL stores, tags use a separate scheduler_job_tag join table. In MongoDB, tags are embedded as an array field on the job document with a multikey index, which is more natural for document storage and eliminates the join.

Configuration

Shared Options

Use RatchetOptions.builder().store(s -> s.priorityBoostIntervalMinutes(...)) to configure the shared starvation-prevention priority boost interval. The default is 15 minutes.

Connection

The MongoDB store injects a MongoDatabase CDI bean. Keep the underlying MongoClient as an application-scoped resource and close it at shutdown:

java
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoDatabase;
import jakarta.annotation.PreDestroy;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Produces;

@ApplicationScoped
public class MongoProducer {
  private MongoClient client;

  @Produces
  @ApplicationScoped
  public MongoDatabase mongoDatabase() {
    if (client == null) {
      client = MongoClients.create("mongodb://localhost:27017");
    }
    return client.getDatabase("ratchet");
  }

  @PreDestroy
  void close() {
    if (client != null) {
      client.close();
    }
  }
}

With MicroProfile Config, read the URI and database name from your own application properties:

java
@Produces
@ApplicationScoped
public MongoDatabase mongoDatabase(
    @ConfigProperty(name = "mongodb.uri") String uri,
    @ConfigProperty(name = "mongodb.database", defaultValue = "ratchet") String database) {
  client = MongoClients.create(uri);
  return client.getDatabase(database);
}

Required unique indexes are created at startup. If Ratchet cannot create the idempotency or active-business-key indexes, startup fails so duplicate scheduling semantics are not silently weakened.

When upgrading an existing database, startup backfills reservations for active queue jobs and live recurring masters. If the old data already contains the same business key in both collections, startup fails and reports the conflicting owners; resolve that conflict before retrying. Perform a quiescent, coordinated upgrade: stop scheduler writes from all old nodes, let one upgraded node complete initialization, and then start the remaining upgraded nodes. Do not run old and new Ratchet versions together, because old MongoDB store code does not write the shared reservation collection.

Monitoring

Use MongoDB's built-in profiler to identify slow queries:

javascript
db.setProfilingLevel(1, { slowms: 100 })
db.system.profile.find({ ns: /scheduler_/ }).sort({ ts: -1 }).limit(10)

Key metrics to watch:

  • scheduler_job collection size and index hit rates
  • Lock acquisition time on scheduler_lock
  • findOneAndUpdate latency on claim operations

See Also