pg-boss 10.0.0-beta1 → 10.0.0-beta2
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 +4 -7
- package/package.json +6 -5
- package/src/attorney.js +0 -13
- package/src/contractor.js +2 -1
- package/src/db.js +8 -16
- package/src/manager.js +26 -39
- package/src/migrationStore.js +0 -161
- package/src/plans.js +16 -28
- package/src/timekeeper.js +4 -1
- package/src/worker.js +1 -1
- package/types.d.ts +11 -11
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Queueing jobs in Node.js
|
|
1
|
+
Queueing jobs in Postgres from Node.js like a boss.
|
|
2
2
|
|
|
3
3
|
[](https://badge.fury.io/js/pg-boss)
|
|
4
4
|
[](https://github.com/timgit/pg-boss/actions/workflows/ci.yml)
|
|
@@ -41,15 +41,13 @@ This will likely cater the most to teams already familiar with the simplicity of
|
|
|
41
41
|
* Backpressure-compatible polling workers
|
|
42
42
|
* Cron scheduling
|
|
43
43
|
* Pub/sub API for fan-out queue relationships
|
|
44
|
-
* Priority, deferral, retries (with exponential backoff), rate limiting, debouncing
|
|
45
|
-
*
|
|
44
|
+
* Priority queues, deferral, retries (with exponential backoff), rate limiting, debouncing
|
|
45
|
+
* Table operations via SQL for bulk loads via COPY or INSERT
|
|
46
46
|
* Multi-master compatible (for example, in a Kubernetes ReplicaSet)
|
|
47
47
|
* Dead letter queues
|
|
48
|
-
* Automatic creation and migration of storage tables
|
|
49
|
-
* Automatic maintenance operations to manage table growth
|
|
50
48
|
|
|
51
49
|
## Requirements
|
|
52
|
-
* Node
|
|
50
|
+
* Node 20 or higher
|
|
53
51
|
* PostgreSQL 12 or higher
|
|
54
52
|
|
|
55
53
|
## Installation
|
|
@@ -66,7 +64,6 @@ yarn add pg-boss
|
|
|
66
64
|
* [Docs](docs/readme.md)
|
|
67
65
|
|
|
68
66
|
## Contributing
|
|
69
|
-
|
|
70
67
|
To setup a development environment for this library:
|
|
71
68
|
|
|
72
69
|
```bash
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pg-boss",
|
|
3
|
-
"version": "10.0.0-
|
|
4
|
-
"description": "Queueing jobs in Node.js
|
|
3
|
+
"version": "10.0.0-beta2",
|
|
4
|
+
"description": "Queueing jobs in Postgres from Node.js like a boss",
|
|
5
5
|
"main": "./src/index.js",
|
|
6
6
|
"engines": {
|
|
7
|
-
"node": ">=
|
|
7
|
+
"node": ">=20"
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
10
|
"cron-parser": "^4.0.0",
|
|
@@ -17,14 +17,15 @@
|
|
|
17
17
|
"@types/node": "^20.3.3",
|
|
18
18
|
"luxon": "^3.0.1",
|
|
19
19
|
"mocha": "^10.0.0",
|
|
20
|
-
"nyc": "^
|
|
20
|
+
"nyc": "^17.0.0",
|
|
21
21
|
"standard": "^17.0.0"
|
|
22
22
|
},
|
|
23
23
|
"scripts": {
|
|
24
24
|
"test": "standard && mocha",
|
|
25
25
|
"cover": "nyc npm test",
|
|
26
26
|
"tsc": "tsc --noEmit types.d.ts",
|
|
27
|
-
"readme": "node ./test/readme.js"
|
|
27
|
+
"readme": "node ./test/readme.js",
|
|
28
|
+
"migrate": "node -e 'console.log(require(\"./src\").getMigrationPlans())'"
|
|
28
29
|
},
|
|
29
30
|
"mocha": {
|
|
30
31
|
"timeout": 10000,
|
package/src/attorney.js
CHANGED
|
@@ -8,7 +8,6 @@ module.exports = {
|
|
|
8
8
|
checkWorkArgs,
|
|
9
9
|
checkFetchArgs,
|
|
10
10
|
warnClockSkew,
|
|
11
|
-
queueNameHasPatternMatch,
|
|
12
11
|
assertPostgresObjectName
|
|
13
12
|
}
|
|
14
13
|
|
|
@@ -140,24 +139,12 @@ function checkWorkArgs (name, args, defaults) {
|
|
|
140
139
|
function checkFetchArgs (name, batchSize, options) {
|
|
141
140
|
assert(name, 'missing queue name')
|
|
142
141
|
|
|
143
|
-
if (queueNameHasPatternMatch(name)) {
|
|
144
|
-
name = sanitizeQueueNameForFetch(name)
|
|
145
|
-
}
|
|
146
|
-
|
|
147
142
|
assert(!batchSize || (Number.isInteger(batchSize) && batchSize >= 1), 'batchSize must be an integer > 0')
|
|
148
143
|
assert(!('includeMetadata' in options) || typeof options.includeMetadata === 'boolean', 'includeMetadata must be a boolean')
|
|
149
144
|
|
|
150
145
|
return { name }
|
|
151
146
|
}
|
|
152
147
|
|
|
153
|
-
function sanitizeQueueNameForFetch (name) {
|
|
154
|
-
return name.replace(/[%_*]/g, match => match === '*' ? '%' : '\\' + match)
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
function queueNameHasPatternMatch (name) {
|
|
158
|
-
return name.includes('*')
|
|
159
|
-
}
|
|
160
|
-
|
|
161
148
|
function getConfig (value) {
|
|
162
149
|
assert(value && (typeof value === 'object' || typeof value === 'string'),
|
|
163
150
|
'configuration assert: string or config object is required to connect to postgres')
|
package/src/contractor.js
CHANGED
|
@@ -40,7 +40,8 @@ class Contractor {
|
|
|
40
40
|
const version = await this.version()
|
|
41
41
|
|
|
42
42
|
if (schemaVersion > version) {
|
|
43
|
-
|
|
43
|
+
throw new Error('Migrations are not supported to v10')
|
|
44
|
+
// await this.migrate(version)
|
|
44
45
|
}
|
|
45
46
|
} else {
|
|
46
47
|
await this.create()
|
package/src/db.js
CHANGED
|
@@ -26,15 +26,15 @@ class Db extends EventEmitter {
|
|
|
26
26
|
|
|
27
27
|
async executeSql (text, values) {
|
|
28
28
|
if (this.opened) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
if (this.config.debug === true) {
|
|
30
|
+
console.log(`${new Date().toISOString()}: DEBUG SQL`)
|
|
31
|
+
console.log(text)
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
if (values) {
|
|
34
|
+
console.log(`${new Date().toISOString()}: DEBUG VALUES`)
|
|
35
|
+
console.log(values)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
38
|
|
|
39
39
|
return await this.pool.query(text, values)
|
|
40
40
|
}
|
|
@@ -68,14 +68,6 @@ class Db extends EventEmitter {
|
|
|
68
68
|
|
|
69
69
|
return locker
|
|
70
70
|
}
|
|
71
|
-
|
|
72
|
-
static quotePostgresStr (str) {
|
|
73
|
-
const delimeter = '$sanitize$'
|
|
74
|
-
if (str.includes(delimeter)) {
|
|
75
|
-
throw new Error(`Attempted to quote string that contains reserved Postgres delimeter: ${str}`)
|
|
76
|
-
}
|
|
77
|
-
return `${delimeter}${str}${delimeter}`
|
|
78
|
-
}
|
|
79
71
|
}
|
|
80
72
|
|
|
81
73
|
module.exports = Db
|
package/src/manager.js
CHANGED
|
@@ -8,7 +8,6 @@ const { delay } = require('./tools')
|
|
|
8
8
|
const Attorney = require('./attorney')
|
|
9
9
|
const Worker = require('./worker')
|
|
10
10
|
const plans = require('./plans')
|
|
11
|
-
const Db = require('./db')
|
|
12
11
|
|
|
13
12
|
const { QUEUES: TIMEKEEPER_QUEUES } = require('./timekeeper')
|
|
14
13
|
const { QUEUE_POLICY } = plans
|
|
@@ -107,10 +106,11 @@ class Manager extends EventEmitter {
|
|
|
107
106
|
}
|
|
108
107
|
|
|
109
108
|
async failWip () {
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
109
|
+
for (const worker of this.workers.values()) {
|
|
110
|
+
const jobIds = worker.jobs.map(j => j.id)
|
|
111
|
+
if (jobIds.length) {
|
|
112
|
+
await this.fail(worker.name, jobIds, 'pg-boss shut down while active')
|
|
113
|
+
}
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
|
|
@@ -220,8 +220,8 @@ class Manager extends EventEmitter {
|
|
|
220
220
|
const maxExpiration = jobs.reduce((acc, i) => Math.max(acc, i.expire_in_seconds), 0)
|
|
221
221
|
|
|
222
222
|
await resolveWithinSeconds(Promise.all([callback(jobs)]), maxExpiration)
|
|
223
|
-
.then(() => this.complete(jobs.map(job => job.id)))
|
|
224
|
-
.catch(err => this.fail(jobs.map(job => job.id), err))
|
|
223
|
+
.then(() => this.complete(name, jobs.map(job => job.id)))
|
|
224
|
+
.catch(err => this.fail(name, jobs.map(job => job.id), err))
|
|
225
225
|
} else {
|
|
226
226
|
if (refill) {
|
|
227
227
|
queueSize += jobs.length || 1
|
|
@@ -229,8 +229,8 @@ class Manager extends EventEmitter {
|
|
|
229
229
|
|
|
230
230
|
const allTeamPromise = pMap(jobs, job =>
|
|
231
231
|
resolveWithinSeconds(callback(job), job.expire_in_seconds)
|
|
232
|
-
.then(result => this.complete(job.id, result))
|
|
233
|
-
.catch(err => this.fail(job.id, err))
|
|
232
|
+
.then(result => this.complete(name, job.id, result))
|
|
233
|
+
.catch(err => this.fail(name, job.id, err))
|
|
234
234
|
.then(() => refill ? onRefill() : null)
|
|
235
235
|
, { concurrency: teamConcurrency }
|
|
236
236
|
).catch(() => {}) // allow promises & non-promises to live together in harmony
|
|
@@ -462,32 +462,15 @@ class Manager extends EventEmitter {
|
|
|
462
462
|
}
|
|
463
463
|
|
|
464
464
|
async fetch (name, batchSize, options = {}) {
|
|
465
|
-
const patternMatch = Attorney.queueNameHasPatternMatch(name)
|
|
466
465
|
const values = Attorney.checkFetchArgs(name, batchSize, options)
|
|
467
466
|
const db = options.db || this.db
|
|
468
|
-
const nextJobSql = this.nextJobCommand({ ...options
|
|
467
|
+
const nextJobSql = this.nextJobCommand({ ...options })
|
|
469
468
|
const statementValues = [values.name, batchSize || 1]
|
|
470
469
|
|
|
471
470
|
let result
|
|
472
471
|
|
|
473
472
|
try {
|
|
474
|
-
|
|
475
|
-
// Prepare/format now and send multi-statement transaction
|
|
476
|
-
const fetchQuery = nextJobSql
|
|
477
|
-
.replace('$1', Db.quotePostgresStr(statementValues[0]))
|
|
478
|
-
.replace('$2', statementValues[1].toString())
|
|
479
|
-
|
|
480
|
-
// eslint-disable-next-line no-unused-vars
|
|
481
|
-
const [_begin, _setLocal, fetchResult, _commit] = await db.executeSql([
|
|
482
|
-
'BEGIN',
|
|
483
|
-
'SET LOCAL jit = OFF', // JIT can slow things down significantly
|
|
484
|
-
fetchQuery,
|
|
485
|
-
'COMMIT'
|
|
486
|
-
].join(';\n'))
|
|
487
|
-
result = fetchResult
|
|
488
|
-
} else {
|
|
489
|
-
result = await db.executeSql(nextJobSql, statementValues)
|
|
490
|
-
}
|
|
473
|
+
result = await db.executeSql(nextJobSql, statementValues)
|
|
491
474
|
} catch (err) {
|
|
492
475
|
// errors from fetchquery should only be unique constraint violations
|
|
493
476
|
}
|
|
@@ -529,31 +512,35 @@ class Manager extends EventEmitter {
|
|
|
529
512
|
}
|
|
530
513
|
}
|
|
531
514
|
|
|
532
|
-
async complete (id, data, options = {}) {
|
|
515
|
+
async complete (name, id, data, options = {}) {
|
|
516
|
+
assert(name, 'Missing queue name argument')
|
|
533
517
|
const db = options.db || this.db
|
|
534
518
|
const ids = this.mapCompletionIdArg(id, 'complete')
|
|
535
|
-
const result = await db.executeSql(this.completeJobsCommand, [ids, this.mapCompletionDataArg(data)])
|
|
519
|
+
const result = await db.executeSql(this.completeJobsCommand, [name, ids, this.mapCompletionDataArg(data)])
|
|
536
520
|
return this.mapCompletionResponse(ids, result)
|
|
537
521
|
}
|
|
538
522
|
|
|
539
|
-
async fail (id, data, options = {}) {
|
|
523
|
+
async fail (name, id, data, options = {}) {
|
|
524
|
+
assert(name, 'Missing queue name argument')
|
|
540
525
|
const db = options.db || this.db
|
|
541
526
|
const ids = this.mapCompletionIdArg(id, 'fail')
|
|
542
|
-
const result = await db.executeSql(this.failJobsByIdCommand, [ids, this.mapCompletionDataArg(data)])
|
|
527
|
+
const result = await db.executeSql(this.failJobsByIdCommand, [name, ids, this.mapCompletionDataArg(data)])
|
|
543
528
|
return this.mapCompletionResponse(ids, result)
|
|
544
529
|
}
|
|
545
530
|
|
|
546
|
-
async cancel (id, options = {}) {
|
|
531
|
+
async cancel (name, id, options = {}) {
|
|
532
|
+
assert(name, 'Missing queue name argument')
|
|
547
533
|
const db = options.db || this.db
|
|
548
534
|
const ids = this.mapCompletionIdArg(id, 'cancel')
|
|
549
|
-
const result = await db.executeSql(this.cancelJobsCommand, [ids])
|
|
535
|
+
const result = await db.executeSql(this.cancelJobsCommand, [name, ids])
|
|
550
536
|
return this.mapCompletionResponse(ids, result)
|
|
551
537
|
}
|
|
552
538
|
|
|
553
|
-
async resume (id, options = {}) {
|
|
539
|
+
async resume (name, id, options = {}) {
|
|
540
|
+
assert(name, 'Missing queue name argument')
|
|
554
541
|
const db = options.db || this.db
|
|
555
542
|
const ids = this.mapCompletionIdArg(id, 'resume')
|
|
556
|
-
const result = await db.executeSql(this.resumeJobsCommand, [ids])
|
|
543
|
+
const result = await db.executeSql(this.resumeJobsCommand, [name, ids])
|
|
557
544
|
return this.mapCompletionResponse(ids, result)
|
|
558
545
|
}
|
|
559
546
|
|
|
@@ -690,15 +677,15 @@ class Manager extends EventEmitter {
|
|
|
690
677
|
return result ? parseFloat(result.rows[0].count) : null
|
|
691
678
|
}
|
|
692
679
|
|
|
693
|
-
async getJobById (id, options = {}) {
|
|
680
|
+
async getJobById (queue, id, options = {}) {
|
|
694
681
|
const db = options.db || this.db
|
|
695
|
-
const result1 = await db.executeSql(this.getJobByIdCommand, [id])
|
|
682
|
+
const result1 = await db.executeSql(this.getJobByIdCommand, [queue, id])
|
|
696
683
|
|
|
697
684
|
if (result1 && result1.rows && result1.rows.length === 1) {
|
|
698
685
|
return result1.rows[0]
|
|
699
686
|
}
|
|
700
687
|
|
|
701
|
-
const result2 = await db.executeSql(this.getArchivedJobByIdCommand, [id])
|
|
688
|
+
const result2 = await db.executeSql(this.getArchivedJobByIdCommand, [queue, id])
|
|
702
689
|
|
|
703
690
|
if (result2 && result2.rows && result2.rows.length === 1) {
|
|
704
691
|
return result2.rows[0]
|
package/src/migrationStore.js
CHANGED
|
@@ -64,166 +64,5 @@ function migrate (value, version, migrations) {
|
|
|
64
64
|
|
|
65
65
|
function getAll (schema) {
|
|
66
66
|
return [
|
|
67
|
-
{
|
|
68
|
-
release: '10.0.0',
|
|
69
|
-
version: 21,
|
|
70
|
-
previous: 20,
|
|
71
|
-
install: [
|
|
72
|
-
`DROP INDEX ${schema}.job_singletonKey`,
|
|
73
|
-
`DROP INDEX ${schema}.job_singleton_queue`,
|
|
74
|
-
`DROP INDEX ${schema}.job_singletonOn`,
|
|
75
|
-
`DROP INDEX ${schema}.job_singletonKeyOn`,
|
|
76
|
-
`DROP INDEX ${schema}.job_fetch`,
|
|
77
|
-
`DROP INDEX ${schema}.job_name`,
|
|
78
|
-
|
|
79
|
-
`ALTER TABLE ${schema}.job ADD COLUMN deadletter text`,
|
|
80
|
-
`ALTER TABLE ${schema}.job ADD COLUMN policy text`,
|
|
81
|
-
`ALTER TABLE ${schema}.job DROP COLUMN on_complete`,
|
|
82
|
-
|
|
83
|
-
// update state enum
|
|
84
|
-
`ALTER TABLE ${schema}.job ALTER COLUMN state TYPE text`,
|
|
85
|
-
`ALTER TABLE ${schema}.job ALTER COLUMN state DROP DEFAULT`,
|
|
86
|
-
`ALTER TABLE ${schema}.archive ALTER COLUMN state TYPE text`,
|
|
87
|
-
|
|
88
|
-
`DROP TABLE IF EXISTS ${schema}.archive_backup`,
|
|
89
|
-
`ALTER TABLE ${schema}.archive RENAME to archive_backup`,
|
|
90
|
-
`ALTER INDEX ${schema}.archive_archivedon_idx RENAME to archive_backup_archivedon_idx`,
|
|
91
|
-
|
|
92
|
-
`DROP TYPE ${schema}.job_state`,
|
|
93
|
-
`CREATE TYPE ${schema}.job_state AS ENUM ('created','retry','active','completed','cancelled','failed')`,
|
|
94
|
-
|
|
95
|
-
`ALTER TABLE ${schema}.job ALTER COLUMN state TYPE ${schema}.job_state USING state::${schema}.job_state`,
|
|
96
|
-
`ALTER TABLE ${schema}.job ALTER COLUMN state SET DEFAULT 'created'::${schema}.job_state`,
|
|
97
|
-
|
|
98
|
-
`DELETE FROM ${schema}.job WHERE name LIKE '__pgboss__%'`,
|
|
99
|
-
|
|
100
|
-
// set up job partitioning
|
|
101
|
-
`ALTER TABLE ${schema}.job RENAME TO job_default`,
|
|
102
|
-
`ALTER TABLE ${schema}.job_default DROP CONSTRAINT IF EXISTS job_pkey`,
|
|
103
|
-
|
|
104
|
-
`CREATE TABLE ${schema}.job (
|
|
105
|
-
id uuid not null default gen_random_uuid(),
|
|
106
|
-
name text not null,
|
|
107
|
-
priority integer not null default(0),
|
|
108
|
-
data jsonb,
|
|
109
|
-
state ${schema}.job_state not null default('created'),
|
|
110
|
-
retryLimit integer not null default(0),
|
|
111
|
-
retryCount integer not null default(0),
|
|
112
|
-
retryDelay integer not null default(0),
|
|
113
|
-
retryBackoff boolean not null default false,
|
|
114
|
-
startAfter timestamp with time zone not null default now(),
|
|
115
|
-
startedOn timestamp with time zone,
|
|
116
|
-
singletonKey text,
|
|
117
|
-
singletonOn timestamp without time zone,
|
|
118
|
-
expireIn interval not null default interval '15 minutes',
|
|
119
|
-
createdOn timestamp with time zone not null default now(),
|
|
120
|
-
completedOn timestamp with time zone,
|
|
121
|
-
keepUntil timestamp with time zone NOT NULL default now() + interval '14 days',
|
|
122
|
-
output jsonb,
|
|
123
|
-
deadletter text,
|
|
124
|
-
policy text,
|
|
125
|
-
CONSTRAINT job_pkey PRIMARY KEY (name, id)
|
|
126
|
-
) PARTITION BY LIST (name)`,
|
|
127
|
-
|
|
128
|
-
`ALTER TABLE ${schema}.job ATTACH PARTITION ${schema}.job_default DEFAULT`,
|
|
129
|
-
|
|
130
|
-
`CREATE TABLE ${schema}.archive (LIKE ${schema}.job)`,
|
|
131
|
-
`ALTER TABLE ${schema}.archive ADD CONSTRAINT archive_pkey PRIMARY KEY (name, id)`,
|
|
132
|
-
`ALTER TABLE ${schema}.archive ADD archivedOn timestamptz NOT NULL DEFAULT now()`,
|
|
133
|
-
`CREATE INDEX archive_archivedon_idx ON ${schema}.archive(archivedon)`,
|
|
134
|
-
`CREATE INDEX archive_name_idx ON ${schema}.archive(name)`,
|
|
135
|
-
|
|
136
|
-
`CREATE INDEX job_fetch ON ${schema}.job (name text_pattern_ops, startAfter) INCLUDE (priority, createdOn, id) WHERE state < 'active'`,
|
|
137
|
-
`CREATE INDEX job_name ON ${schema}.job (name text_pattern_ops)`,
|
|
138
|
-
`CREATE UNIQUE INDEX job_policy_short ON ${schema}.job (name) WHERE state = 'created' AND policy = 'short'`,
|
|
139
|
-
`CREATE UNIQUE INDEX job_policy_singleton ON ${schema}.job (name) WHERE state = 'active' AND policy = 'singleton'`,
|
|
140
|
-
`CREATE UNIQUE INDEX job_policy_stately ON ${schema}.job (name, state) WHERE state <= 'active' AND policy = 'stately'`,
|
|
141
|
-
`CREATE UNIQUE INDEX job_throttle_key ON ${schema}.job (name, singletonKey) WHERE state <= 'completed' AND singletonOn IS NULL`,
|
|
142
|
-
`CREATE UNIQUE INDEX job_throttle_on ON ${schema}.job (name, singletonOn, COALESCE(singletonKey, '')) WHERE state <= 'completed' AND singletonOn IS NOT NULL`,
|
|
143
|
-
|
|
144
|
-
`ALTER TABLE ${schema}.version ADD COLUMN monitored_on timestamp with time zone`,
|
|
145
|
-
|
|
146
|
-
`CREATE TABLE ${schema}.queue (
|
|
147
|
-
name text primary key,
|
|
148
|
-
policy text,
|
|
149
|
-
retry_limit int,
|
|
150
|
-
retry_delay int,
|
|
151
|
-
retry_backoff bool,
|
|
152
|
-
expire_seconds int,
|
|
153
|
-
retention_minutes int,
|
|
154
|
-
dead_letter text,
|
|
155
|
-
created_on timestamp with time zone not null default now()
|
|
156
|
-
)`
|
|
157
|
-
],
|
|
158
|
-
uninstall: [
|
|
159
|
-
`DROP INDEX ${schema}.job_policy_stately`,
|
|
160
|
-
`DROP INDEX ${schema}.job_policy_short`,
|
|
161
|
-
`DROP INDEX ${schema}.job_policy_singleton`,
|
|
162
|
-
`DROP INDEX ${schema}.job_throttle_on`,
|
|
163
|
-
`DROP INDEX ${schema}.job_throttle_key`,
|
|
164
|
-
`DROP INDEX ${schema}.job_fetch`,
|
|
165
|
-
`DROP INDEX ${schema}.job_name`,
|
|
166
|
-
`ALTER TABLE ${schema}.job DETACH PARTITION ${schema}.job_default`,
|
|
167
|
-
`DROP TABLE ${schema}.job`,
|
|
168
|
-
`ALTER TABLE ${schema}.job_default RENAME TO job`,
|
|
169
|
-
`DROP TABLE IF EXISTS ${schema}.archive_backup`,
|
|
170
|
-
`DROP INDEX ${schema}.archive_name_idx`,
|
|
171
|
-
`ALTER TABLE ${schema}.job DROP COLUMN deadletter`,
|
|
172
|
-
`ALTER TABLE ${schema}.job DROP COLUMN policy`,
|
|
173
|
-
`ALTER TABLE ${schema}.job ALTER COLUMN state TYPE text`,
|
|
174
|
-
`ALTER TABLE ${schema}.job ALTER COLUMN state DROP DEFAULT`,
|
|
175
|
-
`ALTER TABLE ${schema}.archive ALTER COLUMN state TYPE text`,
|
|
176
|
-
`DROP TYPE ${schema}.job_state`,
|
|
177
|
-
`CREATE TYPE ${schema}.job_state AS ENUM ('created','retry','active','completed','expired','cancelled','failed')`,
|
|
178
|
-
`ALTER TABLE ${schema}.job ALTER COLUMN state TYPE ${schema}.job_state USING state::${schema}.job_state`,
|
|
179
|
-
`ALTER TABLE ${schema}.job ALTER COLUMN state SET DEFAULT 'created'::${schema}.job_state`,
|
|
180
|
-
`ALTER TABLE ${schema}.job ADD COLUMN on_complete bool NOT NULL DEFAULT false`,
|
|
181
|
-
`ALTER TABLE ${schema}.archive ALTER COLUMN state TYPE ${schema}.job_state USING state::${schema}.job_state`,
|
|
182
|
-
`ALTER TABLE ${schema}.archive DROP COLUMN policy`,
|
|
183
|
-
`ALTER TABLE ${schema}.archive DROP CONSTRAINT archive_pkey`,
|
|
184
|
-
`CREATE INDEX job_fetch ON ${schema}.job (name text_pattern_ops, startAfter) WHERE state < 'active'`,
|
|
185
|
-
`CREATE INDEX job_name ON ${schema}.job (name text_pattern_ops)`,
|
|
186
|
-
`CREATE UNIQUE INDEX job_singletonOn ON ${schema}.job (name, singletonOn) WHERE state < 'expired' AND singletonKey IS NULL`,
|
|
187
|
-
`CREATE UNIQUE INDEX job_singletonKeyOn ON ${schema}.job (name, singletonOn, singletonKey) WHERE state < 'expired'`,
|
|
188
|
-
`CREATE UNIQUE INDEX job_singletonKey ON ${schema}.job (name, singletonKey) WHERE state < 'completed' AND singletonOn IS NULL AND NOT singletonKey LIKE '\\_\\_pgboss\\_\\_singleton\\_queue%'`,
|
|
189
|
-
`CREATE UNIQUE INDEX job_singleton_queue ON ${schema}.job (name, singletonKey) WHERE state < 'active' AND singletonOn IS NULL AND singletonKey LIKE '\\_\\_pgboss\\_\\_singleton\\_queue%'`,
|
|
190
|
-
`DROP TABLE ${schema}.queue`,
|
|
191
|
-
`ALTER TABLE ${schema}.version DROP COLUMN monitored_on`
|
|
192
|
-
]
|
|
193
|
-
},
|
|
194
|
-
{
|
|
195
|
-
release: '7.4.0',
|
|
196
|
-
version: 20,
|
|
197
|
-
previous: 19,
|
|
198
|
-
install: [
|
|
199
|
-
`DROP INDEX ${schema}.job_singletonKey`,
|
|
200
|
-
`DROP INDEX ${schema}.job_singleton_queue`,
|
|
201
|
-
`CREATE UNIQUE INDEX job_singletonKey ON ${schema}.job (name, singletonKey) WHERE state < 'completed' AND singletonOn IS NULL AND NOT singletonKey LIKE '\\_\\_pgboss\\_\\_singleton\\_queue%'`,
|
|
202
|
-
`CREATE UNIQUE INDEX job_singleton_queue ON ${schema}.job (name, singletonKey) WHERE state < 'active' AND singletonOn IS NULL AND singletonKey LIKE '\\_\\_pgboss\\_\\_singleton\\_queue%'`
|
|
203
|
-
],
|
|
204
|
-
uninstall: [
|
|
205
|
-
`DROP INDEX ${schema}.job_singletonKey`,
|
|
206
|
-
`DROP INDEX ${schema}.job_singleton_queue`,
|
|
207
|
-
`CREATE UNIQUE INDEX job_singletonKey ON ${schema}.job (name, singletonKey) WHERE state < 'completed' AND singletonOn IS NULL AND NOT singletonKey = '__pgboss__singleton_queue'`,
|
|
208
|
-
`CREATE UNIQUE INDEX job_singleton_queue ON ${schema}.job (name, singletonKey) WHERE state < 'active' AND singletonOn IS NULL AND singletonKey = '__pgboss__singleton_queue'`
|
|
209
|
-
]
|
|
210
|
-
},
|
|
211
|
-
{
|
|
212
|
-
release: '7.0.0',
|
|
213
|
-
version: 19,
|
|
214
|
-
previous: 18,
|
|
215
|
-
install: [
|
|
216
|
-
`CREATE TABLE ${schema}.subscription (
|
|
217
|
-
event text not null,
|
|
218
|
-
name text not null,
|
|
219
|
-
created_on timestamp with time zone not null default now(),
|
|
220
|
-
updated_on timestamp with time zone not null default now(),
|
|
221
|
-
PRIMARY KEY(event, name)
|
|
222
|
-
)`
|
|
223
|
-
],
|
|
224
|
-
uninstall: [
|
|
225
|
-
`DROP TABLE ${schema}.subscription`
|
|
226
|
-
]
|
|
227
|
-
}
|
|
228
67
|
]
|
|
229
68
|
}
|
package/src/plans.js
CHANGED
|
@@ -77,8 +77,6 @@ function create (schema, version) {
|
|
|
77
77
|
createEnumJobState(schema),
|
|
78
78
|
|
|
79
79
|
createTableJob(schema),
|
|
80
|
-
createTableJobDefault(schema),
|
|
81
|
-
attachPartitionJobDefault(schema),
|
|
82
80
|
createIndexJobName(schema),
|
|
83
81
|
createIndexJobFetch(schema),
|
|
84
82
|
createIndexJobPolicyStately(schema),
|
|
@@ -92,7 +90,6 @@ function create (schema, version) {
|
|
|
92
90
|
createColumnArchiveArchivedOn(schema),
|
|
93
91
|
createIndexArchiveArchivedOn(schema),
|
|
94
92
|
createIndexArchiveName(schema),
|
|
95
|
-
createArchiveBackupTable(schema),
|
|
96
93
|
|
|
97
94
|
createTableVersion(schema),
|
|
98
95
|
createTableQueue(schema),
|
|
@@ -165,14 +162,6 @@ function createTableJob (schema) {
|
|
|
165
162
|
`
|
|
166
163
|
}
|
|
167
164
|
|
|
168
|
-
function createTableJobDefault (schema) {
|
|
169
|
-
return `CREATE TABLE ${schema}.job_default (LIKE ${schema}.job INCLUDING DEFAULTS INCLUDING CONSTRAINTS)`
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
function attachPartitionJobDefault (schema) {
|
|
173
|
-
return `ALTER TABLE ${schema}.job ATTACH PARTITION ${schema}.job_default DEFAULT`
|
|
174
|
-
}
|
|
175
|
-
|
|
176
165
|
function partitionCreateJobName (schema, name) {
|
|
177
166
|
return `
|
|
178
167
|
CREATE TABLE ${schema}.job_${name} (LIKE ${schema}.job INCLUDING DEFAULTS INCLUDING CONSTRAINTS);
|
|
@@ -210,21 +199,17 @@ function createIndexJobThrottleKey (schema) {
|
|
|
210
199
|
}
|
|
211
200
|
|
|
212
201
|
function createIndexJobName (schema) {
|
|
213
|
-
return `CREATE INDEX job_name ON ${schema}.job (name
|
|
202
|
+
return `CREATE INDEX job_name ON ${schema}.job (name)`
|
|
214
203
|
}
|
|
215
204
|
|
|
216
205
|
function createIndexJobFetch (schema) {
|
|
217
|
-
return `CREATE INDEX job_fetch ON ${schema}.job (name
|
|
206
|
+
return `CREATE INDEX job_fetch ON ${schema}.job (name, startAfter) INCLUDE (priority, createdOn, id) WHERE state < '${states.active}'`
|
|
218
207
|
}
|
|
219
208
|
|
|
220
209
|
function createTableArchive (schema) {
|
|
221
210
|
return `CREATE TABLE ${schema}.archive (LIKE ${schema}.job)`
|
|
222
211
|
}
|
|
223
212
|
|
|
224
|
-
function createArchiveBackupTable (schema) {
|
|
225
|
-
return `CREATE TABLE ${schema}.archive_backup (LIKE ${schema}.job)`
|
|
226
|
-
}
|
|
227
|
-
|
|
228
213
|
function createColumnArchiveArchivedOn (schema) {
|
|
229
214
|
return `ALTER TABLE ${schema}.archive ADD archivedOn timestamptz NOT NULL DEFAULT now()`
|
|
230
215
|
}
|
|
@@ -422,12 +407,12 @@ function insertVersion (schema, version) {
|
|
|
422
407
|
}
|
|
423
408
|
|
|
424
409
|
function fetchNextJob (schema) {
|
|
425
|
-
return ({ includeMetadata,
|
|
410
|
+
return ({ includeMetadata, priority = true } = {}) => `
|
|
426
411
|
WITH next as (
|
|
427
412
|
SELECT id
|
|
428
413
|
FROM ${schema}.job
|
|
429
|
-
WHERE
|
|
430
|
-
AND
|
|
414
|
+
WHERE name = $1
|
|
415
|
+
AND state < '${states.active}'
|
|
431
416
|
AND startAfter < now()
|
|
432
417
|
ORDER BY ${priority && 'priority desc, '} createdOn, id
|
|
433
418
|
LIMIT $2
|
|
@@ -438,7 +423,7 @@ function fetchNextJob (schema) {
|
|
|
438
423
|
startedOn = now(),
|
|
439
424
|
retryCount = CASE WHEN startedOn IS NOT NULL THEN retryCount + 1 ELSE retryCount END
|
|
440
425
|
FROM next
|
|
441
|
-
WHERE j.id = next.id
|
|
426
|
+
WHERE name = $1 AND j.id = next.id
|
|
442
427
|
RETURNING ${includeMetadata ? 'j.*' : 'j.id, name, data'},
|
|
443
428
|
EXTRACT(epoch FROM expireIn) as expire_in_seconds
|
|
444
429
|
`
|
|
@@ -450,8 +435,9 @@ function completeJobs (schema) {
|
|
|
450
435
|
UPDATE ${schema}.job
|
|
451
436
|
SET completedOn = now(),
|
|
452
437
|
state = '${states.completed}',
|
|
453
|
-
output = $
|
|
454
|
-
WHERE
|
|
438
|
+
output = $3::jsonb
|
|
439
|
+
WHERE name = $1
|
|
440
|
+
AND id IN (SELECT UNNEST($2::uuid[]))
|
|
455
441
|
AND state = '${states.active}'
|
|
456
442
|
RETURNING *
|
|
457
443
|
)
|
|
@@ -460,8 +446,8 @@ function completeJobs (schema) {
|
|
|
460
446
|
}
|
|
461
447
|
|
|
462
448
|
function failJobsById (schema) {
|
|
463
|
-
const where = `id IN (SELECT UNNEST($
|
|
464
|
-
const output = '$
|
|
449
|
+
const where = `name = $1 AND id IN (SELECT UNNEST($2::uuid[])) AND state < '${states.completed}'`
|
|
450
|
+
const output = '$3::jsonb'
|
|
465
451
|
|
|
466
452
|
return failJobs(schema, where, output)
|
|
467
453
|
}
|
|
@@ -518,7 +504,8 @@ function cancelJobs (schema) {
|
|
|
518
504
|
UPDATE ${schema}.job
|
|
519
505
|
SET completedOn = now(),
|
|
520
506
|
state = '${states.cancelled}'
|
|
521
|
-
WHERE
|
|
507
|
+
WHERE name = $1
|
|
508
|
+
AND id IN (SELECT UNNEST($2::uuid[]))
|
|
522
509
|
AND state < '${states.completed}'
|
|
523
510
|
RETURNING 1
|
|
524
511
|
)
|
|
@@ -532,7 +519,8 @@ function resumeJobs (schema) {
|
|
|
532
519
|
UPDATE ${schema}.job
|
|
533
520
|
SET completedOn = NULL,
|
|
534
521
|
state = '${states.created}'
|
|
535
|
-
WHERE
|
|
522
|
+
WHERE name = $1
|
|
523
|
+
AND id IN (SELECT UNNEST($2::uuid[]))
|
|
536
524
|
RETURNING 1
|
|
537
525
|
)
|
|
538
526
|
SELECT COUNT(*) from results
|
|
@@ -754,5 +742,5 @@ function getArchivedJobById (schema) {
|
|
|
754
742
|
}
|
|
755
743
|
|
|
756
744
|
function getJobByTableAndId (schema, table) {
|
|
757
|
-
return `SELECT * FROM ${schema}.${table} WHERE id = $
|
|
745
|
+
return `SELECT * FROM ${schema}.${table} WHERE name = $1 AND id = $2`
|
|
758
746
|
}
|
package/src/timekeeper.js
CHANGED
|
@@ -6,7 +6,7 @@ const pMap = require('p-map')
|
|
|
6
6
|
|
|
7
7
|
const queues = {
|
|
8
8
|
CRON: '__pgboss__cron',
|
|
9
|
-
SEND_IT: '
|
|
9
|
+
SEND_IT: '__pgboss__send_it'
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
const events = {
|
|
@@ -52,6 +52,9 @@ class Timekeeper extends EventEmitter {
|
|
|
52
52
|
// cache the clock skew from the db server
|
|
53
53
|
await this.cacheClockSkew()
|
|
54
54
|
|
|
55
|
+
await this.manager.createQueue(queues.CRON)
|
|
56
|
+
await this.manager.createQueue(queues.SEND_IT)
|
|
57
|
+
|
|
55
58
|
await this.manager.work(queues.CRON, { newJobCheckIntervalSeconds: this.config.cronWorkerIntervalSeconds }, (job) => this.onCron(job))
|
|
56
59
|
await this.manager.work(queues.SEND_IT, { newJobCheckIntervalSeconds: this.config.cronWorkerIntervalSeconds, teamSize: 50, teamConcurrency: 5 }, (job) => this.onSendIt(job))
|
|
57
60
|
|
package/src/worker.js
CHANGED
|
@@ -74,7 +74,7 @@ class Worker {
|
|
|
74
74
|
|
|
75
75
|
this.lastJobDuration = duration
|
|
76
76
|
|
|
77
|
-
if (!this.stopping && !this.beenNotified && duration
|
|
77
|
+
if (!this.stopping && !this.beenNotified && (this.interval - duration > 500)) {
|
|
78
78
|
this.loopDelayPromise = delay(this.interval - duration)
|
|
79
79
|
await this.loopDelayPromise
|
|
80
80
|
this.loopDelayPromise = null
|
package/types.d.ts
CHANGED
|
@@ -331,22 +331,22 @@ declare class PgBoss extends EventEmitter {
|
|
|
331
331
|
fetch<T>(name: string, batchSize: number, options: PgBoss.FetchOptions & { includeMetadata: true }): Promise<PgBoss.JobWithMetadata<T>[] | null>;
|
|
332
332
|
fetch<T>(name: string, batchSize: number, options: PgBoss.FetchOptions): Promise<PgBoss.Job<T>[] | null>;
|
|
333
333
|
|
|
334
|
-
cancel(id: string, options?: PgBoss.ConnectionOptions): Promise<void>;
|
|
335
|
-
cancel(ids: string[], options?: PgBoss.ConnectionOptions): Promise<void>;
|
|
334
|
+
cancel(name: string, id: string, options?: PgBoss.ConnectionOptions): Promise<void>;
|
|
335
|
+
cancel(name: string, ids: string[], options?: PgBoss.ConnectionOptions): Promise<void>;
|
|
336
336
|
|
|
337
|
-
resume(id: string, options?: PgBoss.ConnectionOptions): Promise<void>;
|
|
338
|
-
resume(ids: string[], options?: PgBoss.ConnectionOptions): Promise<void>;
|
|
337
|
+
resume(name: string, id: string, options?: PgBoss.ConnectionOptions): Promise<void>;
|
|
338
|
+
resume(name: string, ids: string[], options?: PgBoss.ConnectionOptions): Promise<void>;
|
|
339
339
|
|
|
340
|
-
complete(id: string, options?: PgBoss.ConnectionOptions): Promise<void>;
|
|
341
|
-
complete(id: string, data: object, options?: PgBoss.ConnectionOptions): Promise<void>;
|
|
342
|
-
complete(ids: string[], options?: PgBoss.ConnectionOptions): Promise<void>;
|
|
340
|
+
complete(name: string, id: string, options?: PgBoss.ConnectionOptions): Promise<void>;
|
|
341
|
+
complete(name: string, id: string, data: object, options?: PgBoss.ConnectionOptions): Promise<void>;
|
|
342
|
+
complete(name: string, ids: string[], options?: PgBoss.ConnectionOptions): Promise<void>;
|
|
343
343
|
|
|
344
|
-
fail(id: string, options?: PgBoss.ConnectionOptions): Promise<void>;
|
|
345
|
-
fail(id: string, data: object, options?: PgBoss.ConnectionOptions): Promise<void>;
|
|
346
|
-
fail(ids: string[], options?: PgBoss.ConnectionOptions): Promise<void>;
|
|
344
|
+
fail(name: string, id: string, options?: PgBoss.ConnectionOptions): Promise<void>;
|
|
345
|
+
fail(name: string, id: string, data: object, options?: PgBoss.ConnectionOptions): Promise<void>;
|
|
346
|
+
fail(name: string, ids: string[], options?: PgBoss.ConnectionOptions): Promise<void>;
|
|
347
347
|
|
|
348
348
|
getQueueSize(name: string, options?: object): Promise<number>;
|
|
349
|
-
getJobById(id: string, options?: PgBoss.ConnectionOptions): Promise<PgBoss.JobWithMetadata | null>;
|
|
349
|
+
getJobById(name: string, id: string, options?: PgBoss.ConnectionOptions): Promise<PgBoss.JobWithMetadata | null>;
|
|
350
350
|
|
|
351
351
|
createQueue(name: string, policy: 'standard' | 'short' | 'singleton' | 'stately'): Promise<void>;
|
|
352
352
|
deleteQueue(name: string): Promise<void>;
|