pg-boss 10.3.3 → 11.0.1
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/README.md +3 -3
- package/docker-compose.yaml +1 -1
- package/package.json +3 -3
- package/src/attorney.js +69 -220
- package/src/boss.js +100 -117
- package/src/db.js +3 -0
- package/src/index.js +6 -12
- package/src/manager.js +225 -191
- package/src/migrationStore.js +0 -88
- package/src/plans.js +469 -446
- package/src/timekeeper.js +46 -40
- package/src/tools.js +19 -2
- package/types.d.ts +78 -137
- package/version.json +1 -1
package/src/plans.js
CHANGED
|
@@ -28,36 +28,35 @@ module.exports = {
|
|
|
28
28
|
completeJobs,
|
|
29
29
|
cancelJobs,
|
|
30
30
|
resumeJobs,
|
|
31
|
-
deleteJobs,
|
|
32
31
|
retryJobs,
|
|
32
|
+
deleteJobsById,
|
|
33
|
+
deleteAllJobs,
|
|
34
|
+
deleteQueuedJobs,
|
|
35
|
+
deleteStoredJobs,
|
|
36
|
+
truncateTable,
|
|
33
37
|
failJobsById,
|
|
34
38
|
failJobsByTimeout,
|
|
35
|
-
insertJob,
|
|
36
39
|
insertJobs,
|
|
37
40
|
getTime,
|
|
38
41
|
getSchedules,
|
|
42
|
+
getSchedulesByQueue,
|
|
39
43
|
schedule,
|
|
40
44
|
unschedule,
|
|
41
45
|
subscribe,
|
|
42
46
|
unsubscribe,
|
|
43
47
|
getQueuesForEvent,
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
countStates,
|
|
48
|
+
deletion,
|
|
49
|
+
cacheQueueStats,
|
|
47
50
|
updateQueue,
|
|
48
51
|
createQueue,
|
|
49
52
|
deleteQueue,
|
|
50
53
|
getQueues,
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
clearStorage,
|
|
55
|
-
trySetMaintenanceTime,
|
|
56
|
-
trySetMonitorTime,
|
|
54
|
+
getQueueStats,
|
|
55
|
+
trySetQueueMonitorTime,
|
|
56
|
+
trySetQueueDeletionTime,
|
|
57
57
|
trySetCronTime,
|
|
58
58
|
locked,
|
|
59
59
|
assertMigration,
|
|
60
|
-
getArchivedJobById,
|
|
61
60
|
getJobById,
|
|
62
61
|
QUEUE_POLICIES,
|
|
63
62
|
JOB_STATES,
|
|
@@ -66,7 +65,22 @@ module.exports = {
|
|
|
66
65
|
DEFAULT_SCHEMA
|
|
67
66
|
}
|
|
68
67
|
|
|
69
|
-
const
|
|
68
|
+
const COMMON_JOB_TABLE = 'job_common'
|
|
69
|
+
|
|
70
|
+
const FIFTEEN_MINUTES = 60 * 15
|
|
71
|
+
const FORTEEN_DAYS = 60 * 60 * 24 * 14
|
|
72
|
+
const SEVEN_DAYS = 60 * 60 * 24 * 7
|
|
73
|
+
|
|
74
|
+
const QUEUE_DEFAULTS = {
|
|
75
|
+
expire_seconds: FIFTEEN_MINUTES,
|
|
76
|
+
retention_seconds: FORTEEN_DAYS,
|
|
77
|
+
deletion_seconds: SEVEN_DAYS,
|
|
78
|
+
retry_limit: 2,
|
|
79
|
+
retry_delay: 0,
|
|
80
|
+
warning_queued: 0,
|
|
81
|
+
retry_backoff: false,
|
|
82
|
+
partition: false
|
|
83
|
+
}
|
|
70
84
|
|
|
71
85
|
function create (schema, version) {
|
|
72
86
|
const commands = [
|
|
@@ -80,11 +94,7 @@ function create (schema, version) {
|
|
|
80
94
|
|
|
81
95
|
createTableJob(schema),
|
|
82
96
|
createPrimaryKeyJob(schema),
|
|
83
|
-
|
|
84
|
-
createTableArchive(schema),
|
|
85
|
-
createPrimaryKeyArchive(schema),
|
|
86
|
-
createColumnArchiveArchivedOn(schema),
|
|
87
|
-
createIndexArchiveArchivedOn(schema),
|
|
97
|
+
createTableJobCommon(schema, COMMON_JOB_TABLE),
|
|
88
98
|
|
|
89
99
|
createQueueFunction(schema),
|
|
90
100
|
deleteQueueFunction(schema),
|
|
@@ -96,9 +106,7 @@ function create (schema, version) {
|
|
|
96
106
|
}
|
|
97
107
|
|
|
98
108
|
function createSchema (schema) {
|
|
99
|
-
return `
|
|
100
|
-
CREATE SCHEMA IF NOT EXISTS ${schema}
|
|
101
|
-
`
|
|
109
|
+
return `CREATE SCHEMA IF NOT EXISTS ${schema}`
|
|
102
110
|
}
|
|
103
111
|
|
|
104
112
|
function createEnumJobState (schema) {
|
|
@@ -120,9 +128,7 @@ function createTableVersion (schema) {
|
|
|
120
128
|
return `
|
|
121
129
|
CREATE TABLE ${schema}.version (
|
|
122
130
|
version int primary key,
|
|
123
|
-
|
|
124
|
-
cron_on timestamp with time zone,
|
|
125
|
-
monitored_on timestamp with time zone
|
|
131
|
+
cron_on timestamp with time zone
|
|
126
132
|
)
|
|
127
133
|
`
|
|
128
134
|
}
|
|
@@ -130,15 +136,26 @@ function createTableVersion (schema) {
|
|
|
130
136
|
function createTableQueue (schema) {
|
|
131
137
|
return `
|
|
132
138
|
CREATE TABLE ${schema}.queue (
|
|
133
|
-
name text,
|
|
134
|
-
policy text,
|
|
135
|
-
retry_limit int,
|
|
136
|
-
retry_delay int,
|
|
137
|
-
retry_backoff bool,
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
139
|
+
name text NOT NULL,
|
|
140
|
+
policy text NOT NULL,
|
|
141
|
+
retry_limit int NOT NULL,
|
|
142
|
+
retry_delay int NOT NULL,
|
|
143
|
+
retry_backoff bool NOT NULL,
|
|
144
|
+
retry_delay_max int,
|
|
145
|
+
expire_seconds int NOT NULL,
|
|
146
|
+
retention_seconds int NOT NULL,
|
|
147
|
+
deletion_seconds int NOT NULL,
|
|
148
|
+
dead_letter text REFERENCES ${schema}.queue (name) CHECK (dead_letter IS DISTINCT FROM name),
|
|
149
|
+
partition bool NOT NULL,
|
|
150
|
+
table_name text NOT NULL,
|
|
151
|
+
deferred_count int NOT NULL default 0,
|
|
152
|
+
queued_count int NOT NULL default 0,
|
|
153
|
+
warning_queued int NOT NULL default 0,
|
|
154
|
+
active_count int NOT NULL default 0,
|
|
155
|
+
total_count int NOT NULL default 0,
|
|
156
|
+
singletons_active text[],
|
|
157
|
+
monitor_on timestamp with time zone,
|
|
158
|
+
maintain_on timestamp with time zone,
|
|
142
159
|
created_on timestamp with time zone not null default now(),
|
|
143
160
|
updated_on timestamp with time zone not null default now(),
|
|
144
161
|
PRIMARY KEY (name)
|
|
@@ -150,13 +167,14 @@ function createTableSchedule (schema) {
|
|
|
150
167
|
return `
|
|
151
168
|
CREATE TABLE ${schema}.schedule (
|
|
152
169
|
name text REFERENCES ${schema}.queue ON DELETE CASCADE,
|
|
170
|
+
key text not null DEFAULT '',
|
|
153
171
|
cron text not null,
|
|
154
172
|
timezone text,
|
|
155
173
|
data jsonb,
|
|
156
174
|
options jsonb,
|
|
157
175
|
created_on timestamp with time zone not null default now(),
|
|
158
176
|
updated_on timestamp with time zone not null default now(),
|
|
159
|
-
PRIMARY KEY (name)
|
|
177
|
+
PRIMARY KEY (name, key)
|
|
160
178
|
)
|
|
161
179
|
`
|
|
162
180
|
}
|
|
@@ -180,19 +198,21 @@ function createTableJob (schema) {
|
|
|
180
198
|
name text not null,
|
|
181
199
|
priority integer not null default(0),
|
|
182
200
|
data jsonb,
|
|
183
|
-
state ${schema}.job_state not null default
|
|
184
|
-
retry_limit integer not null default
|
|
185
|
-
retry_count integer not null default
|
|
186
|
-
retry_delay integer not null default
|
|
187
|
-
retry_backoff boolean not null default
|
|
188
|
-
|
|
189
|
-
|
|
201
|
+
state ${schema}.job_state not null default '${JOB_STATES.created}',
|
|
202
|
+
retry_limit integer not null default ${QUEUE_DEFAULTS.retry_limit},
|
|
203
|
+
retry_count integer not null default 0,
|
|
204
|
+
retry_delay integer not null default ${QUEUE_DEFAULTS.retry_delay},
|
|
205
|
+
retry_backoff boolean not null default ${QUEUE_DEFAULTS.retry_backoff},
|
|
206
|
+
retry_delay_max integer,
|
|
207
|
+
expire_seconds int not null default ${QUEUE_DEFAULTS.expire_seconds},
|
|
208
|
+
deletion_seconds int not null default ${QUEUE_DEFAULTS.deletion_seconds},
|
|
190
209
|
singleton_key text,
|
|
191
210
|
singleton_on timestamp without time zone,
|
|
192
|
-
|
|
211
|
+
start_after timestamp with time zone not null default now(),
|
|
193
212
|
created_on timestamp with time zone not null default now(),
|
|
213
|
+
started_on timestamp with time zone,
|
|
194
214
|
completed_on timestamp with time zone,
|
|
195
|
-
keep_until timestamp with time zone NOT NULL default now() + interval '
|
|
215
|
+
keep_until timestamp with time zone NOT NULL default now() + interval '${QUEUE_DEFAULTS.retention_seconds}',
|
|
196
216
|
output jsonb,
|
|
197
217
|
dead_letter text,
|
|
198
218
|
policy text
|
|
@@ -200,8 +220,8 @@ function createTableJob (schema) {
|
|
|
200
220
|
`
|
|
201
221
|
}
|
|
202
222
|
|
|
203
|
-
const
|
|
204
|
-
const
|
|
223
|
+
const JOB_COLUMNS_MIN = 'id, name, data, expire_seconds as "expireInSeconds"'
|
|
224
|
+
const JOB_COLUMNS_ALL = `${JOB_COLUMNS_MIN},
|
|
205
225
|
policy,
|
|
206
226
|
state,
|
|
207
227
|
priority,
|
|
@@ -209,11 +229,12 @@ const allJobColumns = `${baseJobColumns},
|
|
|
209
229
|
retry_count as "retryCount",
|
|
210
230
|
retry_delay as "retryDelay",
|
|
211
231
|
retry_backoff as "retryBackoff",
|
|
232
|
+
retry_delay_max as "retryDelayMax",
|
|
212
233
|
start_after as "startAfter",
|
|
213
234
|
started_on as "startedOn",
|
|
214
235
|
singleton_key as "singletonKey",
|
|
215
236
|
singleton_on as "singletonOn",
|
|
216
|
-
|
|
237
|
+
deletion_seconds as "deleteAfterSeconds",
|
|
217
238
|
created_on as "createdOn",
|
|
218
239
|
completed_on as "completedOn",
|
|
219
240
|
keep_until as "keepUntil",
|
|
@@ -221,61 +242,90 @@ const allJobColumns = `${baseJobColumns},
|
|
|
221
242
|
output
|
|
222
243
|
`
|
|
223
244
|
|
|
245
|
+
function createTableJobCommon (schema, table) {
|
|
246
|
+
const format = command => command.replaceAll('.job', `.${table}`) + ';'
|
|
247
|
+
|
|
248
|
+
return `
|
|
249
|
+
CREATE TABLE ${schema}.${table} (LIKE ${schema}.job INCLUDING GENERATED INCLUDING DEFAULTS);
|
|
250
|
+
${format(createPrimaryKeyJob(schema))}
|
|
251
|
+
${format(createQueueForeignKeyJob(schema))}
|
|
252
|
+
${format(createQueueForeignKeyJobDeadLetter(schema))}
|
|
253
|
+
${format(createIndexJobPolicyShort(schema))}
|
|
254
|
+
${format(createIndexJobPolicySingleton(schema))}
|
|
255
|
+
${format(createIndexJobPolicyStately(schema))}
|
|
256
|
+
${format(createIndexJobThrottle(schema))}
|
|
257
|
+
${format(createIndexJobFetch(schema))}
|
|
258
|
+
|
|
259
|
+
ALTER TABLE ${schema}.job ATTACH PARTITION ${schema}.${table} DEFAULT;
|
|
260
|
+
`
|
|
261
|
+
}
|
|
262
|
+
|
|
224
263
|
function createQueueFunction (schema) {
|
|
225
264
|
return `
|
|
226
|
-
CREATE FUNCTION ${schema}.create_queue(queue_name text, options
|
|
265
|
+
CREATE FUNCTION ${schema}.create_queue(queue_name text, options jsonb)
|
|
227
266
|
RETURNS VOID AS
|
|
228
267
|
$$
|
|
229
268
|
DECLARE
|
|
230
|
-
|
|
269
|
+
tablename varchar := CASE WHEN options->>'partition' = 'true'
|
|
270
|
+
THEN 'j' || encode(sha224(queue_name::bytea), 'hex')
|
|
271
|
+
ELSE '${COMMON_JOB_TABLE}'
|
|
272
|
+
END;
|
|
231
273
|
queue_created_on timestamptz;
|
|
232
274
|
BEGIN
|
|
233
275
|
|
|
234
276
|
WITH q as (
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
(
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
277
|
+
INSERT INTO ${schema}.queue (
|
|
278
|
+
name,
|
|
279
|
+
policy,
|
|
280
|
+
retry_limit,
|
|
281
|
+
retry_delay,
|
|
282
|
+
retry_backoff,
|
|
283
|
+
retry_delay_max,
|
|
284
|
+
expire_seconds,
|
|
285
|
+
retention_seconds,
|
|
286
|
+
deletion_seconds,
|
|
287
|
+
warning_queued,
|
|
288
|
+
dead_letter,
|
|
289
|
+
partition,
|
|
290
|
+
table_name
|
|
291
|
+
)
|
|
292
|
+
VALUES (
|
|
293
|
+
queue_name,
|
|
294
|
+
options->>'policy',
|
|
295
|
+
COALESCE((options->>'retryLimit')::int, ${QUEUE_DEFAULTS.retry_limit}),
|
|
296
|
+
COALESCE((options->>'retryDelay')::int, ${QUEUE_DEFAULTS.retry_delay}),
|
|
297
|
+
COALESCE((options->>'retryBackoff')::bool, ${QUEUE_DEFAULTS.retry_backoff}),
|
|
298
|
+
(options->>'retryDelayMax')::int,
|
|
299
|
+
COALESCE((options->>'expireInSeconds')::int, ${QUEUE_DEFAULTS.expire_seconds}),
|
|
300
|
+
COALESCE((options->>'retentionSeconds')::int, ${QUEUE_DEFAULTS.retention_seconds}),
|
|
301
|
+
COALESCE((options->>'deleteAfterSeconds')::int, ${QUEUE_DEFAULTS.deletion_seconds}),
|
|
302
|
+
COALESCE((options->>'warningQueueSize')::int, ${QUEUE_DEFAULTS.warning_queued}),
|
|
303
|
+
options->>'deadLetter',
|
|
304
|
+
COALESCE((options->>'partition')::bool, ${QUEUE_DEFAULTS.partition}),
|
|
305
|
+
tablename
|
|
306
|
+
)
|
|
307
|
+
ON CONFLICT DO NOTHING
|
|
308
|
+
RETURNING created_on
|
|
259
309
|
)
|
|
260
310
|
SELECT created_on into queue_created_on from q;
|
|
261
311
|
|
|
262
|
-
IF queue_created_on IS NULL THEN
|
|
312
|
+
IF queue_created_on IS NULL OR options->>'partition' IS DISTINCT FROM 'true' THEN
|
|
263
313
|
RETURN;
|
|
264
314
|
END IF;
|
|
265
315
|
|
|
266
|
-
EXECUTE format('CREATE TABLE ${schema}.%I (LIKE ${schema}.job INCLUDING DEFAULTS)',
|
|
267
|
-
|
|
268
|
-
EXECUTE format('${formatPartitionCommand(createPrimaryKeyJob(schema))}',
|
|
269
|
-
EXECUTE format('${formatPartitionCommand(createQueueForeignKeyJob(schema))}',
|
|
270
|
-
EXECUTE format('${formatPartitionCommand(createQueueForeignKeyJobDeadLetter(schema))}',
|
|
271
|
-
EXECUTE format('${formatPartitionCommand(createIndexJobPolicyShort(schema))}',
|
|
272
|
-
EXECUTE format('${formatPartitionCommand(createIndexJobPolicySingleton(schema))}',
|
|
273
|
-
EXECUTE format('${formatPartitionCommand(createIndexJobPolicyStately(schema))}',
|
|
274
|
-
EXECUTE format('${formatPartitionCommand(createIndexJobThrottle(schema))}',
|
|
275
|
-
EXECUTE format('${formatPartitionCommand(createIndexJobFetch(schema))}',
|
|
276
|
-
|
|
277
|
-
EXECUTE format('ALTER TABLE ${schema}.%I ADD CONSTRAINT cjc CHECK (name=%L)',
|
|
278
|
-
EXECUTE format('ALTER TABLE ${schema}.job ATTACH PARTITION ${schema}.%I FOR VALUES IN (%L)',
|
|
316
|
+
EXECUTE format('CREATE TABLE ${schema}.%I (LIKE ${schema}.job INCLUDING DEFAULTS)', tablename);
|
|
317
|
+
|
|
318
|
+
EXECUTE format('${formatPartitionCommand(createPrimaryKeyJob(schema))}', tablename);
|
|
319
|
+
EXECUTE format('${formatPartitionCommand(createQueueForeignKeyJob(schema))}', tablename);
|
|
320
|
+
EXECUTE format('${formatPartitionCommand(createQueueForeignKeyJobDeadLetter(schema))}', tablename);
|
|
321
|
+
EXECUTE format('${formatPartitionCommand(createIndexJobPolicyShort(schema))}', tablename);
|
|
322
|
+
EXECUTE format('${formatPartitionCommand(createIndexJobPolicySingleton(schema))}', tablename);
|
|
323
|
+
EXECUTE format('${formatPartitionCommand(createIndexJobPolicyStately(schema))}', tablename);
|
|
324
|
+
EXECUTE format('${formatPartitionCommand(createIndexJobThrottle(schema))}', tablename);
|
|
325
|
+
EXECUTE format('${formatPartitionCommand(createIndexJobFetch(schema))}', tablename);
|
|
326
|
+
|
|
327
|
+
EXECUTE format('ALTER TABLE ${schema}.%I ADD CONSTRAINT cjc CHECK (name=%L)', tablename, queue_name);
|
|
328
|
+
EXECUTE format('ALTER TABLE ${schema}.job ATTACH PARTITION ${schema}.%I FOR VALUES IN (%L)', tablename, queue_name);
|
|
279
329
|
END;
|
|
280
330
|
$$
|
|
281
331
|
LANGUAGE plpgsql;
|
|
@@ -283,7 +333,10 @@ function createQueueFunction (schema) {
|
|
|
283
333
|
}
|
|
284
334
|
|
|
285
335
|
function formatPartitionCommand (command) {
|
|
286
|
-
return command
|
|
336
|
+
return command
|
|
337
|
+
.replace('.job', '.%1$I')
|
|
338
|
+
.replace('job_i', '%1$s_i')
|
|
339
|
+
.replaceAll("'", "''")
|
|
287
340
|
}
|
|
288
341
|
|
|
289
342
|
function deleteQueueFunction (schema) {
|
|
@@ -292,28 +345,35 @@ function deleteQueueFunction (schema) {
|
|
|
292
345
|
RETURNS VOID AS
|
|
293
346
|
$$
|
|
294
347
|
DECLARE
|
|
295
|
-
|
|
348
|
+
v_table varchar;
|
|
349
|
+
v_partition bool;
|
|
296
350
|
BEGIN
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
351
|
+
SELECT table_name, partition
|
|
352
|
+
FROM ${schema}.queue
|
|
353
|
+
WHERE name = queue_name
|
|
354
|
+
INTO v_table, v_partition;
|
|
355
|
+
|
|
356
|
+
IF v_partition THEN
|
|
357
|
+
EXECUTE format('DROP TABLE IF EXISTS ${schema}.%I', v_table);
|
|
358
|
+
ELSE
|
|
359
|
+
EXECUTE format('DELETE FROM ${schema}.%I WHERE name = %L', v_table, queue_name);
|
|
360
|
+
END IF;
|
|
303
361
|
|
|
304
|
-
|
|
362
|
+
DELETE FROM ${schema}.queue WHERE name = queue_name;
|
|
305
363
|
END;
|
|
306
364
|
$$
|
|
307
365
|
LANGUAGE plpgsql;
|
|
308
366
|
`
|
|
309
367
|
}
|
|
310
368
|
|
|
311
|
-
function createQueue (schema) {
|
|
312
|
-
|
|
369
|
+
function createQueue (schema, name, options) {
|
|
370
|
+
const sql = `SELECT ${schema}.create_queue('${name}', '${JSON.stringify(options)}'::jsonb)`
|
|
371
|
+
return locked(schema, sql, 'create-queue')
|
|
313
372
|
}
|
|
314
373
|
|
|
315
|
-
function deleteQueue (schema) {
|
|
316
|
-
|
|
374
|
+
function deleteQueue (schema, name) {
|
|
375
|
+
const sql = `SELECT ${schema}.delete_queue('${name}')`
|
|
376
|
+
return locked(schema, sql, 'delete-queue')
|
|
317
377
|
}
|
|
318
378
|
|
|
319
379
|
function createPrimaryKeyJob (schema) {
|
|
@@ -328,10 +388,6 @@ function createQueueForeignKeyJobDeadLetter (schema) {
|
|
|
328
388
|
return `ALTER TABLE ${schema}.job ADD CONSTRAINT dlq_fkey FOREIGN KEY (dead_letter) REFERENCES ${schema}.queue (name) ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED`
|
|
329
389
|
}
|
|
330
390
|
|
|
331
|
-
function createPrimaryKeyArchive (schema) {
|
|
332
|
-
return `ALTER TABLE ${schema}.archive ADD PRIMARY KEY (name, id)`
|
|
333
|
-
}
|
|
334
|
-
|
|
335
391
|
function createIndexJobPolicyShort (schema) {
|
|
336
392
|
return `CREATE UNIQUE INDEX job_i1 ON ${schema}.job (name, COALESCE(singleton_key, '')) WHERE state = '${JOB_STATES.created}' AND policy = '${QUEUE_POLICIES.short}'`
|
|
337
393
|
}
|
|
@@ -352,97 +408,132 @@ function createIndexJobFetch (schema) {
|
|
|
352
408
|
return `CREATE INDEX job_i5 ON ${schema}.job (name, start_after) INCLUDE (priority, created_on, id) WHERE state < '${JOB_STATES.active}'`
|
|
353
409
|
}
|
|
354
410
|
|
|
355
|
-
function
|
|
356
|
-
return
|
|
411
|
+
function trySetQueueMonitorTime (schema, queues, seconds) {
|
|
412
|
+
return trySetQueueTimestamp(schema, queues, 'monitor_on', seconds)
|
|
357
413
|
}
|
|
358
414
|
|
|
359
|
-
function
|
|
360
|
-
return
|
|
415
|
+
function trySetQueueDeletionTime (schema, queues, seconds) {
|
|
416
|
+
return trySetQueueTimestamp(schema, queues, 'maintain_on', seconds)
|
|
361
417
|
}
|
|
362
418
|
|
|
363
|
-
function
|
|
364
|
-
return
|
|
419
|
+
function trySetCronTime (schema, seconds) {
|
|
420
|
+
return trySetTimestamp(schema, 'cron_on', seconds)
|
|
365
421
|
}
|
|
366
422
|
|
|
367
|
-
function
|
|
368
|
-
return
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
function trySetCronTime (schema) {
|
|
376
|
-
return trySetTimestamp(schema, 'cron_on')
|
|
423
|
+
function trySetTimestamp (schema, column, seconds) {
|
|
424
|
+
return `
|
|
425
|
+
UPDATE ${schema}.version
|
|
426
|
+
SET ${column} = now()
|
|
427
|
+
WHERE EXTRACT( EPOCH FROM (now() - COALESCE(${column}, now() - interval '1 week') ) ) > ${seconds}
|
|
428
|
+
RETURNING true
|
|
429
|
+
`
|
|
377
430
|
}
|
|
378
431
|
|
|
379
|
-
function
|
|
432
|
+
function trySetQueueTimestamp (schema, queues, column, seconds) {
|
|
380
433
|
return `
|
|
381
|
-
UPDATE ${schema}.
|
|
382
|
-
|
|
383
|
-
|
|
434
|
+
UPDATE ${schema}.queue
|
|
435
|
+
SET ${column} = now()
|
|
436
|
+
WHERE name IN(${getQueueInClause(queues)})
|
|
437
|
+
AND EXTRACT( EPOCH FROM (now() - COALESCE(${column}, now() - interval '1 week') ) ) > ${seconds}
|
|
438
|
+
RETURNING name
|
|
384
439
|
`
|
|
385
440
|
}
|
|
386
441
|
|
|
387
|
-
function updateQueue (schema) {
|
|
442
|
+
function updateQueue (schema, { deadLetter } = {}) {
|
|
388
443
|
return `
|
|
444
|
+
WITH options as (SELECT $2::jsonb as data)
|
|
389
445
|
UPDATE ${schema}.queue SET
|
|
390
|
-
policy = COALESCE(
|
|
391
|
-
retry_limit = COALESCE(
|
|
392
|
-
retry_delay = COALESCE(
|
|
393
|
-
retry_backoff = COALESCE(
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
446
|
+
policy = COALESCE(o.data->>'policy', policy),
|
|
447
|
+
retry_limit = COALESCE((o.data->>'retryLimit')::int, retry_limit),
|
|
448
|
+
retry_delay = COALESCE((o.data->>'retryDelay')::int, retry_delay),
|
|
449
|
+
retry_backoff = COALESCE((o.data->>'retryBackoff')::bool, retry_backoff),
|
|
450
|
+
retry_delay_max = CASE WHEN o.data ? 'retryDelayMax'
|
|
451
|
+
THEN (o.data->>'retryDelayMax')::int
|
|
452
|
+
ELSE retry_delay_max END,
|
|
453
|
+
expire_seconds = COALESCE((o.data->>'expireInSeconds')::int, expire_seconds),
|
|
454
|
+
retention_seconds = COALESCE((o.data->>'retentionSeconds')::int, retention_seconds),
|
|
455
|
+
deletion_seconds = COALESCE((o.data->>'deleteAfterSeconds')::int, deletion_seconds),
|
|
456
|
+
warning_queued = COALESCE((o.data->>'warningQueueSize')::int, warning_queued),
|
|
457
|
+
${
|
|
458
|
+
deadLetter === undefined
|
|
459
|
+
? ''
|
|
460
|
+
: `dead_letter = CASE WHEN '${deadLetter}' IS DISTINCT FROM dead_letter THEN '${deadLetter}' ELSE dead_letter END,`
|
|
461
|
+
}
|
|
397
462
|
updated_on = now()
|
|
463
|
+
FROM options o
|
|
398
464
|
WHERE name = $1
|
|
399
465
|
`
|
|
400
466
|
}
|
|
401
467
|
|
|
402
|
-
function getQueues (schema) {
|
|
468
|
+
function getQueues (schema, names) {
|
|
403
469
|
return `
|
|
404
|
-
SELECT
|
|
405
|
-
name,
|
|
406
|
-
policy,
|
|
407
|
-
retry_limit as "retryLimit",
|
|
408
|
-
retry_delay as "retryDelay",
|
|
409
|
-
retry_backoff as "retryBackoff",
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
470
|
+
SELECT
|
|
471
|
+
q.name,
|
|
472
|
+
q.policy,
|
|
473
|
+
q.retry_limit as "retryLimit",
|
|
474
|
+
q.retry_delay as "retryDelay",
|
|
475
|
+
q.retry_backoff as "retryBackoff",
|
|
476
|
+
q.retry_delay_max as "retryDelayMax",
|
|
477
|
+
q.expire_seconds as "expireInSeconds",
|
|
478
|
+
q.retention_seconds as "retentionSeconds",
|
|
479
|
+
q.deletion_seconds as "deleteAfterSeconds",
|
|
480
|
+
q.partition,
|
|
481
|
+
q.dead_letter as "deadLetter",
|
|
482
|
+
q.deferred_count as "deferredCount",
|
|
483
|
+
q.warning_queued as "warningQueueSize",
|
|
484
|
+
q.queued_count as "queuedCount",
|
|
485
|
+
q.active_count as "activeCount",
|
|
486
|
+
q.total_count as "totalCount",
|
|
487
|
+
q.singletons_active as "singletonsActive",
|
|
488
|
+
q.table_name as "table",
|
|
489
|
+
q.created_on as "createdOn",
|
|
490
|
+
q.updated_on as "updatedOn"
|
|
491
|
+
FROM ${schema}.queue q
|
|
492
|
+
${names ? `WHERE q.name IN (${names.map(i => `'${i}'`)})` : ''}
|
|
416
493
|
`
|
|
417
494
|
}
|
|
418
495
|
|
|
419
|
-
function
|
|
420
|
-
return
|
|
496
|
+
function deleteJobsById (schema, table) {
|
|
497
|
+
return `
|
|
498
|
+
WITH results as (
|
|
499
|
+
DELETE FROM ${schema}.${table}
|
|
500
|
+
WHERE name = $1
|
|
501
|
+
AND id IN (SELECT UNNEST($2::uuid[]))
|
|
502
|
+
RETURNING 1
|
|
503
|
+
)
|
|
504
|
+
SELECT COUNT(*) from results
|
|
505
|
+
`
|
|
421
506
|
}
|
|
422
507
|
|
|
423
|
-
function
|
|
424
|
-
return `DELETE from ${schema}
|
|
508
|
+
function deleteQueuedJobs (schema, table) {
|
|
509
|
+
return `DELETE from ${schema}.${table} WHERE name = $1 and state < '${JOB_STATES.active}'`
|
|
425
510
|
}
|
|
426
511
|
|
|
427
|
-
function
|
|
428
|
-
return `
|
|
512
|
+
function deleteStoredJobs (schema, table) {
|
|
513
|
+
return `DELETE from ${schema}.${table} WHERE name = $1 and state > '${JOB_STATES.active}'`
|
|
429
514
|
}
|
|
430
515
|
|
|
431
|
-
function
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
516
|
+
function truncateTable (schema, table) {
|
|
517
|
+
return `TRUNCATE ${schema}.${table}`
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function deleteAllJobs (schema, table) {
|
|
521
|
+
return `DELETE from ${schema}.${table} WHERE name = $1`
|
|
435
522
|
}
|
|
436
523
|
|
|
437
524
|
function getSchedules (schema) {
|
|
438
525
|
return `SELECT * FROM ${schema}.schedule`
|
|
439
526
|
}
|
|
440
527
|
|
|
528
|
+
function getSchedulesByQueue (schema) {
|
|
529
|
+
return `SELECT * FROM ${schema}.schedule WHERE name = $1 AND COALESCE(key, '') = $2`
|
|
530
|
+
}
|
|
531
|
+
|
|
441
532
|
function schedule (schema) {
|
|
442
533
|
return `
|
|
443
|
-
INSERT INTO ${schema}.schedule (name, cron, timezone, data, options)
|
|
444
|
-
VALUES ($1, $2, $3, $4, $5)
|
|
445
|
-
ON CONFLICT (name) DO UPDATE SET
|
|
534
|
+
INSERT INTO ${schema}.schedule (name, key, cron, timezone, data, options)
|
|
535
|
+
VALUES ($1, $2, $3, $4, $5, $6)
|
|
536
|
+
ON CONFLICT (name, key) DO UPDATE SET
|
|
446
537
|
cron = EXCLUDED.cron,
|
|
447
538
|
timezone = EXCLUDED.timezone,
|
|
448
539
|
data = EXCLUDED.data,
|
|
@@ -455,6 +546,7 @@ function unschedule (schema) {
|
|
|
455
546
|
return `
|
|
456
547
|
DELETE FROM ${schema}.schedule
|
|
457
548
|
WHERE name = $1
|
|
549
|
+
AND COALESCE(key, '') = $2
|
|
458
550
|
`
|
|
459
551
|
}
|
|
460
552
|
|
|
@@ -503,32 +595,38 @@ function insertVersion (schema, version) {
|
|
|
503
595
|
return `INSERT INTO ${schema}.version(version) VALUES ('${version}')`
|
|
504
596
|
}
|
|
505
597
|
|
|
506
|
-
function fetchNextJob (schema) {
|
|
507
|
-
|
|
598
|
+
function fetchNextJob ({ schema, table, name, policy, limit, includeMetadata, priority = true, ignoreStartAfter = false, ignoreSingletons = null }) {
|
|
599
|
+
const singletonFetch = limit > 1 && (policy === QUEUE_POLICIES.singleton || policy === QUEUE_POLICIES.stately)
|
|
600
|
+
const cte = singletonFetch ? 'grouped' : 'next'
|
|
601
|
+
|
|
602
|
+
return `
|
|
508
603
|
WITH next as (
|
|
509
|
-
SELECT id
|
|
510
|
-
FROM ${schema}
|
|
511
|
-
WHERE name = $
|
|
604
|
+
SELECT id ${singletonFetch ? ', singleton_key' : ''}
|
|
605
|
+
FROM ${schema}.${table}
|
|
606
|
+
WHERE name = '${name}'
|
|
512
607
|
AND state < '${JOB_STATES.active}'
|
|
513
608
|
${ignoreStartAfter ? '' : 'AND start_after < now()'}
|
|
609
|
+
${ignoreSingletons?.length > 0 ? `AND singleton_key NOT IN (${ignoreSingletons.map(i => `'${i}'`).join()})` : ''}
|
|
514
610
|
ORDER BY ${priority ? 'priority desc, ' : ''}created_on, id
|
|
515
|
-
LIMIT $
|
|
611
|
+
LIMIT ${limit}
|
|
516
612
|
FOR UPDATE SKIP LOCKED
|
|
517
613
|
)
|
|
518
|
-
|
|
614
|
+
${singletonFetch ? ', grouped as ( SELECT id, row_number() OVER (PARTITION BY singleton_key) FROM next)' : ''}
|
|
615
|
+
UPDATE ${schema}.${table} j SET
|
|
519
616
|
state = '${JOB_STATES.active}',
|
|
520
617
|
started_on = now(),
|
|
521
618
|
retry_count = CASE WHEN started_on IS NOT NULL THEN retry_count + 1 ELSE retry_count END
|
|
522
|
-
FROM
|
|
523
|
-
WHERE name = $
|
|
524
|
-
|
|
619
|
+
FROM ${cte}
|
|
620
|
+
WHERE name = '${name}' AND j.id = ${cte}.id
|
|
621
|
+
${singletonFetch ? ` AND ${cte}.row_number = 1` : ''}
|
|
622
|
+
RETURNING j.${includeMetadata ? JOB_COLUMNS_ALL : JOB_COLUMNS_MIN}
|
|
525
623
|
`
|
|
526
624
|
}
|
|
527
625
|
|
|
528
|
-
function completeJobs (schema) {
|
|
626
|
+
function completeJobs (schema, table) {
|
|
529
627
|
return `
|
|
530
628
|
WITH results AS (
|
|
531
|
-
UPDATE ${schema}
|
|
629
|
+
UPDATE ${schema}.${table}
|
|
532
630
|
SET completed_on = now(),
|
|
533
631
|
state = '${JOB_STATES.completed}',
|
|
534
632
|
output = $3::jsonb
|
|
@@ -541,28 +639,134 @@ function completeJobs (schema) {
|
|
|
541
639
|
`
|
|
542
640
|
}
|
|
543
641
|
|
|
544
|
-
function
|
|
642
|
+
function cancelJobs (schema, table) {
|
|
643
|
+
return `
|
|
644
|
+
WITH results as (
|
|
645
|
+
UPDATE ${schema}.${table}
|
|
646
|
+
SET completed_on = now(),
|
|
647
|
+
state = '${JOB_STATES.cancelled}'
|
|
648
|
+
WHERE name = $1
|
|
649
|
+
AND id IN (SELECT UNNEST($2::uuid[]))
|
|
650
|
+
AND state < '${JOB_STATES.completed}'
|
|
651
|
+
RETURNING 1
|
|
652
|
+
)
|
|
653
|
+
SELECT COUNT(*) from results
|
|
654
|
+
`
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
function resumeJobs (schema, table) {
|
|
658
|
+
return `
|
|
659
|
+
WITH results as (
|
|
660
|
+
UPDATE ${schema}.${table}
|
|
661
|
+
SET completed_on = NULL,
|
|
662
|
+
state = '${JOB_STATES.created}'
|
|
663
|
+
WHERE name = $1
|
|
664
|
+
AND id IN (SELECT UNNEST($2::uuid[]))
|
|
665
|
+
AND state = '${JOB_STATES.cancelled}'
|
|
666
|
+
RETURNING 1
|
|
667
|
+
)
|
|
668
|
+
SELECT COUNT(*) from results
|
|
669
|
+
`
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
function insertJobs (schema, { table, name, returnId = true }) {
|
|
673
|
+
const sql = `
|
|
674
|
+
INSERT INTO ${schema}.${table} (
|
|
675
|
+
id,
|
|
676
|
+
name,
|
|
677
|
+
data,
|
|
678
|
+
priority,
|
|
679
|
+
start_after,
|
|
680
|
+
singleton_key,
|
|
681
|
+
singleton_on,
|
|
682
|
+
expire_seconds,
|
|
683
|
+
deletion_seconds,
|
|
684
|
+
keep_until,
|
|
685
|
+
retry_limit,
|
|
686
|
+
retry_delay,
|
|
687
|
+
retry_backoff,
|
|
688
|
+
retry_delay_max,
|
|
689
|
+
policy,
|
|
690
|
+
dead_letter
|
|
691
|
+
)
|
|
692
|
+
SELECT
|
|
693
|
+
COALESCE(id, gen_random_uuid()) as id,
|
|
694
|
+
'${name}' as name,
|
|
695
|
+
data,
|
|
696
|
+
COALESCE(priority, 0) as priority,
|
|
697
|
+
j.start_after,
|
|
698
|
+
"singletonKey",
|
|
699
|
+
CASE
|
|
700
|
+
WHEN "singletonSeconds" IS NOT NULL THEN 'epoch'::timestamp + '1s'::interval * ("singletonSeconds" * floor(( date_part('epoch', now()) + "singletonOffset") / "singletonSeconds" ))
|
|
701
|
+
ELSE NULL
|
|
702
|
+
END as singleton_on,
|
|
703
|
+
COALESCE("expireInSeconds", q.expire_seconds) as expire_seconds,
|
|
704
|
+
COALESCE("deleteAfterSeconds", q.deletion_seconds) as deletion_seconds,
|
|
705
|
+
COALESCE("keepUntil", COALESCE(j.start_after, now()) + q.retention_seconds * interval '1s') as keep_until,
|
|
706
|
+
COALESCE("retryLimit", q.retry_limit) as retry_limit,
|
|
707
|
+
COALESCE("retryDelay", q.retry_delay) as retry_delay,
|
|
708
|
+
COALESCE("retryBackoff", q.retry_backoff, false) as retry_backoff,
|
|
709
|
+
COALESCE("retryDelayMax", q.retry_delay_max) as retry_delay_max,
|
|
710
|
+
q.policy,
|
|
711
|
+
q.dead_letter
|
|
712
|
+
FROM (
|
|
713
|
+
SELECT *,
|
|
714
|
+
CASE
|
|
715
|
+
WHEN right("startAfter", 1) = 'Z' THEN CAST("startAfter" as timestamp with time zone)
|
|
716
|
+
ELSE now() + CAST(COALESCE("startAfter",'0') as interval)
|
|
717
|
+
END as start_after
|
|
718
|
+
FROM json_to_recordset($1::json) as x (
|
|
719
|
+
id uuid,
|
|
720
|
+
name text,
|
|
721
|
+
priority integer,
|
|
722
|
+
data jsonb,
|
|
723
|
+
"startAfter" text,
|
|
724
|
+
"retryLimit" integer,
|
|
725
|
+
"retryDelay" integer,
|
|
726
|
+
"retryDelayMax" integer,
|
|
727
|
+
"retryBackoff" boolean,
|
|
728
|
+
"singletonKey" text,
|
|
729
|
+
"singletonSeconds" integer,
|
|
730
|
+
"singletonOffset" integer,
|
|
731
|
+
"expireInSeconds" integer,
|
|
732
|
+
"deleteAfterSeconds" integer,
|
|
733
|
+
"keepUntil" timestamp with time zone
|
|
734
|
+
)
|
|
735
|
+
) j
|
|
736
|
+
JOIN ${schema}.queue q ON q.name = '${name}'
|
|
737
|
+
ON CONFLICT DO NOTHING
|
|
738
|
+
${returnId ? 'RETURNING id' : ''}
|
|
739
|
+
`
|
|
740
|
+
|
|
741
|
+
return sql
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
function failJobsById (schema, table) {
|
|
545
745
|
const where = `name = $1 AND id IN (SELECT UNNEST($2::uuid[])) AND state < '${JOB_STATES.completed}'`
|
|
546
746
|
const output = '$3::jsonb'
|
|
547
747
|
|
|
548
|
-
return failJobs(schema, where, output)
|
|
748
|
+
return failJobs(schema, table, where, output)
|
|
549
749
|
}
|
|
550
750
|
|
|
551
|
-
function failJobsByTimeout (schema) {
|
|
552
|
-
const where = `state = '${JOB_STATES.active}'
|
|
553
|
-
|
|
554
|
-
|
|
751
|
+
function failJobsByTimeout (schema, table, queues) {
|
|
752
|
+
const where = `state = '${JOB_STATES.active}'
|
|
753
|
+
AND (started_on + expire_seconds * interval '1s') < now()
|
|
754
|
+
AND name IN (${getQueueInClause(queues)})`
|
|
755
|
+
|
|
756
|
+
const output = '\'{ "value": { "message": "job timed out" } }\'::jsonb'
|
|
757
|
+
|
|
758
|
+
return locked(schema, failJobs(schema, table, where, output), table + 'failJobsByTimeout')
|
|
555
759
|
}
|
|
556
760
|
|
|
557
|
-
function failJobs (schema, where, output) {
|
|
761
|
+
function failJobs (schema, table, where, output) {
|
|
558
762
|
return `
|
|
559
763
|
WITH deleted_jobs AS (
|
|
560
|
-
DELETE FROM ${schema}
|
|
764
|
+
DELETE FROM ${schema}.${table}
|
|
561
765
|
WHERE ${where}
|
|
562
766
|
RETURNING *
|
|
563
767
|
),
|
|
564
768
|
retried_jobs AS (
|
|
565
|
-
INSERT INTO ${schema}
|
|
769
|
+
INSERT INTO ${schema}.${table} (
|
|
566
770
|
id,
|
|
567
771
|
name,
|
|
568
772
|
priority,
|
|
@@ -572,17 +776,19 @@ function failJobs (schema, where, output) {
|
|
|
572
776
|
retry_count,
|
|
573
777
|
retry_delay,
|
|
574
778
|
retry_backoff,
|
|
779
|
+
retry_delay_max,
|
|
575
780
|
start_after,
|
|
576
781
|
started_on,
|
|
577
782
|
singleton_key,
|
|
578
783
|
singleton_on,
|
|
579
|
-
|
|
784
|
+
expire_seconds,
|
|
785
|
+
deletion_seconds,
|
|
580
786
|
created_on,
|
|
581
787
|
completed_on,
|
|
582
788
|
keep_until,
|
|
583
|
-
dead_letter,
|
|
584
789
|
policy,
|
|
585
|
-
output
|
|
790
|
+
output,
|
|
791
|
+
dead_letter
|
|
586
792
|
)
|
|
587
793
|
SELECT
|
|
588
794
|
id,
|
|
@@ -597,33 +803,34 @@ function failJobs (schema, where, output) {
|
|
|
597
803
|
retry_count,
|
|
598
804
|
retry_delay,
|
|
599
805
|
retry_backoff,
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
806
|
+
retry_delay_max,
|
|
807
|
+
CASE WHEN retry_count = retry_limit THEN start_after
|
|
808
|
+
WHEN NOT retry_backoff THEN now() + retry_delay * interval '1'
|
|
809
|
+
ELSE now() + LEAST(
|
|
810
|
+
retry_delay_max,
|
|
811
|
+
retry_delay + (
|
|
812
|
+
2 ^ LEAST(16, retry_count + 1) / 2 +
|
|
813
|
+
2 ^ LEAST(16, retry_count + 1) / 2 * random()
|
|
814
|
+
)
|
|
815
|
+
) * interval '1s'
|
|
816
|
+
END as start_after,
|
|
608
817
|
started_on,
|
|
609
818
|
singleton_key,
|
|
610
819
|
singleton_on,
|
|
611
|
-
|
|
820
|
+
expire_seconds,
|
|
821
|
+
deletion_seconds,
|
|
612
822
|
created_on,
|
|
613
|
-
CASE
|
|
614
|
-
WHEN retry_count < retry_limit THEN NULL
|
|
615
|
-
ELSE now()
|
|
616
|
-
END as completed_on,
|
|
823
|
+
CASE WHEN retry_count < retry_limit THEN NULL ELSE now() END as completed_on,
|
|
617
824
|
keep_until,
|
|
618
|
-
dead_letter,
|
|
619
825
|
policy,
|
|
620
|
-
${output}
|
|
826
|
+
${output},
|
|
827
|
+
dead_letter
|
|
621
828
|
FROM deleted_jobs
|
|
622
829
|
ON CONFLICT DO NOTHING
|
|
623
830
|
RETURNING *
|
|
624
831
|
),
|
|
625
832
|
failed_jobs as (
|
|
626
|
-
INSERT INTO ${schema}
|
|
833
|
+
INSERT INTO ${schema}.${table} (
|
|
627
834
|
id,
|
|
628
835
|
name,
|
|
629
836
|
priority,
|
|
@@ -633,17 +840,19 @@ function failJobs (schema, where, output) {
|
|
|
633
840
|
retry_count,
|
|
634
841
|
retry_delay,
|
|
635
842
|
retry_backoff,
|
|
843
|
+
retry_delay_max,
|
|
636
844
|
start_after,
|
|
637
845
|
started_on,
|
|
638
846
|
singleton_key,
|
|
639
847
|
singleton_on,
|
|
640
|
-
|
|
848
|
+
expire_seconds,
|
|
849
|
+
deletion_seconds,
|
|
641
850
|
created_on,
|
|
642
851
|
completed_on,
|
|
643
852
|
keep_until,
|
|
644
|
-
dead_letter,
|
|
645
853
|
policy,
|
|
646
|
-
output
|
|
854
|
+
output,
|
|
855
|
+
dead_letter
|
|
647
856
|
)
|
|
648
857
|
SELECT
|
|
649
858
|
id,
|
|
@@ -655,17 +864,19 @@ function failJobs (schema, where, output) {
|
|
|
655
864
|
retry_count,
|
|
656
865
|
retry_delay,
|
|
657
866
|
retry_backoff,
|
|
867
|
+
retry_delay_max,
|
|
658
868
|
start_after,
|
|
659
869
|
started_on,
|
|
660
870
|
singleton_key,
|
|
661
871
|
singleton_on,
|
|
662
|
-
|
|
872
|
+
expire_seconds,
|
|
873
|
+
deletion_seconds,
|
|
663
874
|
created_on,
|
|
664
875
|
now() as completed_on,
|
|
665
876
|
keep_until,
|
|
666
|
-
dead_letter,
|
|
667
877
|
policy,
|
|
668
|
-
${output}
|
|
878
|
+
${output},
|
|
879
|
+
dead_letter
|
|
669
880
|
FROM deleted_jobs
|
|
670
881
|
WHERE id NOT IN (SELECT id from retried_jobs)
|
|
671
882
|
RETURNING *
|
|
@@ -676,67 +887,42 @@ function failJobs (schema, where, output) {
|
|
|
676
887
|
SELECT * FROM failed_jobs
|
|
677
888
|
),
|
|
678
889
|
dlq_jobs as (
|
|
679
|
-
INSERT INTO ${schema}.job (name, data, output, retry_limit, keep_until)
|
|
890
|
+
INSERT INTO ${schema}.job (name, data, output, retry_limit, retry_backoff, retry_delay, keep_until, deletion_seconds)
|
|
680
891
|
SELECT
|
|
681
|
-
dead_letter,
|
|
892
|
+
r.dead_letter,
|
|
682
893
|
data,
|
|
683
894
|
output,
|
|
684
|
-
retry_limit,
|
|
685
|
-
|
|
686
|
-
|
|
895
|
+
q.retry_limit,
|
|
896
|
+
q.retry_backoff,
|
|
897
|
+
q.retry_delay,
|
|
898
|
+
now() + q.retention_seconds * interval '1s',
|
|
899
|
+
q.deletion_seconds
|
|
900
|
+
FROM results r
|
|
901
|
+
JOIN ${schema}.queue q ON q.name = r.dead_letter
|
|
687
902
|
WHERE state = '${JOB_STATES.failed}'
|
|
688
|
-
AND dead_letter IS NOT NULL
|
|
689
|
-
AND NOT name = dead_letter
|
|
690
903
|
)
|
|
691
904
|
SELECT COUNT(*) FROM results
|
|
692
905
|
`
|
|
693
906
|
}
|
|
694
907
|
|
|
695
|
-
function
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
)
|
|
706
|
-
SELECT COUNT(*) from results
|
|
908
|
+
function deletion (schema, table, queues) {
|
|
909
|
+
const sql = `
|
|
910
|
+
DELETE FROM ${schema}.${table}
|
|
911
|
+
WHERE name IN (${getQueueInClause(queues)})
|
|
912
|
+
AND
|
|
913
|
+
(
|
|
914
|
+
completed_on + deletion_seconds * interval '1s' < now()
|
|
915
|
+
OR
|
|
916
|
+
(state < '${JOB_STATES.active}' AND keep_until < now())
|
|
917
|
+
)
|
|
707
918
|
`
|
|
708
|
-
}
|
|
709
919
|
|
|
710
|
-
|
|
711
|
-
return `
|
|
712
|
-
with results as (
|
|
713
|
-
UPDATE ${schema}.job
|
|
714
|
-
SET completed_on = NULL,
|
|
715
|
-
state = '${JOB_STATES.created}'
|
|
716
|
-
WHERE name = $1
|
|
717
|
-
AND id IN (SELECT UNNEST($2::uuid[]))
|
|
718
|
-
AND state = '${JOB_STATES.cancelled}'
|
|
719
|
-
RETURNING 1
|
|
720
|
-
)
|
|
721
|
-
SELECT COUNT(*) from results
|
|
722
|
-
`
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
function deleteJobs (schema) {
|
|
726
|
-
return `
|
|
727
|
-
with results as (
|
|
728
|
-
DELETE FROM ${schema}.job
|
|
729
|
-
WHERE name = $1
|
|
730
|
-
AND id IN (SELECT UNNEST($2::uuid[]))
|
|
731
|
-
RETURNING 1
|
|
732
|
-
)
|
|
733
|
-
SELECT COUNT(*) from results
|
|
734
|
-
`
|
|
920
|
+
return locked(schema, sql, table + 'deletion')
|
|
735
921
|
}
|
|
736
922
|
|
|
737
923
|
function retryJobs (schema) {
|
|
738
924
|
return `
|
|
739
|
-
|
|
925
|
+
WITH results as (
|
|
740
926
|
UPDATE ${schema}.job
|
|
741
927
|
SET state = '${JOB_STATES.retry}',
|
|
742
928
|
retry_limit = retry_limit + 1
|
|
@@ -749,210 +935,51 @@ function retryJobs (schema) {
|
|
|
749
935
|
`
|
|
750
936
|
}
|
|
751
937
|
|
|
752
|
-
function
|
|
753
|
-
return `
|
|
754
|
-
INSERT INTO ${schema}.job (
|
|
755
|
-
id,
|
|
756
|
-
name,
|
|
757
|
-
data,
|
|
758
|
-
priority,
|
|
759
|
-
start_after,
|
|
760
|
-
singleton_key,
|
|
761
|
-
singleton_on,
|
|
762
|
-
dead_letter,
|
|
763
|
-
expire_in,
|
|
764
|
-
keep_until,
|
|
765
|
-
retry_limit,
|
|
766
|
-
retry_delay,
|
|
767
|
-
retry_backoff,
|
|
768
|
-
policy
|
|
769
|
-
)
|
|
770
|
-
SELECT
|
|
771
|
-
id,
|
|
772
|
-
j.name,
|
|
773
|
-
data,
|
|
774
|
-
priority,
|
|
775
|
-
start_after,
|
|
776
|
-
singleton_key,
|
|
777
|
-
singleton_on,
|
|
778
|
-
COALESCE(j.dead_letter, q.dead_letter) as dead_letter,
|
|
779
|
-
CASE
|
|
780
|
-
WHEN expire_in IS NOT NULL THEN CAST(expire_in as interval)
|
|
781
|
-
WHEN q.expire_seconds IS NOT NULL THEN q.expire_seconds * interval '1s'
|
|
782
|
-
WHEN expire_in_default IS NOT NULL THEN CAST(expire_in_default as interval)
|
|
783
|
-
ELSE interval '15 minutes'
|
|
784
|
-
END as expire_in,
|
|
785
|
-
CASE
|
|
786
|
-
WHEN right(keep_until, 1) = 'Z' THEN CAST(keep_until as timestamp with time zone)
|
|
787
|
-
ELSE start_after + CAST(COALESCE(keep_until, (q.retention_minutes * 60)::text, keep_until_default, '14 days') as interval)
|
|
788
|
-
END as keep_until,
|
|
789
|
-
COALESCE(j.retry_limit, q.retry_limit, retry_limit_default, 2) as retry_limit,
|
|
790
|
-
CASE
|
|
791
|
-
WHEN COALESCE(j.retry_backoff, q.retry_backoff, retry_backoff_default, false)
|
|
792
|
-
THEN GREATEST(COALESCE(j.retry_delay, q.retry_delay, retry_delay_default), 1)
|
|
793
|
-
ELSE COALESCE(j.retry_delay, q.retry_delay, retry_delay_default, 0)
|
|
794
|
-
END as retry_delay,
|
|
795
|
-
COALESCE(j.retry_backoff, q.retry_backoff, retry_backoff_default, false) as retry_backoff,
|
|
796
|
-
q.policy
|
|
797
|
-
FROM
|
|
798
|
-
( SELECT
|
|
799
|
-
COALESCE($1::uuid, gen_random_uuid()) as id,
|
|
800
|
-
$2 as name,
|
|
801
|
-
$3::jsonb as data,
|
|
802
|
-
COALESCE($4::int, 0) as priority,
|
|
803
|
-
CASE
|
|
804
|
-
WHEN right($5, 1) = 'Z' THEN CAST($5 as timestamp with time zone)
|
|
805
|
-
ELSE now() + CAST(COALESCE($5,'0') as interval)
|
|
806
|
-
END as start_after,
|
|
807
|
-
$6 as singleton_key,
|
|
808
|
-
CASE
|
|
809
|
-
WHEN $7::integer IS NOT NULL THEN 'epoch'::timestamp + '1 second'::interval * ($7 * floor((date_part('epoch', now()) + $8) / $7))
|
|
810
|
-
ELSE NULL
|
|
811
|
-
END as singleton_on,
|
|
812
|
-
$9 as dead_letter,
|
|
813
|
-
$10 as expire_in,
|
|
814
|
-
$11 as expire_in_default,
|
|
815
|
-
$12 as keep_until,
|
|
816
|
-
$13 as keep_until_default,
|
|
817
|
-
$14::int as retry_limit,
|
|
818
|
-
$15::int as retry_limit_default,
|
|
819
|
-
$16::int as retry_delay,
|
|
820
|
-
$17::int as retry_delay_default,
|
|
821
|
-
$18::bool as retry_backoff,
|
|
822
|
-
$19::bool as retry_backoff_default
|
|
823
|
-
) j JOIN ${schema}.queue q ON j.name = q.name
|
|
824
|
-
ON CONFLICT DO NOTHING
|
|
825
|
-
RETURNING id
|
|
826
|
-
`
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
function insertJobs (schema) {
|
|
938
|
+
function getQueueStats (schema, table, queues) {
|
|
830
939
|
return `
|
|
831
|
-
WITH defaults as (
|
|
832
|
-
SELECT
|
|
833
|
-
$2 as expire_in,
|
|
834
|
-
$3 as keep_until,
|
|
835
|
-
$4::int as retry_limit,
|
|
836
|
-
$5::int as retry_delay,
|
|
837
|
-
$6::bool as retry_backoff
|
|
838
|
-
)
|
|
839
|
-
INSERT INTO ${schema}.job (
|
|
840
|
-
id,
|
|
841
|
-
name,
|
|
842
|
-
data,
|
|
843
|
-
priority,
|
|
844
|
-
start_after,
|
|
845
|
-
singleton_key,
|
|
846
|
-
singleton_on,
|
|
847
|
-
dead_letter,
|
|
848
|
-
expire_in,
|
|
849
|
-
keep_until,
|
|
850
|
-
retry_limit,
|
|
851
|
-
retry_delay,
|
|
852
|
-
retry_backoff,
|
|
853
|
-
policy
|
|
854
|
-
)
|
|
855
940
|
SELECT
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
END as singleton_on,
|
|
866
|
-
COALESCE("deadLetter", q.dead_letter) as dead_letter,
|
|
867
|
-
CASE
|
|
868
|
-
WHEN "expireInSeconds" IS NOT NULL THEN "expireInSeconds" * interval '1s'
|
|
869
|
-
WHEN q.expire_seconds IS NOT NULL THEN q.expire_seconds * interval '1s'
|
|
870
|
-
WHEN defaults.expire_in IS NOT NULL THEN CAST(defaults.expire_in as interval)
|
|
871
|
-
ELSE interval '15 minutes'
|
|
872
|
-
END as expire_in,
|
|
873
|
-
CASE
|
|
874
|
-
WHEN "keepUntil" IS NOT NULL THEN "keepUntil"
|
|
875
|
-
ELSE COALESCE(j.start_after, now()) + CAST(COALESCE((q.retention_minutes * 60)::text, defaults.keep_until, '14 days') as interval)
|
|
876
|
-
END as keep_until,
|
|
877
|
-
COALESCE("retryLimit", q.retry_limit, defaults.retry_limit, 2),
|
|
878
|
-
CASE
|
|
879
|
-
WHEN COALESCE("retryBackoff", q.retry_backoff, defaults.retry_backoff, false)
|
|
880
|
-
THEN GREATEST(COALESCE("retryDelay", q.retry_delay, defaults.retry_delay), 1)
|
|
881
|
-
ELSE COALESCE("retryDelay", q.retry_delay, defaults.retry_delay, 0)
|
|
882
|
-
END as retry_delay,
|
|
883
|
-
COALESCE("retryBackoff", q.retry_backoff, defaults.retry_backoff, false) as retry_backoff,
|
|
884
|
-
q.policy
|
|
885
|
-
FROM (
|
|
886
|
-
SELECT *,
|
|
887
|
-
CASE
|
|
888
|
-
WHEN right("startAfter", 1) = 'Z' THEN CAST("startAfter" as timestamp with time zone)
|
|
889
|
-
ELSE now() + CAST(COALESCE("startAfter",'0') as interval)
|
|
890
|
-
END as start_after
|
|
891
|
-
FROM json_to_recordset($1) as x (
|
|
892
|
-
id uuid,
|
|
893
|
-
name text,
|
|
894
|
-
priority integer,
|
|
895
|
-
data jsonb,
|
|
896
|
-
"startAfter" text,
|
|
897
|
-
"retryLimit" integer,
|
|
898
|
-
"retryDelay" integer,
|
|
899
|
-
"retryBackoff" boolean,
|
|
900
|
-
"singletonKey" text,
|
|
901
|
-
"singletonSeconds" integer,
|
|
902
|
-
"expireInSeconds" integer,
|
|
903
|
-
"keepUntil" timestamp with time zone,
|
|
904
|
-
"deadLetter" text
|
|
905
|
-
)
|
|
906
|
-
) j
|
|
907
|
-
JOIN ${schema}.queue q ON j.name = q.name,
|
|
908
|
-
defaults
|
|
909
|
-
ON CONFLICT DO NOTHING
|
|
941
|
+
name,
|
|
942
|
+
(count(*) FILTER (WHERE start_after > now()))::int as "deferredCount",
|
|
943
|
+
(count(*) FILTER (WHERE state < '${JOB_STATES.active}'))::int as "queuedCount",
|
|
944
|
+
(count(*) FILTER (WHERE state = '${JOB_STATES.active}'))::int as "activeCount",
|
|
945
|
+
count(*)::int as "totalCount",
|
|
946
|
+
array_agg(singleton_key) FILTER (WHERE policy IN ('${QUEUE_POLICIES.singleton}','${QUEUE_POLICIES.stately}') AND state IN ('${JOB_STATES.retry}','${JOB_STATES.active}')) as "singletonsActive"
|
|
947
|
+
FROM ${schema}.${table}
|
|
948
|
+
WHERE name IN (${getQueueInClause(queues)})
|
|
949
|
+
GROUP BY 1
|
|
910
950
|
`
|
|
911
951
|
}
|
|
912
952
|
|
|
913
|
-
function
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
OR (state < '${JOB_STATES.active}' AND keep_until < now())
|
|
929
|
-
RETURNING *
|
|
930
|
-
)
|
|
931
|
-
INSERT INTO ${schema}.archive (${columns})
|
|
932
|
-
SELECT ${columns}
|
|
933
|
-
FROM archived_rows
|
|
934
|
-
ON CONFLICT DO NOTHING
|
|
953
|
+
function cacheQueueStats (schema, table, queues) {
|
|
954
|
+
const sql = `
|
|
955
|
+
WITH stats AS (${getQueueStats(schema, table, queues)})
|
|
956
|
+
UPDATE ${schema}.queue SET
|
|
957
|
+
deferred_count = "deferredCount",
|
|
958
|
+
queued_count = "queuedCount",
|
|
959
|
+
active_count = "activeCount",
|
|
960
|
+
total_count = "totalCount",
|
|
961
|
+
singletons_active = "singletonsActive"
|
|
962
|
+
FROM stats
|
|
963
|
+
WHERE queue.name = stats.name
|
|
964
|
+
RETURNING
|
|
965
|
+
queue.name,
|
|
966
|
+
"queuedCount",
|
|
967
|
+
warning_queued as "warningQueueSize"
|
|
935
968
|
`
|
|
936
|
-
}
|
|
937
969
|
|
|
938
|
-
|
|
939
|
-
return `
|
|
940
|
-
SELECT name, state, count(*) size
|
|
941
|
-
FROM ${schema}.job
|
|
942
|
-
GROUP BY rollup(name), rollup(state)
|
|
943
|
-
`
|
|
970
|
+
return locked(schema, sql, 'queue-stats')
|
|
944
971
|
}
|
|
945
972
|
|
|
946
|
-
function locked (schema, query) {
|
|
973
|
+
function locked (schema, query, key) {
|
|
947
974
|
if (Array.isArray(query)) {
|
|
948
975
|
query = query.join(';\n')
|
|
949
976
|
}
|
|
950
977
|
|
|
951
978
|
return `
|
|
952
979
|
BEGIN;
|
|
953
|
-
SET LOCAL lock_timeout =
|
|
954
|
-
SET LOCAL idle_in_transaction_session_timeout =
|
|
955
|
-
${advisoryLock(schema)};
|
|
980
|
+
SET LOCAL lock_timeout = 30000;
|
|
981
|
+
SET LOCAL idle_in_transaction_session_timeout = 30000;
|
|
982
|
+
${advisoryLock(schema, key)};
|
|
956
983
|
${query};
|
|
957
984
|
COMMIT;
|
|
958
985
|
`
|
|
@@ -969,14 +996,10 @@ function assertMigration (schema, version) {
|
|
|
969
996
|
return `SELECT version::int/(version::int-${version}) from ${schema}.version`
|
|
970
997
|
}
|
|
971
998
|
|
|
972
|
-
function getJobById (schema) {
|
|
973
|
-
return
|
|
974
|
-
}
|
|
975
|
-
|
|
976
|
-
function getArchivedJobById (schema) {
|
|
977
|
-
return getJobByTableQueueId(schema, 'archive')
|
|
999
|
+
function getJobById (schema, table) {
|
|
1000
|
+
return `SELECT ${JOB_COLUMNS_ALL} FROM ${schema}.${table} WHERE name = $1 AND id = $2`
|
|
978
1001
|
}
|
|
979
1002
|
|
|
980
|
-
function
|
|
981
|
-
return
|
|
1003
|
+
function getQueueInClause (queues) {
|
|
1004
|
+
return queues.map(i => `'${i}'`).join(',')
|
|
982
1005
|
}
|