@dbos-inc/dbos-sdk 4.26.10 → 4.27.3-preview

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -21,21 +21,32 @@ export interface QueueRateLimit {
21
21
  }
22
22
  /**
23
23
  * Limit the number of concurrent workflows running for a queue.
24
- * This limit may be per worker or global
24
+ *
25
+ * Queue-wide limits bound the queue as a whole. Setting any `partition` limit
26
+ * additionally partitions the queue, so every enqueue must supply a partition
27
+ * key, and that limit is then enforced separately within each partition.
25
28
  */
26
29
  export interface QueueParameters {
27
30
  /** If defined, this limits the number of running workflows for a single DBOS process */
28
31
  workerConcurrency?: number;
29
32
  /** If defined, this limits the number of running workflows globally in the app */
30
- concurrency?: number;
33
+ globalConcurrency?: number;
31
34
  /** If set, this limits the rate at which queued workflows are started */
32
35
  rateLimit?: QueueRateLimit;
33
- /** If set, this queue supports priority */
34
- priorityEnabled?: boolean;
35
- /** If set, this queue supports partitioning */
36
- partitionQueue?: boolean;
36
+ /** If defined, this limits the number of running workflows globally within each partition */
37
+ partitionConcurrency?: number;
38
+ /** If defined, this limits the number of running workflows on a single DBOS process within each partition */
39
+ partitionWorkerConcurrency?: number;
40
+ /** If set, this limits the rate at which queued workflows are started within each partition */
41
+ partitionRateLimit?: QueueRateLimit;
37
42
  /** Base (minimum) polling interval in ms for this queue's dispatch loop (default 1000) */
38
43
  minPollingIntervalMs?: number;
44
+ /** @deprecated Use `globalConcurrency`. */
45
+ concurrency?: number;
46
+ /** @deprecated Priority is always enabled. */
47
+ priorityEnabled?: boolean;
48
+ /** @deprecated Use the partition limits, any of which partitions the queue. */
49
+ partitionQueue?: boolean;
39
50
  }
40
51
  /**
41
52
  * Behavior of `DBOS.registerQueue` / `DBOSClient.registerQueue` when a queue
@@ -53,6 +64,17 @@ export interface RegisterQueueOptions extends QueueParameters {
53
64
  /** How to behave when a queue with the same name already exists. */
54
65
  onConflict?: QueueConflictResolution;
55
66
  }
67
+ /** A queue's limits, each resolved to the scope it is enforced at. */
68
+ export interface ResolvedQueueLimits {
69
+ globalConcurrency?: number;
70
+ workerConcurrency?: number;
71
+ rateLimit?: QueueRateLimit;
72
+ partitionConcurrency?: number;
73
+ partitionWorkerConcurrency?: number;
74
+ partitionRateLimit?: QueueRateLimit;
75
+ }
76
+ /** Resolve every limit on a queue to the scope it is actually enforced at. */
77
+ export declare function resolveQueueLimits(q: WorkflowQueue): ResolvedQueueLimits;
56
78
  /**
57
79
  * Settings structure for a named workflow queue.
58
80
  * Workflow queues limit the rate and concurrency at which DBOS executes workflows.
@@ -70,6 +92,9 @@ export declare class WorkflowQueue {
70
92
  workerConcurrency?: number;
71
93
  priorityEnabled: boolean;
72
94
  partitionQueue: boolean;
95
+ partitionConcurrency?: number;
96
+ partitionWorkerConcurrency?: number;
97
+ partitionRateLimit?: QueueRateLimit;
73
98
  minPollingIntervalMs?: number;
74
99
  /** Owner from the queues table; undefined for in-memory and unclaimed queues. */
75
100
  applicationName?: string;
@@ -105,16 +130,30 @@ export declare class WorkflowQueue {
105
130
  * @internal
106
131
  */
107
132
  static _fromRecord(record: QueueRecord, clientSystemDatabase?: SystemDatabase): WorkflowQueue;
133
+ /** @deprecated Use `setGlobalConcurrency`. */
108
134
  setConcurrency(value: number | undefined): Promise<void>;
135
+ setGlobalConcurrency(value: number | undefined): Promise<void>;
109
136
  setWorkerConcurrency(value: number | undefined): Promise<void>;
110
137
  setRateLimit(value: QueueRateLimit | undefined): Promise<void>;
138
+ setPartitionConcurrency(value: number | undefined): Promise<void>;
139
+ setPartitionWorkerConcurrency(value: number | undefined): Promise<void>;
140
+ setPartitionRateLimit(value: QueueRateLimit | undefined): Promise<void>;
141
+ /** @deprecated Priority is always enabled. */
111
142
  setPriorityEnabled(value: boolean): Promise<void>;
143
+ /** @deprecated Use the partition limit setters. */
112
144
  setPartitionQueue(value: boolean): Promise<void>;
113
145
  setMinPollingIntervalMs(value: number): Promise<void>;
146
+ /** @deprecated Use `getGlobalConcurrency`. */
114
147
  getConcurrency(): Promise<number | undefined>;
148
+ getGlobalConcurrency(): Promise<number | undefined>;
115
149
  getWorkerConcurrency(): Promise<number | undefined>;
116
150
  getRateLimit(): Promise<QueueRateLimit | undefined>;
151
+ getPartitionConcurrency(): Promise<number | undefined>;
152
+ getPartitionWorkerConcurrency(): Promise<number | undefined>;
153
+ getPartitionRateLimit(): Promise<QueueRateLimit | undefined>;
154
+ /** @deprecated Priority is always enabled. */
117
155
  getPriorityEnabled(): Promise<boolean>;
156
+ /** @deprecated Use the partition limit getters. */
118
157
  getPartitionQueue(): Promise<boolean>;
119
158
  getMinPollingIntervalMs(): Promise<number | undefined>;
120
159
  }
@@ -1 +1 @@
1
- {"version":3,"file":"wfqueue.d.ts","sourceRoot":"","sources":["../../src/wfqueue.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"wfqueue.d.ts","sourceRoot":"","sources":["../../src/wfqueue.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.wfQueueRunner = exports.WorkflowQueue = exports.logQueue = void 0;
3
+ exports.wfQueueRunner = exports.WorkflowQueue = exports.resolveQueueLimits = exports.logQueue = void 0;
4
4
  const dbos_executor_1 = require("./dbos-executor");
5
5
  const dbos_1 = require("./dbos");
6
6
  const debugpoint_1 = require("./debugpoint");
@@ -12,12 +12,22 @@ const utils_1 = require("./utils");
12
12
  */
13
13
  function logQueue(logger, q) {
14
14
  const opts = [];
15
- if (q.concurrency !== undefined)
16
- opts.push(`concurrency=${q.concurrency}`);
15
+ if (q.concurrency !== undefined) {
16
+ // On a partitioned queue the queue-wide scope is worth naming explicitly.
17
+ opts.push(`${hasPartitionLimits(q) ? 'global_concurrency' : 'concurrency'}=${q.concurrency}`);
18
+ }
17
19
  if (q.workerConcurrency !== undefined)
18
20
  opts.push(`worker_concurrency=${q.workerConcurrency}`);
19
21
  if (q.rateLimit !== undefined)
20
22
  opts.push(`limit=${q.rateLimit.limitPerPeriod}/${q.rateLimit.periodSec}s`);
23
+ if (q.partitionConcurrency !== undefined)
24
+ opts.push(`partition_concurrency=${q.partitionConcurrency}`);
25
+ if (q.partitionWorkerConcurrency !== undefined) {
26
+ opts.push(`partition_worker_concurrency=${q.partitionWorkerConcurrency}`);
27
+ }
28
+ if (q.partitionRateLimit !== undefined) {
29
+ opts.push(`partition_limit=${q.partitionRateLimit.limitPerPeriod}/${q.partitionRateLimit.periodSec}s`);
30
+ }
21
31
  if (q.priorityEnabled)
22
32
  opts.push('priority');
23
33
  if (q.partitionQueue)
@@ -26,6 +36,67 @@ function logQueue(logger, q) {
26
36
  logger.info(`Queue: ${q.name}${optsStr}`);
27
37
  }
28
38
  exports.logQueue = logQueue;
39
+ /** True when any per-partition limit is set, which is what partitions a queue. */
40
+ function hasPartitionLimits(limits) {
41
+ return (limits.partitionConcurrency !== undefined ||
42
+ limits.partitionWorkerConcurrency !== undefined ||
43
+ limits.partitionRateLimit !== undefined);
44
+ }
45
+ /**
46
+ * True for the deprecated `partitionQueue` mode, under which `concurrency`,
47
+ * `workerConcurrency`, and `rateLimit` all apply per partition.
48
+ */
49
+ function isLegacyPartitioned(q) {
50
+ return q.partitionQueue && !hasPartitionLimits(q);
51
+ }
52
+ /** Resolve every limit on a queue to the scope it is actually enforced at. */
53
+ function resolveQueueLimits(q) {
54
+ if (isLegacyPartitioned(q)) {
55
+ return {
56
+ partitionConcurrency: q.concurrency,
57
+ partitionWorkerConcurrency: q.workerConcurrency,
58
+ partitionRateLimit: q.rateLimit,
59
+ };
60
+ }
61
+ return {
62
+ globalConcurrency: q.concurrency,
63
+ workerConcurrency: q.workerConcurrency,
64
+ rateLimit: q.rateLimit,
65
+ partitionConcurrency: q.partitionConcurrency,
66
+ partitionWorkerConcurrency: q.partitionWorkerConcurrency,
67
+ partitionRateLimit: q.partitionRateLimit,
68
+ };
69
+ }
70
+ exports.resolveQueueLimits = resolveQueueLimits;
71
+ /**
72
+ * Room left under this worker's queue-wide concurrency limit, given how many of
73
+ * its workflows are already running or claimed.
74
+ */
75
+ function workerBudget(limits, running) {
76
+ if (limits.partitionWorkerConcurrency !== undefined && limits.partitionWorkerConcurrency <= 0) {
77
+ // Zero per partition pauses this worker; the batched sweep enforces no per-partition worker limit of its own.
78
+ return 0;
79
+ }
80
+ if (limits.workerConcurrency === undefined) {
81
+ // A non-zero per-partition worker limit is enforced per partition instead.
82
+ return Infinity;
83
+ }
84
+ return Math.max(0, limits.workerConcurrency - running);
85
+ }
86
+ /** 40001 serialization_failure or 55P03 lock_not_available: a peer is claiming the same rows. */
87
+ function isContentionError(e) {
88
+ const code = e.code;
89
+ return code === '40001' || code === '55P03';
90
+ }
91
+ /** Fisher-Yates copy, so a sweep visits partitions in a different order each poll. */
92
+ function shuffled(items) {
93
+ const result = [...items];
94
+ for (let i = result.length - 1; i > 0; i--) {
95
+ const j = Math.floor(Math.random() * (i + 1));
96
+ [result[i], result[j]] = [result[j], result[i]];
97
+ }
98
+ return result;
99
+ }
29
100
  /**
30
101
  * Per-instance association of a client-bound queue to its `SystemDatabase`.
31
102
  * Stored off-class because any class member — including TS `private` — gives
@@ -49,6 +120,54 @@ function sysDBFor(q) {
49
120
  }
50
121
  return exec.systemDatabase;
51
122
  }
123
+ /** Reject a write that would silently re-scope the other limits on a legacy queue. */
124
+ function requireNotLegacyPartitioned(q, field) {
125
+ if (isLegacyPartitioned(q)) {
126
+ throw new Error(`Cannot set ${field} on queue ${q.name}: it is registered with the deprecated partitionQueue option, ` +
127
+ `under which concurrency, workerConcurrency, and rateLimit apply per partition. ` +
128
+ `Re-register the queue with the partition limits instead.`);
129
+ }
130
+ }
131
+ /** Validate a new queue-wide concurrency against the queue's other cached limits. */
132
+ function checkConcurrencyBounds(q, value) {
133
+ if (value === undefined)
134
+ return;
135
+ if (q.workerConcurrency !== undefined && q.workerConcurrency > value) {
136
+ throw new Error('workerConcurrency must be less than or equal to concurrency');
137
+ }
138
+ if (q.partitionConcurrency !== undefined && q.partitionConcurrency > value) {
139
+ throw new Error('partitionConcurrency must be less than or equal to globalConcurrency');
140
+ }
141
+ if (q.partitionWorkerConcurrency !== undefined && q.partitionWorkerConcurrency > value) {
142
+ throw new Error('partitionWorkerConcurrency must be less than or equal to concurrency');
143
+ }
144
+ }
145
+ /** Whether the queue is still partitioned once these limits take these values. */
146
+ function partitionedAfter(q, overrides) {
147
+ return hasPartitionLimits({
148
+ partitionConcurrency: q.partitionConcurrency,
149
+ partitionWorkerConcurrency: q.partitionWorkerConcurrency,
150
+ partitionRateLimit: q.partitionRateLimit,
151
+ ...overrides,
152
+ });
153
+ }
154
+ function rateLimitFromRecord(max, periodSec) {
155
+ return max !== null && periodSec !== null ? { limitPerPeriod: max, periodSec } : undefined;
156
+ }
157
+ /** Copy a persisted row's configuration onto a queue instance. */
158
+ function applyRecord(q, record) {
159
+ q.concurrency = record.concurrency ?? undefined;
160
+ q.workerConcurrency = record.workerConcurrency ?? undefined;
161
+ q.rateLimit = rateLimitFromRecord(record.rateLimitMax, record.rateLimitPeriodSec);
162
+ q.priorityEnabled = record.priorityEnabled;
163
+ q.partitionConcurrency = record.partitionConcurrency ?? undefined;
164
+ q.partitionWorkerConcurrency = record.partitionWorkerConcurrency ?? undefined;
165
+ q.partitionRateLimit = rateLimitFromRecord(record.partitionRateLimitMax, record.partitionRateLimitPeriodSec);
166
+ // Partitioning is inferred from the limits, so a row whose flag disagrees with them heals on read.
167
+ q.partitionQueue = record.partitionQueue || hasPartitionLimits(q);
168
+ q.minPollingIntervalMs = record.pollingIntervalSec * 1000;
169
+ q.applicationName = record.applicationName;
170
+ }
52
171
  /**
53
172
  * Re-read the queue's row from the database and update the cached fields on
54
173
  * `q` in place. No-op for in-memory queues. Throws if the row has been
@@ -61,16 +180,7 @@ async function refreshFromDb(q) {
61
180
  if (record === null) {
62
181
  throw new Error(`Queue '${q.name}' was not found in the database.`);
63
182
  }
64
- q.concurrency = record.concurrency ?? undefined;
65
- q.workerConcurrency = record.workerConcurrency ?? undefined;
66
- q.rateLimit =
67
- record.rateLimitMax !== null && record.rateLimitPeriodSec !== null
68
- ? { limitPerPeriod: record.rateLimitMax, periodSec: record.rateLimitPeriodSec }
69
- : undefined;
70
- q.priorityEnabled = record.priorityEnabled;
71
- q.partitionQueue = record.partitionQueue;
72
- q.minPollingIntervalMs = record.pollingIntervalSec * 1000;
73
- q.applicationName = record.applicationName;
183
+ applyRecord(q, record);
74
184
  }
75
185
  /**
76
186
  * Settings structure for a named workflow queue.
@@ -89,6 +199,9 @@ class WorkflowQueue {
89
199
  workerConcurrency;
90
200
  priorityEnabled = false;
91
201
  partitionQueue = false;
202
+ partitionConcurrency;
203
+ partitionWorkerConcurrency;
204
+ partitionRateLimit;
92
205
  minPollingIntervalMs;
93
206
  /** Owner from the queues table; undefined for in-memory and unclaimed queues. */
94
207
  applicationName;
@@ -120,11 +233,15 @@ class WorkflowQueue {
120
233
  params = { concurrency: arg2, rateLimit };
121
234
  }
122
235
  WorkflowQueue.validateQueueParams(params);
123
- this.concurrency = params.concurrency;
236
+ this.concurrency = params.globalConcurrency ?? params.concurrency;
124
237
  this.rateLimit = params.rateLimit;
125
238
  this.workerConcurrency = params.workerConcurrency;
126
239
  this.priorityEnabled = params.priorityEnabled ?? false;
127
- this.partitionQueue = params.partitionQueue ?? false;
240
+ this.partitionConcurrency = params.partitionConcurrency;
241
+ this.partitionWorkerConcurrency = params.partitionWorkerConcurrency;
242
+ this.partitionRateLimit = params.partitionRateLimit;
243
+ // Partitioning is inferred from any per-partition limit; the deprecated flag tracks it.
244
+ this.partitionQueue = (params.partitionQueue ?? false) || hasPartitionLimits(params);
128
245
  this.minPollingIntervalMs = params.minPollingIntervalMs;
129
246
  if (exports.wfQueueRunner.wfQueuesByName.has(name)) {
130
247
  throw Error(`Workflow Queue '${name}' defined multiple times`);
@@ -133,10 +250,51 @@ class WorkflowQueue {
133
250
  }
134
251
  /** Throws if any combination of queue parameters is invalid. */
135
252
  static validateQueueParams(params) {
136
- const { concurrency, workerConcurrency, rateLimit, minPollingIntervalMs } = params;
137
- if (workerConcurrency !== undefined && concurrency !== undefined && workerConcurrency > concurrency) {
253
+ const { concurrency, globalConcurrency, workerConcurrency, rateLimit, partitionConcurrency, partitionWorkerConcurrency, partitionRateLimit, partitionQueue, minPollingIntervalMs, } = params;
254
+ if (concurrency !== undefined && globalConcurrency !== undefined) {
255
+ throw new Error('concurrency is deprecated in favor of globalConcurrency; set only one of them');
256
+ }
257
+ if (partitionQueue && hasPartitionLimits(params)) {
258
+ throw new Error('partitionQueue is deprecated in favor of the partition limits; set only one of them');
259
+ }
260
+ if (partitionQueue && globalConcurrency !== undefined) {
261
+ throw new Error('partitionQueue applies every limit per partition, so it cannot be combined with globalConcurrency; use partitionConcurrency instead');
262
+ }
263
+ if (partitionConcurrency !== undefined && partitionConcurrency < 1) {
264
+ throw new Error('partitionConcurrency must be at least 1');
265
+ }
266
+ if (partitionWorkerConcurrency !== undefined && partitionWorkerConcurrency < 1) {
267
+ throw new Error('partitionWorkerConcurrency must be at least 1');
268
+ }
269
+ if (partitionRateLimit !== undefined &&
270
+ (partitionRateLimit.limitPerPeriod === undefined || partitionRateLimit.periodSec === undefined)) {
271
+ throw new Error('partitionRateLimit must specify both limitPerPeriod and periodSec');
272
+ }
273
+ if (partitionWorkerConcurrency !== undefined &&
274
+ partitionConcurrency !== undefined &&
275
+ partitionWorkerConcurrency > partitionConcurrency) {
276
+ throw new Error('partitionConcurrency must be greater than or equal to partitionWorkerConcurrency');
277
+ }
278
+ if (partitionWorkerConcurrency !== undefined &&
279
+ workerConcurrency !== undefined &&
280
+ partitionWorkerConcurrency > workerConcurrency) {
281
+ throw new Error('workerConcurrency must be greater than or equal to partitionWorkerConcurrency');
282
+ }
283
+ // Under the deprecated partitionQueue mode concurrency is itself a per-partition limit, so these compare like with like.
284
+ const queueConcurrency = globalConcurrency ?? concurrency;
285
+ if (workerConcurrency !== undefined && queueConcurrency !== undefined && workerConcurrency > queueConcurrency) {
138
286
  throw new Error('concurrency must be greater than or equal to workerConcurrency');
139
287
  }
288
+ if (partitionConcurrency !== undefined &&
289
+ queueConcurrency !== undefined &&
290
+ partitionConcurrency > queueConcurrency) {
291
+ throw new Error('globalConcurrency must be greater than or equal to partitionConcurrency');
292
+ }
293
+ if (partitionWorkerConcurrency !== undefined &&
294
+ queueConcurrency !== undefined &&
295
+ partitionWorkerConcurrency > queueConcurrency) {
296
+ throw new Error('concurrency must be greater than or equal to partitionWorkerConcurrency');
297
+ }
140
298
  if (minPollingIntervalMs !== undefined && minPollingIntervalMs <= 0) {
141
299
  throw new Error('minPollingIntervalMs must be positive');
142
300
  }
@@ -148,12 +306,17 @@ class WorkflowQueue {
148
306
  static recordFromParams(name, params) {
149
307
  return {
150
308
  name,
151
- concurrency: params.concurrency ?? null,
309
+ concurrency: params.globalConcurrency ?? params.concurrency ?? null,
152
310
  workerConcurrency: params.workerConcurrency ?? null,
153
311
  rateLimitMax: params.rateLimit ? params.rateLimit.limitPerPeriod : null,
154
312
  rateLimitPeriodSec: params.rateLimit ? params.rateLimit.periodSec : null,
155
313
  priorityEnabled: params.priorityEnabled ?? false,
156
- partitionQueue: params.partitionQueue ?? false,
314
+ // Any per-partition limit implies partitioning, whichever mode was used.
315
+ partitionQueue: (params.partitionQueue ?? false) || hasPartitionLimits(params),
316
+ partitionConcurrency: params.partitionConcurrency ?? null,
317
+ partitionWorkerConcurrency: params.partitionWorkerConcurrency ?? null,
318
+ partitionRateLimitMax: params.partitionRateLimit ? params.partitionRateLimit.limitPerPeriod : null,
319
+ partitionRateLimitPeriodSec: params.partitionRateLimit ? params.partitionRateLimit.periodSec : null,
157
320
  pollingIntervalSec: (params.minPollingIntervalMs ?? 1000) / 1000,
158
321
  };
159
322
  }
@@ -168,35 +331,42 @@ class WorkflowQueue {
168
331
  // in `wfQueuesByName`) and strip `readonly` so we can set the fields here.
169
332
  const q = Object.create(WorkflowQueue.prototype);
170
333
  q.name = record.name;
171
- q.concurrency = record.concurrency ?? undefined;
172
- q.workerConcurrency = record.workerConcurrency ?? undefined;
173
- q.rateLimit =
174
- record.rateLimitMax !== null && record.rateLimitPeriodSec !== null
175
- ? { limitPerPeriod: record.rateLimitMax, periodSec: record.rateLimitPeriodSec }
176
- : undefined;
177
- q.priorityEnabled = record.priorityEnabled;
178
- q.partitionQueue = record.partitionQueue;
179
- q.minPollingIntervalMs = record.pollingIntervalSec * 1000;
180
- q.applicationName = record.applicationName;
181
334
  q.databaseBacked = true;
182
335
  q.clientBound = clientSystemDatabase !== undefined;
336
+ applyRecord(q, record);
183
337
  if (clientSystemDatabase !== undefined) {
184
338
  clientSystemDatabases.set(q, clientSystemDatabase);
185
339
  }
186
340
  return q;
187
341
  }
342
+ /** @deprecated Use `setGlobalConcurrency`. */
188
343
  async setConcurrency(value) {
189
344
  requireDatabaseBacked(this);
190
- if (value !== undefined && this.workerConcurrency !== undefined && this.workerConcurrency > value) {
191
- throw new Error('workerConcurrency must be less than or equal to concurrency');
192
- }
345
+ // Refresh so the cross-field checks see the limits currently stored in the database.
346
+ await refreshFromDb(this);
347
+ checkConcurrencyBounds(this, value);
348
+ await sysDBFor(this).updateQueue(this.name, { concurrency: value ?? null });
349
+ this.concurrency = value;
350
+ }
351
+ async setGlobalConcurrency(value) {
352
+ requireDatabaseBacked(this);
353
+ await refreshFromDb(this);
354
+ requireNotLegacyPartitioned(this, 'globalConcurrency');
355
+ checkConcurrencyBounds(this, value);
193
356
  await sysDBFor(this).updateQueue(this.name, { concurrency: value ?? null });
194
357
  this.concurrency = value;
195
358
  }
196
359
  async setWorkerConcurrency(value) {
197
360
  requireDatabaseBacked(this);
198
- if (value !== undefined && this.concurrency !== undefined && value > this.concurrency) {
199
- throw new Error('workerConcurrency must be less than or equal to concurrency');
361
+ await refreshFromDb(this);
362
+ requireNotLegacyPartitioned(this, 'workerConcurrency');
363
+ if (value !== undefined) {
364
+ if (this.concurrency !== undefined && value > this.concurrency) {
365
+ throw new Error('workerConcurrency must be less than or equal to concurrency');
366
+ }
367
+ if (this.partitionWorkerConcurrency !== undefined && this.partitionWorkerConcurrency > value) {
368
+ throw new Error('partitionWorkerConcurrency must be less than or equal to workerConcurrency');
369
+ }
200
370
  }
201
371
  await sysDBFor(this).updateQueue(this.name, { workerConcurrency: value ?? null });
202
372
  this.workerConcurrency = value;
@@ -206,19 +376,95 @@ class WorkflowQueue {
206
376
  if (value !== undefined && (value.limitPerPeriod === undefined || value.periodSec === undefined)) {
207
377
  throw new Error('rateLimit must specify both limitPerPeriod and periodSec');
208
378
  }
379
+ // Refresh so the check below sees the partition limits currently stored in the database.
380
+ await refreshFromDb(this);
381
+ requireNotLegacyPartitioned(this, 'rateLimit');
209
382
  await sysDBFor(this).updateQueue(this.name, {
210
383
  rateLimitMax: value ? value.limitPerPeriod : null,
211
384
  rateLimitPeriodSec: value ? value.periodSec : null,
212
385
  });
213
386
  this.rateLimit = value;
214
387
  }
388
+ async setPartitionConcurrency(value) {
389
+ requireDatabaseBacked(this);
390
+ if (value !== undefined && value < 1) {
391
+ throw new Error('partitionConcurrency must be at least 1');
392
+ }
393
+ await refreshFromDb(this);
394
+ requireNotLegacyPartitioned(this, 'partitionConcurrency');
395
+ if (value !== undefined) {
396
+ if (this.concurrency !== undefined && value > this.concurrency) {
397
+ throw new Error('partitionConcurrency must be less than or equal to globalConcurrency');
398
+ }
399
+ if (this.partitionWorkerConcurrency !== undefined && this.partitionWorkerConcurrency > value) {
400
+ throw new Error('partitionConcurrency must be greater than or equal to partitionWorkerConcurrency');
401
+ }
402
+ }
403
+ // Partitioning is inferred from the limits, so the deprecated flag follows them.
404
+ const partitioned = partitionedAfter(this, { partitionConcurrency: value });
405
+ await sysDBFor(this).updateQueue(this.name, {
406
+ partitionConcurrency: value ?? null,
407
+ partitionQueue: partitioned,
408
+ });
409
+ this.partitionConcurrency = value;
410
+ this.partitionQueue = partitioned;
411
+ }
412
+ async setPartitionWorkerConcurrency(value) {
413
+ requireDatabaseBacked(this);
414
+ if (value !== undefined && value < 1) {
415
+ throw new Error('partitionWorkerConcurrency must be at least 1');
416
+ }
417
+ await refreshFromDb(this);
418
+ requireNotLegacyPartitioned(this, 'partitionWorkerConcurrency');
419
+ if (value !== undefined) {
420
+ if (this.partitionConcurrency !== undefined && value > this.partitionConcurrency) {
421
+ throw new Error('partitionWorkerConcurrency must be less than or equal to partitionConcurrency');
422
+ }
423
+ if (this.workerConcurrency !== undefined && value > this.workerConcurrency) {
424
+ throw new Error('partitionWorkerConcurrency must be less than or equal to workerConcurrency');
425
+ }
426
+ if (this.concurrency !== undefined && value > this.concurrency) {
427
+ throw new Error('partitionWorkerConcurrency must be less than or equal to concurrency');
428
+ }
429
+ }
430
+ const partitioned = partitionedAfter(this, { partitionWorkerConcurrency: value });
431
+ await sysDBFor(this).updateQueue(this.name, {
432
+ partitionWorkerConcurrency: value ?? null,
433
+ partitionQueue: partitioned,
434
+ });
435
+ this.partitionWorkerConcurrency = value;
436
+ this.partitionQueue = partitioned;
437
+ }
438
+ async setPartitionRateLimit(value) {
439
+ requireDatabaseBacked(this);
440
+ if (value !== undefined && (value.limitPerPeriod === undefined || value.periodSec === undefined)) {
441
+ throw new Error('partitionRateLimit must specify both limitPerPeriod and periodSec');
442
+ }
443
+ await refreshFromDb(this);
444
+ requireNotLegacyPartitioned(this, 'partitionRateLimit');
445
+ const partitioned = partitionedAfter(this, { partitionRateLimit: value });
446
+ await sysDBFor(this).updateQueue(this.name, {
447
+ partitionRateLimitMax: value ? value.limitPerPeriod : null,
448
+ partitionRateLimitPeriodSec: value ? value.periodSec : null,
449
+ partitionQueue: partitioned,
450
+ });
451
+ this.partitionRateLimit = value;
452
+ this.partitionQueue = partitioned;
453
+ }
454
+ /** @deprecated Priority is always enabled. */
215
455
  async setPriorityEnabled(value) {
216
456
  requireDatabaseBacked(this);
217
457
  await sysDBFor(this).updateQueue(this.name, { priorityEnabled: value });
218
458
  this.priorityEnabled = value;
219
459
  }
460
+ /** @deprecated Use the partition limit setters. */
220
461
  async setPartitionQueue(value) {
221
462
  requireDatabaseBacked(this);
463
+ // Refresh so the check below sees the partition limits currently stored in the database.
464
+ await refreshFromDb(this);
465
+ if (hasPartitionLimits(this)) {
466
+ throw new Error(`Cannot set partitionQueue on queue ${this.name}: it is partitioned by its partition limits. Clear those instead.`);
467
+ }
222
468
  await sysDBFor(this).updateQueue(this.name, { partitionQueue: value });
223
469
  this.partitionQueue = value;
224
470
  }
@@ -230,22 +476,41 @@ class WorkflowQueue {
230
476
  await sysDBFor(this).updateQueue(this.name, { pollingIntervalSec: value / 1000 });
231
477
  this.minPollingIntervalMs = value;
232
478
  }
479
+ /** @deprecated Use `getGlobalConcurrency`. */
233
480
  async getConcurrency() {
234
481
  await refreshFromDb(this);
235
482
  return this.concurrency;
236
483
  }
484
+ async getGlobalConcurrency() {
485
+ await refreshFromDb(this);
486
+ return resolveQueueLimits(this).globalConcurrency;
487
+ }
237
488
  async getWorkerConcurrency() {
238
489
  await refreshFromDb(this);
239
- return this.workerConcurrency;
490
+ return resolveQueueLimits(this).workerConcurrency;
240
491
  }
241
492
  async getRateLimit() {
242
493
  await refreshFromDb(this);
243
- return this.rateLimit;
494
+ return resolveQueueLimits(this).rateLimit;
244
495
  }
496
+ async getPartitionConcurrency() {
497
+ await refreshFromDb(this);
498
+ return resolveQueueLimits(this).partitionConcurrency;
499
+ }
500
+ async getPartitionWorkerConcurrency() {
501
+ await refreshFromDb(this);
502
+ return resolveQueueLimits(this).partitionWorkerConcurrency;
503
+ }
504
+ async getPartitionRateLimit() {
505
+ await refreshFromDb(this);
506
+ return resolveQueueLimits(this).partitionRateLimit;
507
+ }
508
+ /** @deprecated Priority is always enabled. */
245
509
  async getPriorityEnabled() {
246
510
  await refreshFromDb(this);
247
511
  return this.priorityEnabled;
248
512
  }
513
+ /** @deprecated Use the partition limit getters. */
249
514
  async getPartitionQueue() {
250
515
  await refreshFromDb(this);
251
516
  return this.partitionQueue;
@@ -515,35 +780,54 @@ class WFQueueRunner {
515
780
  }
516
781
  await exec.dispatchDequeuedWorkflows(wfids);
517
782
  };
783
+ const limits = resolveQueueLimits(queue);
784
+ const sysdb = exec.systemDatabase;
518
785
  // Dequeue workflows for this queue, either in one batched sweep across partitions or one partition at a time.
519
786
  try {
520
- if (queue.partitionQueue &&
521
- queue.concurrency === 1 &&
522
- queue.rateLimit === undefined &&
523
- queue.workerConcurrency !== 0) {
524
- // Batched path: one transaction claims every partition's head (valid only for concurrency=1, see findAndMarkStartablePartitionedWorkflows).
525
- const wfids = await exec.systemDatabase.findAndMarkStartablePartitionedWorkflows(queue, exec.executorID, utils_1.globalParams.appVersion);
787
+ if (!queue.partitionQueue) {
788
+ const wfids = await sysdb.findAndMarkStartableWorkflows(queue, exec.executorID, utils_1.globalParams.appVersion, undefined, sysdb.countRunningWorkflowsForQueue(queue.name));
526
789
  await dispatch(wfids);
527
790
  }
528
- else if (queue.partitionQueue) {
529
- // Every other partitioned config sweeps one partition at a time.
530
- const partitionKeys = await exec.systemDatabase.getQueuePartitions(queue.name);
791
+ else if (limits.partitionConcurrency === 1 &&
792
+ limits.globalConcurrency === undefined &&
793
+ limits.rateLimit === undefined &&
794
+ limits.partitionRateLimit === undefined) {
795
+ // Batched path: one transaction claims every partition's head (see findAndMarkStartablePartitionedWorkflows).
796
+ const maxTasks = workerBudget(limits, sysdb.countRunningWorkflowsForQueue(queue.name));
797
+ if (maxTasks > 0) {
798
+ const wfids = await sysdb.findAndMarkStartablePartitionedWorkflows(queue, exec.executorID, utils_1.globalParams.appVersion, maxTasks);
799
+ await dispatch(wfids);
800
+ }
801
+ }
802
+ else {
803
+ // Every other partitioned config sweeps one partition at a time, in random order to prevent starvation.
804
+ const partitionKeys = shuffled(await sysdb.getQueuePartitions(queue.name));
805
+ // Snapshot once: dispatch is asynchronous, so re-reading would count this sweep's own claims twice.
806
+ const running = sysdb.countRunningWorkflowsForQueue(queue.name);
807
+ let claimed = 0;
531
808
  for (const partitionKey of partitionKeys) {
532
- const partitionWfids = await exec.systemDatabase.findAndMarkStartableWorkflows(queue, exec.executorID, utils_1.globalParams.appVersion, partitionKey);
809
+ if (workerBudget(limits, running + claimed) <= 0)
810
+ break;
811
+ let partitionWfids;
812
+ try {
813
+ partitionWfids = await sysdb.findAndMarkStartableWorkflows(queue, exec.executorID, utils_1.globalParams.appVersion, partitionKey, running + claimed, sysdb.countRunningWorkflowsForPartition(queue.name, partitionKey));
814
+ }
815
+ catch (e) {
816
+ // Lock held or claim raced by another worker: skip just this partition, no queue-wide backoff.
817
+ if (isContentionError(e))
818
+ continue;
819
+ throw e;
820
+ }
821
+ claimed += partitionWfids.length;
533
822
  await dispatch(partitionWfids);
534
823
  await (0, debugpoint_1.debugTriggerPoint)(debugpoint_1.DEBUG_TRIGGER_BETWEEN_PARTITION_DISPATCHES);
535
824
  }
536
825
  }
537
- else {
538
- const wfids = await exec.systemDatabase.findAndMarkStartableWorkflows(queue, exec.executorID, utils_1.globalParams.appVersion, undefined);
539
- await dispatch(wfids);
540
- }
541
826
  }
542
827
  catch (e) {
543
828
  const err = e;
544
829
  // Handle serialization errors and lock contention with backoff
545
- if ('code' in err && (err.code === '40001' || err.code === '55P03')) {
546
- // 40001: serialization_failure, 55P03: lock_not_available
830
+ if (isContentionError(err)) {
547
831
  contentionDetected = true;
548
832
  exec.logger.warn(`Contention detected in queue ${queue.name}.`);
549
833
  }