Browse Source

docs(job-queue-plugin): Clarify BullMQ per-queue concurrency behavior

Add detailed documentation explaining that when using function-based
concurrency, workers are grouped by concurrency value, not queue name.
Since all Vendure job types share a single BullMQ queue (QUEUE_NAME),
jobs from different Vendure queues may be processed by the same worker.

Documents the affected symbols and recommends BullMQ Pro Groups for
strict per-queue isolation.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Will Nahmens 4 days ago
parent
commit
c64e257027

+ 15 - 2
packages/job-queue-plugin/src/bullmq/bullmq-job-queue-strategy.ts

@@ -55,6 +55,11 @@ export class BullMQJobQueueStrategy implements InspectableJobQueueStrategy {
     private connectionOptions: ConnectionOptions;
     private connectionOptions: ConnectionOptions;
     private queue: Queue;
     private queue: Queue;
     private worker: Worker;
     private worker: Worker;
+    /**
+     * When using function-based concurrency, workers are grouped by their concurrency value.
+     * Key: concurrency number, Value: Worker instance.
+     * Multiple Vendure queues with the same concurrency share a single worker.
+     */
     private workers = new Map<number, Worker>();
     private workers = new Map<number, Worker>();
     private workerProcessor: Processor;
     private workerProcessor: Processor;
     private options: BullMQPluginOptions;
     private options: BullMQPluginOptions;
@@ -287,7 +292,13 @@ export class BullMQJobQueueStrategy implements InspectableJobQueueStrategy {
     ): Promise<void> {
     ): Promise<void> {
         this.queueNameProcessFnMap.set(queueName, process);
         this.queueNameProcessFnMap.set(queueName, process);
 
 
-        // If concurrency is a function, we use per-concurrency workers
+        // If concurrency is a function, we create workers grouped by concurrency value.
+        // Note: Workers are stored in `this.workers` keyed by concurrency number, not queue name.
+        // All Vendure job types share a single BullMQ queue (`QUEUE_NAME`), so any worker can
+        // process any job type. This means multiple Vendure queues returning the same concurrency
+        // will share a worker, and the concurrency limit applies to total jobs processed by that
+        // worker—not strictly per Vendure queue. For strict per-queue isolation, use BullMQ Pro
+        // Groups or create separate BullMQ queues per Vendure queue.
         if (typeof this.options.concurrency === 'function') {
         if (typeof this.options.concurrency === 'function') {
             const concurrency = this.options.concurrency(queueName);
             const concurrency = this.options.concurrency(queueName);
             if (!this.workers.has(concurrency)) {
             if (!this.workers.has(concurrency)) {
@@ -317,7 +328,9 @@ export class BullMQJobQueueStrategy implements InspectableJobQueueStrategy {
                 this.workers.set(concurrency, worker);
                 this.workers.set(concurrency, worker);
             }
             }
 
 
-            // Subscribe to cancellation on first worker
+            // Subscribe to cancellation events once (on first worker creation).
+            // The `subscribeToCancellationEvents` handler broadcasts to all workers
+            // via `cancelRunningJob$` since any worker may be processing the job.
             if (this.workers.size === 1) {
             if (this.workers.size === 1) {
                 await this.cancellationSub.subscribe(this.CANCEL_JOB_CHANNEL);
                 await this.cancellationSub.subscribe(this.CANCEL_JOB_CHANNEL);
                 this.cancellationSub.on('message', this.subscribeToCancellationEvents);
                 this.cancellationSub.on('message', this.subscribeToCancellationEvents);

+ 14 - 0
packages/job-queue-plugin/src/bullmq/types.ts

@@ -48,6 +48,20 @@ export interface BullMQPluginOptions {
      * the concurrency limit. This is useful for limiting concurrency on
      * the concurrency limit. This is useful for limiting concurrency on
      * queues which have resource-intensive jobs.
      * queues which have resource-intensive jobs.
      *
      *
+     * **Important implementation note:** When using a function, workers are grouped
+     * by the _concurrency value_, not by queue name. Because all Vendure job types
+     * are stored in a single BullMQ queue (`QUEUE_NAME`), any worker can process
+     * any job type. This means:
+     *
+     * - Multiple Vendure queues returning the same concurrency value will share a worker
+     * - Jobs from different Vendure queues may be processed by the same worker
+     * - The concurrency limit applies to the total jobs processed by that worker,
+     *   not strictly per Vendure queue
+     *
+     * For strict per-queue concurrency isolation, consider:
+     * - Creating separate BullMQ queues per Vendure queue (requires custom implementation)
+     * - Using [BullMQ Pro Groups](https://docs.bullmq.io/bullmq-pro/groups)
+     *
      * @example
      * @example
      * ```ts
      * ```ts
      * BullMQJobQueuePlugin.init({
      * BullMQJobQueuePlugin.init({