@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.
- package/dist/schemas/system_db_schema.d.ts +4 -0
- package/dist/schemas/system_db_schema.d.ts.map +1 -1
- package/dist/schemas/system_db_schema.js.map +1 -1
- package/dist/src/adminserver.d.ts.map +1 -1
- package/dist/src/adminserver.js +6 -0
- package/dist/src/adminserver.js.map +1 -1
- package/dist/src/conductor/conductor.d.ts.map +1 -1
- package/dist/src/conductor/conductor.js +8 -0
- package/dist/src/conductor/conductor.js.map +1 -1
- package/dist/src/conductor/protocol.d.ts +4 -0
- package/dist/src/conductor/protocol.d.ts.map +1 -1
- package/dist/src/conductor/protocol.js.map +1 -1
- package/dist/src/dbos.d.ts.map +1 -1
- package/dist/src/dbos.js +3 -7
- package/dist/src/dbos.js.map +1 -1
- package/dist/src/sysdb_migrations/internal/migrations.js +10 -0
- package/dist/src/sysdb_migrations/internal/migrations.js.map +1 -1
- package/dist/src/system_database.d.ts +12 -5
- package/dist/src/system_database.d.ts.map +1 -1
- package/dist/src/system_database.js +138 -79
- package/dist/src/system_database.js.map +1 -1
- package/dist/src/wfqueue.d.ts +45 -6
- package/dist/src/wfqueue.d.ts.map +1 -1
- package/dist/src/wfqueue.js +336 -52
- package/dist/src/wfqueue.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
package/dist/src/wfqueue.d.ts
CHANGED
|
@@ -21,21 +21,32 @@ export interface QueueRateLimit {
|
|
|
21
21
|
}
|
|
22
22
|
/**
|
|
23
23
|
* Limit the number of concurrent workflows running for a queue.
|
|
24
|
-
*
|
|
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
|
-
|
|
33
|
+
globalConcurrency?: number;
|
|
31
34
|
/** If set, this limits the rate at which queued workflows are started */
|
|
32
35
|
rateLimit?: QueueRateLimit;
|
|
33
|
-
/** If
|
|
34
|
-
|
|
35
|
-
/** If
|
|
36
|
-
|
|
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":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAO/C,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACrE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAGrD;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,YAAY,EAAE,CAAC,EAAE,aAAa,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"wfqueue.d.ts","sourceRoot":"","sources":["../../src/wfqueue.ts"],"names":[],"mappings":"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"}
|
package/dist/src/wfqueue.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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.
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
191
|
-
|
|
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
|
-
|
|
199
|
-
|
|
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.
|
|
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 (
|
|
529
|
-
|
|
530
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
}
|