pg-boss 10.3.2 → 11.0.0

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/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
- archive,
45
- drop,
46
- countStates,
48
+ deletion,
49
+ cacheQueueStats,
47
50
  updateQueue,
48
51
  createQueue,
49
52
  deleteQueue,
50
53
  getQueues,
51
- getQueueByName,
52
- getQueueSize,
53
- purgeQueue,
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 assert = require('node:assert')
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
- maintained_on timestamp with time zone,
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
- expire_seconds int,
139
- retention_minutes int,
140
- dead_letter text REFERENCES ${schema}.queue (name),
141
- partition_name text,
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('${JOB_STATES.created}'),
184
- retry_limit integer not null default(2),
185
- retry_count integer not null default(0),
186
- retry_delay integer not null default(0),
187
- retry_backoff boolean not null default false,
188
- start_after timestamp with time zone not null default now(),
189
- started_on timestamp with time zone,
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
- expire_in interval not null default interval '15 minutes',
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 '14 days',
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 baseJobColumns = 'id, name, data, EXTRACT(epoch FROM expire_in) as "expireInSeconds"'
204
- const allJobColumns = `${baseJobColumns},
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
- expire_in as "expireIn",
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 json)
265
+ CREATE FUNCTION ${schema}.create_queue(queue_name text, options jsonb)
227
266
  RETURNS VOID AS
228
267
  $$
229
268
  DECLARE
230
- table_name varchar := 'j' || encode(sha224(queue_name::bytea), 'hex');
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
- INSERT INTO ${schema}.queue (
236
- name,
237
- policy,
238
- retry_limit,
239
- retry_delay,
240
- retry_backoff,
241
- expire_seconds,
242
- retention_minutes,
243
- dead_letter,
244
- partition_name
245
- )
246
- VALUES (
247
- queue_name,
248
- options->>'policy',
249
- (options->>'retryLimit')::int,
250
- (options->>'retryDelay')::int,
251
- (options->>'retryBackoff')::bool,
252
- (options->>'expireInSeconds')::int,
253
- (options->>'retentionMinutes')::int,
254
- options->>'deadLetter',
255
- table_name
256
- )
257
- ON CONFLICT DO NOTHING
258
- RETURNING created_on
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)', table_name);
267
-
268
- EXECUTE format('${formatPartitionCommand(createPrimaryKeyJob(schema))}', table_name);
269
- EXECUTE format('${formatPartitionCommand(createQueueForeignKeyJob(schema))}', table_name);
270
- EXECUTE format('${formatPartitionCommand(createQueueForeignKeyJobDeadLetter(schema))}', table_name);
271
- EXECUTE format('${formatPartitionCommand(createIndexJobPolicyShort(schema))}', table_name);
272
- EXECUTE format('${formatPartitionCommand(createIndexJobPolicySingleton(schema))}', table_name);
273
- EXECUTE format('${formatPartitionCommand(createIndexJobPolicyStately(schema))}', table_name);
274
- EXECUTE format('${formatPartitionCommand(createIndexJobThrottle(schema))}', table_name);
275
- EXECUTE format('${formatPartitionCommand(createIndexJobFetch(schema))}', table_name);
276
-
277
- EXECUTE format('ALTER TABLE ${schema}.%I ADD CONSTRAINT cjc CHECK (name=%L)', table_name, queue_name);
278
- EXECUTE format('ALTER TABLE ${schema}.job ATTACH PARTITION ${schema}.%I FOR VALUES IN (%L)', table_name, queue_name);
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.replace('.job', '.%1$I').replace('job_i', '%1$s_i').replaceAll('\'', '\'\'')
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
- table_name varchar;
348
+ v_table varchar;
349
+ v_partition bool;
296
350
  BEGIN
297
- WITH deleted as (
298
- DELETE FROM ${schema}.queue
299
- WHERE name = queue_name
300
- RETURNING partition_name
301
- )
302
- SELECT partition_name from deleted INTO table_name;
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
- EXECUTE format('DROP TABLE IF EXISTS ${schema}.%I', table_name);
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
- return `SELECT ${schema}.create_queue($1, $2)`
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
- return `SELECT ${schema}.delete_queue($1)`
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 createTableArchive (schema) {
356
- return `CREATE TABLE ${schema}.archive (LIKE ${schema}.job)`
357
- }
358
-
359
- function createColumnArchiveArchivedOn (schema) {
360
- return `ALTER TABLE ${schema}.archive ADD archived_on timestamptz NOT NULL DEFAULT now()`
361
- }
362
-
363
- function createIndexArchiveArchivedOn (schema) {
364
- return `CREATE INDEX archive_i1 ON ${schema}.archive(archived_on)`
411
+ function trySetQueueMonitorTime (schema, queues, seconds) {
412
+ return trySetQueueTimestamp(schema, queues, 'monitor_on', seconds)
365
413
  }
366
414
 
367
- function trySetMaintenanceTime (schema) {
368
- return trySetTimestamp(schema, 'maintained_on')
415
+ function trySetQueueDeletionTime (schema, queues, seconds) {
416
+ return trySetQueueTimestamp(schema, queues, 'maintain_on', seconds)
369
417
  }
370
418
 
371
- function trySetMonitorTime (schema) {
372
- return trySetTimestamp(schema, 'monitored_on')
419
+ function trySetCronTime (schema, seconds) {
420
+ return trySetTimestamp(schema, 'cron_on', seconds)
373
421
  }
374
422
 
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 trySetTimestamp (schema, column) {
432
+ function trySetQueueTimestamp (schema, queues, column, seconds) {
380
433
  return `
381
- UPDATE ${schema}.version SET ${column} = now()
382
- WHERE EXTRACT( EPOCH FROM (now() - COALESCE(${column}, now() - interval '1 week') ) ) > $1
383
- RETURNING true
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($2, policy),
391
- retry_limit = COALESCE($3, retry_limit),
392
- retry_delay = COALESCE($4, retry_delay),
393
- retry_backoff = COALESCE($5, retry_backoff),
394
- expire_seconds = COALESCE($6, expire_seconds),
395
- retention_minutes = COALESCE($7, retention_minutes),
396
- dead_letter = COALESCE($8, dead_letter),
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
- expire_seconds as "expireInSeconds",
411
- retention_minutes as "retentionMinutes",
412
- dead_letter as "deadLetter",
413
- created_on as "createdOn",
414
- updated_on as "updatedOn"
415
- FROM ${schema}.queue
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 getQueueByName (schema) {
420
- return `${getQueues(schema)} WHERE name = $1`
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
+ `
506
+ }
507
+
508
+ function deleteQueuedJobs (schema, table) {
509
+ return `DELETE from ${schema}.${table} WHERE name = $1 and state < '${JOB_STATES.active}'`
421
510
  }
422
511
 
423
- function purgeQueue (schema) {
424
- return `DELETE from ${schema}.job WHERE name = $1 and state < '${JOB_STATES.active}'`
512
+ function deleteStoredJobs (schema, table) {
513
+ return `DELETE from ${schema}.${table} WHERE name = $1 and state > '${JOB_STATES.active}'`
425
514
  }
426
515
 
427
- function clearStorage (schema) {
428
- return `TRUNCATE ${schema}.job, ${schema}.archive`
516
+ function truncateTable (schema, table) {
517
+ return `TRUNCATE ${schema}.${table}`
429
518
  }
430
519
 
431
- function getQueueSize (schema, options = {}) {
432
- options.before = options.before || JOB_STATES.active
433
- assert(options.before in JOB_STATES, `${options.before} is not a valid state`)
434
- return `SELECT count(*) as count FROM ${schema}.job WHERE name = $1 AND state < '${options.before}'`
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
- return ({ includeMetadata, priority = true, ignoreStartAfter = false } = {}) => `
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}.job
511
- WHERE name = $1
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 $2
611
+ LIMIT ${limit}
516
612
  FOR UPDATE SKIP LOCKED
517
613
  )
518
- UPDATE ${schema}.job j SET
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 next
523
- WHERE name = $1 AND j.id = next.id
524
- RETURNING j.${includeMetadata ? allJobColumns : baseJobColumns}
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}.job
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 failJobsById (schema) {
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) 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}' AND (started_on + expire_in) < now()`
553
- const output = '\'{ "value": { "message": "job failed by timeout in active state" } }\'::jsonb'
554
- return failJobs(schema, where, output)
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}.job
764
+ DELETE FROM ${schema}.${table}
561
765
  WHERE ${where}
562
766
  RETURNING *
563
767
  ),
564
768
  retried_jobs AS (
565
- INSERT INTO ${schema}.job (
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
- expire_in,
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
- CASE
601
- WHEN retry_count = retry_limit THEN start_after
602
- WHEN NOT retry_backoff THEN now() + retry_delay * interval '1'
603
- ELSE now() + (
604
- retry_delay * 2 ^ LEAST(16, retry_count + 1) / 2 +
605
- retry_delay * 2 ^ LEAST(16, retry_count + 1) / 2 * random()
606
- ) * interval '1'
607
- END as start_after,
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
- expire_in,
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}.job (
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
- expire_in,
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
- expire_in,
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
- keep_until + (keep_until - start_after)
686
- FROM results
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 cancelJobs (schema) {
696
- return `
697
- with results as (
698
- UPDATE ${schema}.job
699
- SET completed_on = now(),
700
- state = '${JOB_STATES.cancelled}'
701
- WHERE name = $1
702
- AND id IN (SELECT UNNEST($2::uuid[]))
703
- AND state < '${JOB_STATES.completed}'
704
- RETURNING 1
705
- )
706
- SELECT COUNT(*) from results
707
- `
708
- }
709
-
710
- function resumeJobs (schema) {
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
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
+ )
722
918
  `
723
- }
724
919
 
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
- with results as (
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 insertJob (schema) {
938
+ function getQueueStats (schema, table, queues) {
753
939
  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
940
  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
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
826
950
  `
827
951
  }
828
952
 
829
- function insertJobs (schema) {
830
- 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
- SELECT
856
- COALESCE(id, gen_random_uuid()) as id,
857
- j.name,
858
- data,
859
- COALESCE(priority, 0) as priority,
860
- j.start_after,
861
- "singletonKey" as singleton_key,
862
- CASE
863
- WHEN "singletonSeconds" IS NOT NULL THEN 'epoch'::timestamp + '1 second'::interval * ("singletonSeconds" * floor( date_part('epoch', now()) / "singletonSeconds" ))
864
- ELSE NULL
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
910
- `
911
- }
912
-
913
- function drop (schema, interval) {
914
- return `
915
- DELETE FROM ${schema}.archive
916
- WHERE archived_on < (now() - interval '${interval}')
917
- `
918
- }
919
-
920
- function archive (schema, completedInterval, failedInterval = completedInterval) {
921
- const columns = 'id, name, priority, data, state, retry_limit, retry_count, retry_delay, retry_backoff, start_after, started_on, singleton_key, singleton_on, expire_in, created_on, completed_on, keep_until, dead_letter, policy, output'
922
-
923
- return `
924
- WITH archived_rows AS (
925
- DELETE FROM ${schema}.job
926
- WHERE (state <> '${JOB_STATES.failed}' AND completed_on < (now() - interval '${completedInterval}'))
927
- OR (state = '${JOB_STATES.failed}' AND completed_on < (now() - interval '${failedInterval}'))
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
- function countStates (schema) {
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 = '30s';
954
- SET LOCAL idle_in_transaction_session_timeout = '30s';
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 getJobByTableQueueId(schema, 'job')
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 getJobByTableQueueId (schema, table) {
981
- return `SELECT ${allJobColumns} FROM ${schema}.${table} WHERE name = $1 AND id = $2`
1003
+ function getQueueInClause (queues) {
1004
+ return queues.map(i => `'${i}'`).join(',')
982
1005
  }