pg-boss 10.0.0-beta1 → 10.0.0-beta3
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 +10 -18
- package/src/contractor.js +8 -1
- package/src/db.js +12 -16
- package/src/index.js +81 -60
- package/src/manager.js +31 -42
- package/src/migrationStore.js +0 -161
- package/src/plans.js +63 -34
- package/src/timekeeper.js +3 -0
- package/src/worker.js +1 -1
- package/types.d.ts +12 -12
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-beta3",
|
|
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,8 +8,8 @@ module.exports = {
|
|
|
8
8
|
checkWorkArgs,
|
|
9
9
|
checkFetchArgs,
|
|
10
10
|
warnClockSkew,
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
assertPostgresObjectName,
|
|
12
|
+
assertQueueName
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
const MAX_INTERVAL_HOURS = 24
|
|
@@ -30,8 +30,6 @@ const WARNINGS = {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
function checkQueueArgs (name, options = {}) {
|
|
33
|
-
assertPostgresObjectName(name)
|
|
34
|
-
|
|
35
33
|
assert(!('deadLetter' in options) || (typeof options.deadLetter === 'string'), 'deadLetter must be a string')
|
|
36
34
|
|
|
37
35
|
applyRetryConfig(options)
|
|
@@ -140,24 +138,12 @@ function checkWorkArgs (name, args, defaults) {
|
|
|
140
138
|
function checkFetchArgs (name, batchSize, options) {
|
|
141
139
|
assert(name, 'missing queue name')
|
|
142
140
|
|
|
143
|
-
if (queueNameHasPatternMatch(name)) {
|
|
144
|
-
name = sanitizeQueueNameForFetch(name)
|
|
145
|
-
}
|
|
146
|
-
|
|
147
141
|
assert(!batchSize || (Number.isInteger(batchSize) && batchSize >= 1), 'batchSize must be an integer > 0')
|
|
148
142
|
assert(!('includeMetadata' in options) || typeof options.includeMetadata === 'boolean', 'includeMetadata must be a boolean')
|
|
149
143
|
|
|
150
144
|
return { name }
|
|
151
145
|
}
|
|
152
146
|
|
|
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
147
|
function getConfig (value) {
|
|
162
148
|
assert(value && (typeof value === 'object' || typeof value === 'string'),
|
|
163
149
|
'configuration assert: string or config object is required to connect to postgres')
|
|
@@ -191,8 +177,14 @@ function applySchemaConfig (config) {
|
|
|
191
177
|
function assertPostgresObjectName (name) {
|
|
192
178
|
assert(typeof name === 'string', 'Name must be a string')
|
|
193
179
|
assert(name.length <= 50, 'Name cannot exceed 50 characters')
|
|
194
|
-
assert(!/\W/.test(name), 'Name can only contain alphanumeric characters
|
|
195
|
-
assert(
|
|
180
|
+
assert(!/\W/.test(name), 'Name can only contain alphanumeric characters or underscores')
|
|
181
|
+
assert(!/^\d/.test(name), 'Name cannot start with a number')
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function assertQueueName (name) {
|
|
185
|
+
assert(typeof name === 'string', 'Name must be a string')
|
|
186
|
+
assert(name.length <= 50, 'Name cannot exceed 50 characters')
|
|
187
|
+
assert(/[\w-]/.test(name), 'Name can only contain alphanumeric characters, underscores, or hyphens')
|
|
196
188
|
}
|
|
197
189
|
|
|
198
190
|
function applyArchiveConfig (config) {
|
package/src/contractor.js
CHANGED
|
@@ -21,6 +21,12 @@ class Contractor {
|
|
|
21
21
|
this.config = config
|
|
22
22
|
this.db = db
|
|
23
23
|
this.migrations = this.config.migrations || migrationStore.getAll(this.config.schema)
|
|
24
|
+
|
|
25
|
+
// exported api to index
|
|
26
|
+
this.functions = [
|
|
27
|
+
this.version,
|
|
28
|
+
this.isInstalled
|
|
29
|
+
]
|
|
24
30
|
}
|
|
25
31
|
|
|
26
32
|
async version () {
|
|
@@ -40,7 +46,8 @@ class Contractor {
|
|
|
40
46
|
const version = await this.version()
|
|
41
47
|
|
|
42
48
|
if (schemaVersion > version) {
|
|
43
|
-
|
|
49
|
+
throw new Error('Migrations are not supported to v10')
|
|
50
|
+
// await this.migrate(version)
|
|
44
51
|
}
|
|
45
52
|
} else {
|
|
46
53
|
await this.create()
|
package/src/db.js
CHANGED
|
@@ -11,6 +11,10 @@ class Db extends EventEmitter {
|
|
|
11
11
|
this.config = config
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
events = {
|
|
15
|
+
error: 'error'
|
|
16
|
+
}
|
|
17
|
+
|
|
14
18
|
async open () {
|
|
15
19
|
this.pool = new pg.Pool(this.config)
|
|
16
20
|
this.pool.on('error', error => this.emit('error', error))
|
|
@@ -26,15 +30,15 @@ class Db extends EventEmitter {
|
|
|
26
30
|
|
|
27
31
|
async executeSql (text, values) {
|
|
28
32
|
if (this.opened) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
33
|
+
if (this.config.debug === true) {
|
|
34
|
+
console.log(`${new Date().toISOString()}: DEBUG SQL`)
|
|
35
|
+
console.log(text)
|
|
32
36
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
if (values) {
|
|
38
|
+
console.log(`${new Date().toISOString()}: DEBUG VALUES`)
|
|
39
|
+
console.log(values)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
38
42
|
|
|
39
43
|
return await this.pool.query(text, values)
|
|
40
44
|
}
|
|
@@ -68,14 +72,6 @@ class Db extends EventEmitter {
|
|
|
68
72
|
|
|
69
73
|
return locker
|
|
70
74
|
}
|
|
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
75
|
}
|
|
80
76
|
|
|
81
77
|
module.exports = Db
|
package/src/index.js
CHANGED
|
@@ -13,6 +13,15 @@ const events = {
|
|
|
13
13
|
stopped: 'stopped'
|
|
14
14
|
}
|
|
15
15
|
class PgBoss extends EventEmitter {
|
|
16
|
+
#stoppingOn
|
|
17
|
+
#stopped
|
|
18
|
+
#config
|
|
19
|
+
#db
|
|
20
|
+
#boss
|
|
21
|
+
#contractor
|
|
22
|
+
#manager
|
|
23
|
+
#timekeeper
|
|
24
|
+
|
|
16
25
|
static getConstructionPlans (schema) {
|
|
17
26
|
return Contractor.constructionPlans(schema)
|
|
18
27
|
}
|
|
@@ -26,60 +35,72 @@ class PgBoss extends EventEmitter {
|
|
|
26
35
|
}
|
|
27
36
|
|
|
28
37
|
constructor (value) {
|
|
29
|
-
const config = Attorney.getConfig(value)
|
|
30
|
-
|
|
31
38
|
super()
|
|
32
39
|
|
|
33
|
-
|
|
40
|
+
this.#stoppingOn = null
|
|
41
|
+
this.#stopped = true
|
|
42
|
+
|
|
43
|
+
const config = Attorney.getConfig(value)
|
|
44
|
+
this.#config = config
|
|
45
|
+
|
|
46
|
+
const db = this.getDb()
|
|
47
|
+
this.#db = db
|
|
34
48
|
|
|
35
49
|
if (db.isOurs) {
|
|
36
|
-
|
|
50
|
+
this.#promoteEvents(db)
|
|
37
51
|
}
|
|
38
52
|
|
|
39
|
-
const
|
|
40
|
-
Object.keys(manager.events).forEach(event => promoteEvent.call(this, manager, manager.events[event]))
|
|
41
|
-
manager.functions.forEach(func => promoteFunction.call(this, manager, func))
|
|
53
|
+
const contractor = new Contractor(db, config)
|
|
42
54
|
|
|
55
|
+
const manager = new Manager(db, config)
|
|
43
56
|
const bossConfig = { ...config, manager }
|
|
44
57
|
|
|
45
58
|
const boss = new Boss(db, bossConfig)
|
|
46
|
-
Object.keys(boss.events).forEach(event => promoteEvent.call(this, boss, boss.events[event]))
|
|
47
|
-
boss.functions.forEach(func => promoteFunction.call(this, boss, func))
|
|
48
59
|
|
|
49
60
|
const timekeeper = new Timekeeper(db, bossConfig)
|
|
50
|
-
Object.keys(timekeeper.events).forEach(event => promoteEvent.call(this, timekeeper, timekeeper.events[event]))
|
|
51
|
-
timekeeper.functions.forEach(func => promoteFunction.call(this, timekeeper, func))
|
|
52
|
-
|
|
53
61
|
manager.timekeeper = timekeeper
|
|
54
62
|
|
|
55
|
-
this
|
|
56
|
-
this
|
|
57
|
-
this
|
|
58
|
-
this.db = db
|
|
59
|
-
this.boss = boss
|
|
60
|
-
this.contractor = new Contractor(db, config)
|
|
61
|
-
this.manager = manager
|
|
62
|
-
this.timekeeper = timekeeper
|
|
63
|
-
|
|
64
|
-
function getDb (config) {
|
|
65
|
-
if (config.db) {
|
|
66
|
-
return config.db
|
|
67
|
-
}
|
|
63
|
+
this.#promoteEvents(manager)
|
|
64
|
+
this.#promoteEvents(boss)
|
|
65
|
+
this.#promoteEvents(timekeeper)
|
|
68
66
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
67
|
+
this.#promoteFunctions(boss)
|
|
68
|
+
this.#promoteFunctions(contractor)
|
|
69
|
+
this.#promoteFunctions(manager)
|
|
70
|
+
this.#promoteFunctions(timekeeper)
|
|
71
|
+
|
|
72
|
+
this.#boss = boss
|
|
73
|
+
this.#contractor = contractor
|
|
74
|
+
this.#manager = manager
|
|
75
|
+
this.#timekeeper = timekeeper
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
getDb () {
|
|
79
|
+
if (this.#db) {
|
|
80
|
+
return this.#db
|
|
72
81
|
}
|
|
73
82
|
|
|
74
|
-
|
|
75
|
-
this
|
|
83
|
+
if (this.#config.db) {
|
|
84
|
+
return this.#config.db
|
|
76
85
|
}
|
|
77
86
|
|
|
78
|
-
|
|
87
|
+
const db = new Db(this.#config)
|
|
88
|
+
db.isOurs = true
|
|
89
|
+
return db
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
#promoteEvents (emitter) {
|
|
93
|
+
for (const event of Object.values(emitter?.events)) {
|
|
79
94
|
emitter.on(event, arg => this.emit(event, arg))
|
|
80
95
|
}
|
|
81
96
|
}
|
|
82
97
|
|
|
98
|
+
#promoteFunctions (obj) {
|
|
99
|
+
for (const func of obj?.functions) {
|
|
100
|
+
this[func.name] = (...args) => func.apply(obj, args)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
83
104
|
async start () {
|
|
84
105
|
if (this.starting || this.started) {
|
|
85
106
|
return
|
|
@@ -87,39 +108,39 @@ class PgBoss extends EventEmitter {
|
|
|
87
108
|
|
|
88
109
|
this.starting = true
|
|
89
110
|
|
|
90
|
-
if (this
|
|
91
|
-
await this
|
|
111
|
+
if (this.#db.isOurs && !this.#db.opened) {
|
|
112
|
+
await this.#db.open()
|
|
92
113
|
}
|
|
93
114
|
|
|
94
|
-
if (this
|
|
95
|
-
await this
|
|
115
|
+
if (this.#config.migrate) {
|
|
116
|
+
await this.#contractor.start()
|
|
96
117
|
} else {
|
|
97
|
-
await this
|
|
118
|
+
await this.#contractor.check()
|
|
98
119
|
}
|
|
99
120
|
|
|
100
|
-
this
|
|
121
|
+
this.#manager.start()
|
|
101
122
|
|
|
102
|
-
if (this
|
|
103
|
-
await this
|
|
123
|
+
if (this.#config.supervise) {
|
|
124
|
+
await this.#boss.supervise()
|
|
104
125
|
}
|
|
105
126
|
|
|
106
|
-
if (this
|
|
107
|
-
await this
|
|
127
|
+
if (this.#config.monitorStateIntervalSeconds) {
|
|
128
|
+
await this.#boss.monitor()
|
|
108
129
|
}
|
|
109
130
|
|
|
110
|
-
if (this
|
|
111
|
-
await this
|
|
131
|
+
if (this.#config.schedule) {
|
|
132
|
+
await this.#timekeeper.start()
|
|
112
133
|
}
|
|
113
134
|
|
|
114
135
|
this.starting = false
|
|
115
136
|
this.started = true
|
|
116
|
-
this
|
|
137
|
+
this.#stopped = false
|
|
117
138
|
|
|
118
139
|
return this
|
|
119
140
|
}
|
|
120
141
|
|
|
121
142
|
async stop (options = {}) {
|
|
122
|
-
if (this
|
|
143
|
+
if (this.#stoppingOn || this.#stopped) {
|
|
123
144
|
return
|
|
124
145
|
}
|
|
125
146
|
|
|
@@ -127,27 +148,27 @@ class PgBoss extends EventEmitter {
|
|
|
127
148
|
|
|
128
149
|
timeout = Math.max(timeout, 1000)
|
|
129
150
|
|
|
130
|
-
this
|
|
151
|
+
this.#stoppingOn = Date.now()
|
|
131
152
|
|
|
132
|
-
await this
|
|
133
|
-
await this
|
|
134
|
-
await this
|
|
153
|
+
await this.#manager.stop()
|
|
154
|
+
await this.#timekeeper.stop()
|
|
155
|
+
await this.#boss.stop()
|
|
135
156
|
|
|
136
157
|
await new Promise((resolve, reject) => {
|
|
137
158
|
const shutdown = async () => {
|
|
138
159
|
try {
|
|
139
|
-
if (this
|
|
140
|
-
throw new Error(this
|
|
160
|
+
if (this.#config.__test__throw_shutdown) {
|
|
161
|
+
throw new Error(this.#config.__test__throw_shutdown)
|
|
141
162
|
}
|
|
142
163
|
|
|
143
|
-
await this
|
|
164
|
+
await this.#manager.failWip()
|
|
144
165
|
|
|
145
|
-
if (this
|
|
146
|
-
await this
|
|
166
|
+
if (this.#db.isOurs && this.#db.opened && destroy) {
|
|
167
|
+
await this.#db.close()
|
|
147
168
|
}
|
|
148
169
|
|
|
149
|
-
this
|
|
150
|
-
this
|
|
170
|
+
this.#stopped = true
|
|
171
|
+
this.#stoppingOn = null
|
|
151
172
|
this.started = false
|
|
152
173
|
|
|
153
174
|
this.emit(events.stopped)
|
|
@@ -168,13 +189,13 @@ class PgBoss extends EventEmitter {
|
|
|
168
189
|
|
|
169
190
|
setImmediate(async () => {
|
|
170
191
|
try {
|
|
171
|
-
if (this
|
|
172
|
-
throw new Error(this
|
|
192
|
+
if (this.#config.__test__throw_stop_monitor) {
|
|
193
|
+
throw new Error(this.#config.__test__throw_stop_monitor)
|
|
173
194
|
}
|
|
174
195
|
|
|
175
|
-
const isWip = () => this
|
|
196
|
+
const isWip = () => this.#manager.getWipData({ includeInternal: false }).length > 0
|
|
176
197
|
|
|
177
|
-
while ((Date.now() - this
|
|
198
|
+
while ((Date.now() - this.#stoppingOn) < timeout && isWip()) {
|
|
178
199
|
await delay(500)
|
|
179
200
|
}
|
|
180
201
|
|
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,37 +512,43 @@ 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
|
|
|
560
547
|
async createQueue (name, options = {}) {
|
|
561
548
|
assert(name, 'Missing queue name argument')
|
|
562
549
|
|
|
550
|
+
Attorney.assertQueueName(name)
|
|
551
|
+
|
|
563
552
|
const { policy = QUEUE_POLICY.standard } = options
|
|
564
553
|
|
|
565
554
|
assert(policy in QUEUE_POLICY, `${policy} is not a valid queue policy`)
|
|
@@ -573,7 +562,7 @@ class Manager extends EventEmitter {
|
|
|
573
562
|
deadLetter
|
|
574
563
|
} = Attorney.checkQueueArgs(name, options)
|
|
575
564
|
|
|
576
|
-
const paritionSql = plans.
|
|
565
|
+
const paritionSql = plans.createPartition(this.config.schema, name)
|
|
577
566
|
|
|
578
567
|
await this.db.executeSql(paritionSql)
|
|
579
568
|
|
|
@@ -659,8 +648,8 @@ class Manager extends EventEmitter {
|
|
|
659
648
|
const result = await this.db.executeSql(queueSql, [name])
|
|
660
649
|
|
|
661
650
|
if (result?.rows?.length) {
|
|
662
|
-
Attorney.
|
|
663
|
-
const sql = plans.
|
|
651
|
+
Attorney.assertQueueName(name)
|
|
652
|
+
const sql = plans.dropPartition(this.config.schema, name)
|
|
664
653
|
await this.db.executeSql(sql)
|
|
665
654
|
}
|
|
666
655
|
|
|
@@ -690,15 +679,15 @@ class Manager extends EventEmitter {
|
|
|
690
679
|
return result ? parseFloat(result.rows[0].count) : null
|
|
691
680
|
}
|
|
692
681
|
|
|
693
|
-
async getJobById (id, options = {}) {
|
|
682
|
+
async getJobById (queue, id, options = {}) {
|
|
694
683
|
const db = options.db || this.db
|
|
695
|
-
const result1 = await db.executeSql(this.getJobByIdCommand, [id])
|
|
684
|
+
const result1 = await db.executeSql(this.getJobByIdCommand, [queue, id])
|
|
696
685
|
|
|
697
686
|
if (result1 && result1.rows && result1.rows.length === 1) {
|
|
698
687
|
return result1.rows[0]
|
|
699
688
|
}
|
|
700
689
|
|
|
701
|
-
const result2 = await db.executeSql(this.getArchivedJobByIdCommand, [id])
|
|
690
|
+
const result2 = await db.executeSql(this.getArchivedJobByIdCommand, [queue, id])
|
|
702
691
|
|
|
703
692
|
if (result2 && result2.rows && result2.rows.length === 1) {
|
|
704
693
|
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
|
@@ -46,8 +46,8 @@ module.exports = {
|
|
|
46
46
|
countStates,
|
|
47
47
|
createQueue,
|
|
48
48
|
updateQueue,
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
createPartition,
|
|
50
|
+
dropPartition,
|
|
51
51
|
deleteQueueRecords,
|
|
52
52
|
getQueueByName,
|
|
53
53
|
getQueueSize,
|
|
@@ -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,13 +90,16 @@ 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),
|
|
99
96
|
createTableSchedule(schema),
|
|
100
97
|
createTableSubscription(schema),
|
|
101
98
|
|
|
99
|
+
getPartitionFunction(schema),
|
|
100
|
+
createPartitionFunction(schema),
|
|
101
|
+
dropPartitionFunction(schema),
|
|
102
|
+
|
|
102
103
|
insertVersion(schema, version)
|
|
103
104
|
]
|
|
104
105
|
|
|
@@ -165,24 +166,53 @@ function createTableJob (schema) {
|
|
|
165
166
|
`
|
|
166
167
|
}
|
|
167
168
|
|
|
168
|
-
function
|
|
169
|
-
return `
|
|
169
|
+
function createPartition (schema, name) {
|
|
170
|
+
return `SELECT ${schema}.create_partition('${name}');`
|
|
170
171
|
}
|
|
171
172
|
|
|
172
|
-
function
|
|
173
|
-
return `
|
|
173
|
+
function getPartitionFunction (schema) {
|
|
174
|
+
return `
|
|
175
|
+
CREATE FUNCTION ${schema}.get_partition(queue_name text, out name text) AS
|
|
176
|
+
$$
|
|
177
|
+
SELECT '${schema}.j' || encode(digest(queue_name, 'sha1'), 'hex');
|
|
178
|
+
$$
|
|
179
|
+
LANGUAGE SQL
|
|
180
|
+
IMMUTABLE
|
|
181
|
+
`
|
|
174
182
|
}
|
|
175
183
|
|
|
176
|
-
function
|
|
184
|
+
function createPartitionFunction (schema) {
|
|
177
185
|
return `
|
|
178
|
-
CREATE
|
|
179
|
-
|
|
180
|
-
|
|
186
|
+
CREATE FUNCTION ${schema}.create_partition(queue_name text)
|
|
187
|
+
RETURNS VOID AS
|
|
188
|
+
$$
|
|
189
|
+
DECLARE
|
|
190
|
+
table_name varchar := ${schema}.get_partition(queue_name);
|
|
191
|
+
BEGIN
|
|
192
|
+
EXECUTE format('CREATE TABLE %I (LIKE ${schema}.job INCLUDING DEFAULTS INCLUDING CONSTRAINTS)', table_name);
|
|
193
|
+
EXECUTE format('ALTER TABLE %I ADD CHECK (name=%L)', table_name, queue_name);
|
|
194
|
+
EXECUTE format('ALTER TABLE ${schema}.job ATTACH PARTITION %I FOR VALUES IN (%L)', table_name, queue_name);
|
|
195
|
+
END;
|
|
196
|
+
$$
|
|
197
|
+
LANGUAGE plpgsql;
|
|
181
198
|
`
|
|
182
199
|
}
|
|
183
200
|
|
|
184
|
-
function
|
|
185
|
-
return `
|
|
201
|
+
function dropPartitionFunction (schema) {
|
|
202
|
+
return `
|
|
203
|
+
CREATE FUNCTION ${schema}.drop_partition(queue_name text)
|
|
204
|
+
RETURNS VOID AS
|
|
205
|
+
$$
|
|
206
|
+
BEGIN
|
|
207
|
+
EXECUTE format('DROP TABLE IF EXISTS %I', ${schema}.get_partition(queue_name));
|
|
208
|
+
END;
|
|
209
|
+
$$
|
|
210
|
+
LANGUAGE plpgsql;
|
|
211
|
+
`
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function dropPartition (schema, name) {
|
|
215
|
+
return `SELECT ${schema}.drop_partition('${name}');`
|
|
186
216
|
}
|
|
187
217
|
|
|
188
218
|
function createPrimaryKeyArchive (schema) {
|
|
@@ -210,21 +240,17 @@ function createIndexJobThrottleKey (schema) {
|
|
|
210
240
|
}
|
|
211
241
|
|
|
212
242
|
function createIndexJobName (schema) {
|
|
213
|
-
return `CREATE INDEX job_name ON ${schema}.job (name
|
|
243
|
+
return `CREATE INDEX job_name ON ${schema}.job (name)`
|
|
214
244
|
}
|
|
215
245
|
|
|
216
246
|
function createIndexJobFetch (schema) {
|
|
217
|
-
return `CREATE INDEX job_fetch ON ${schema}.job (name
|
|
247
|
+
return `CREATE INDEX job_fetch ON ${schema}.job (name, startAfter) INCLUDE (priority, createdOn, id) WHERE state < '${states.active}'`
|
|
218
248
|
}
|
|
219
249
|
|
|
220
250
|
function createTableArchive (schema) {
|
|
221
251
|
return `CREATE TABLE ${schema}.archive (LIKE ${schema}.job)`
|
|
222
252
|
}
|
|
223
253
|
|
|
224
|
-
function createArchiveBackupTable (schema) {
|
|
225
|
-
return `CREATE TABLE ${schema}.archive_backup (LIKE ${schema}.job)`
|
|
226
|
-
}
|
|
227
|
-
|
|
228
254
|
function createColumnArchiveArchivedOn (schema) {
|
|
229
255
|
return `ALTER TABLE ${schema}.archive ADD archivedOn timestamptz NOT NULL DEFAULT now()`
|
|
230
256
|
}
|
|
@@ -422,12 +448,12 @@ function insertVersion (schema, version) {
|
|
|
422
448
|
}
|
|
423
449
|
|
|
424
450
|
function fetchNextJob (schema) {
|
|
425
|
-
return ({ includeMetadata,
|
|
451
|
+
return ({ includeMetadata, priority = true } = {}) => `
|
|
426
452
|
WITH next as (
|
|
427
453
|
SELECT id
|
|
428
454
|
FROM ${schema}.job
|
|
429
|
-
WHERE
|
|
430
|
-
AND
|
|
455
|
+
WHERE name = $1
|
|
456
|
+
AND state < '${states.active}'
|
|
431
457
|
AND startAfter < now()
|
|
432
458
|
ORDER BY ${priority && 'priority desc, '} createdOn, id
|
|
433
459
|
LIMIT $2
|
|
@@ -438,7 +464,7 @@ function fetchNextJob (schema) {
|
|
|
438
464
|
startedOn = now(),
|
|
439
465
|
retryCount = CASE WHEN startedOn IS NOT NULL THEN retryCount + 1 ELSE retryCount END
|
|
440
466
|
FROM next
|
|
441
|
-
WHERE j.id = next.id
|
|
467
|
+
WHERE name = $1 AND j.id = next.id
|
|
442
468
|
RETURNING ${includeMetadata ? 'j.*' : 'j.id, name, data'},
|
|
443
469
|
EXTRACT(epoch FROM expireIn) as expire_in_seconds
|
|
444
470
|
`
|
|
@@ -450,8 +476,9 @@ function completeJobs (schema) {
|
|
|
450
476
|
UPDATE ${schema}.job
|
|
451
477
|
SET completedOn = now(),
|
|
452
478
|
state = '${states.completed}',
|
|
453
|
-
output = $
|
|
454
|
-
WHERE
|
|
479
|
+
output = $3::jsonb
|
|
480
|
+
WHERE name = $1
|
|
481
|
+
AND id IN (SELECT UNNEST($2::uuid[]))
|
|
455
482
|
AND state = '${states.active}'
|
|
456
483
|
RETURNING *
|
|
457
484
|
)
|
|
@@ -460,8 +487,8 @@ function completeJobs (schema) {
|
|
|
460
487
|
}
|
|
461
488
|
|
|
462
489
|
function failJobsById (schema) {
|
|
463
|
-
const where = `id IN (SELECT UNNEST($
|
|
464
|
-
const output = '$
|
|
490
|
+
const where = `name = $1 AND id IN (SELECT UNNEST($2::uuid[])) AND state < '${states.completed}'`
|
|
491
|
+
const output = '$3::jsonb'
|
|
465
492
|
|
|
466
493
|
return failJobs(schema, where, output)
|
|
467
494
|
}
|
|
@@ -518,7 +545,8 @@ function cancelJobs (schema) {
|
|
|
518
545
|
UPDATE ${schema}.job
|
|
519
546
|
SET completedOn = now(),
|
|
520
547
|
state = '${states.cancelled}'
|
|
521
|
-
WHERE
|
|
548
|
+
WHERE name = $1
|
|
549
|
+
AND id IN (SELECT UNNEST($2::uuid[]))
|
|
522
550
|
AND state < '${states.completed}'
|
|
523
551
|
RETURNING 1
|
|
524
552
|
)
|
|
@@ -532,7 +560,8 @@ function resumeJobs (schema) {
|
|
|
532
560
|
UPDATE ${schema}.job
|
|
533
561
|
SET completedOn = NULL,
|
|
534
562
|
state = '${states.created}'
|
|
535
|
-
WHERE
|
|
563
|
+
WHERE name = $1
|
|
564
|
+
AND id IN (SELECT UNNEST($2::uuid[]))
|
|
536
565
|
RETURNING 1
|
|
537
566
|
)
|
|
538
567
|
SELECT COUNT(*) from results
|
|
@@ -735,8 +764,8 @@ function locked (schema, query) {
|
|
|
735
764
|
}
|
|
736
765
|
|
|
737
766
|
function advisoryLock (schema, key) {
|
|
738
|
-
return `SELECT pg_advisory_xact_lock(
|
|
739
|
-
('x' ||
|
|
767
|
+
return `SELECT pg_advisory_xact_lock(
|
|
768
|
+
('x' || encode(digest(current_database() || '.pgboss.${schema}${key || ''}', 'sha256'), 'hex'))::bit(64)::bigint
|
|
740
769
|
)`
|
|
741
770
|
}
|
|
742
771
|
|
|
@@ -754,5 +783,5 @@ function getArchivedJobById (schema) {
|
|
|
754
783
|
}
|
|
755
784
|
|
|
756
785
|
function getJobByTableAndId (schema, table) {
|
|
757
|
-
return `SELECT * FROM ${schema}.${table} WHERE id = $
|
|
786
|
+
return `SELECT * FROM ${schema}.${table} WHERE name = $1 AND id = $2`
|
|
758
787
|
}
|
package/src/timekeeper.js
CHANGED
|
@@ -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,24 +331,24 @@ 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
|
-
createQueue(name: string, policy: 'standard' | 'short' | 'singleton' | 'stately'): Promise<void>;
|
|
351
|
+
createQueue(name: string, options?: { policy: 'standard' | 'short' | 'singleton' | 'stately' }): Promise<void>;
|
|
352
352
|
deleteQueue(name: string): Promise<void>;
|
|
353
353
|
purgeQueue(name: string): Promise<void>;
|
|
354
354
|
clearStorage(): Promise<void>;
|