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/README.md +3 -3
- package/docker-compose.yaml +1 -1
- package/package.json +5 -5
- package/src/attorney.js +69 -220
- package/src/boss.js +100 -117
- package/src/db.js +3 -0
- package/src/index.js +6 -12
- package/src/manager.js +225 -191
- package/src/migrationStore.js +0 -88
- package/src/plans.js +469 -446
- package/src/timekeeper.js +46 -40
- package/src/tools.js +19 -2
- package/types.d.ts +81 -138
- package/version.json +1 -1
package/src/boss.js
CHANGED
|
@@ -1,175 +1,158 @@
|
|
|
1
1
|
const EventEmitter = require('node:events')
|
|
2
2
|
const plans = require('./plans')
|
|
3
|
-
const { delay } = require('./tools')
|
|
4
3
|
|
|
5
4
|
const events = {
|
|
6
5
|
error: 'error',
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
warning: 'warning'
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const WARNINGS = {
|
|
10
|
+
SLOW_QUERY: { seconds: 30, message: 'Warning: slow query. Your queues and/or database server should be reviewed' },
|
|
11
|
+
LARGE_QUEUE: { size: 10_000, mesasge: 'Warning: large queue backlog. Your queue should be reviewed' }
|
|
9
12
|
}
|
|
10
13
|
|
|
11
14
|
class Boss extends EventEmitter {
|
|
15
|
+
#stopped
|
|
16
|
+
#maintaining
|
|
17
|
+
#superviseInterval
|
|
18
|
+
#db
|
|
19
|
+
#config
|
|
20
|
+
#manager
|
|
21
|
+
|
|
12
22
|
constructor (db, config) {
|
|
13
23
|
super()
|
|
14
24
|
|
|
15
|
-
this
|
|
16
|
-
this
|
|
17
|
-
this
|
|
18
|
-
|
|
19
|
-
this.maintenanceIntervalSeconds = config.maintenanceIntervalSeconds
|
|
20
|
-
this.monitorStateIntervalSeconds = config.monitorStateIntervalSeconds
|
|
25
|
+
this.#db = db
|
|
26
|
+
this.#config = config
|
|
27
|
+
this.#manager = config.manager
|
|
28
|
+
this.#stopped = true
|
|
21
29
|
|
|
22
30
|
this.events = events
|
|
23
|
-
|
|
24
|
-
this.failJobsByTimeoutCommand = plans.locked(config.schema, plans.failJobsByTimeout(config.schema))
|
|
25
|
-
this.archiveCommand = plans.locked(config.schema, plans.archive(config.schema, config.archiveInterval, config.archiveFailedInterval))
|
|
26
|
-
this.dropCommand = plans.locked(config.schema, plans.drop(config.schema, config.deleteAfter))
|
|
27
|
-
this.trySetMaintenanceTimeCommand = plans.trySetMaintenanceTime(config.schema)
|
|
28
|
-
this.trySetMonitorTimeCommand = plans.trySetMonitorTime(config.schema)
|
|
29
|
-
this.countStatesCommand = plans.countStates(config.schema)
|
|
30
|
-
|
|
31
31
|
this.functions = [
|
|
32
|
-
this.
|
|
33
|
-
this.archive,
|
|
34
|
-
this.drop,
|
|
35
|
-
this.countStates,
|
|
36
|
-
this.maintain
|
|
32
|
+
this.supervise
|
|
37
33
|
]
|
|
38
|
-
}
|
|
39
34
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
35
|
+
if (config.warningSlowQuerySeconds) {
|
|
36
|
+
WARNINGS.SLOW_QUERY.seconds = config.warningSlowQuerySeconds
|
|
37
|
+
}
|
|
43
38
|
|
|
44
|
-
|
|
45
|
-
|
|
39
|
+
if (config.warningQueueSize) {
|
|
40
|
+
WARNINGS.LARGE_QUEUE.size = config.warningQueueSize
|
|
41
|
+
}
|
|
46
42
|
}
|
|
47
43
|
|
|
48
|
-
async
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
44
|
+
async start () {
|
|
45
|
+
if (this.#stopped) {
|
|
46
|
+
this.#superviseInterval = setInterval(() => this.#onSupervise(), this.#config.superviseIntervalSeconds * 1000)
|
|
47
|
+
this.#stopped = false
|
|
48
|
+
}
|
|
49
|
+
}
|
|
53
50
|
|
|
54
|
-
|
|
51
|
+
async stop () {
|
|
52
|
+
if (!this.#stopped) {
|
|
53
|
+
if (this.#superviseInterval) clearInterval(this.#superviseInterval)
|
|
54
|
+
this.#stopped = true
|
|
55
|
+
}
|
|
56
|
+
}
|
|
55
57
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
58
|
+
async #executeSql (sql, values) {
|
|
59
|
+
const started = Date.now()
|
|
59
60
|
|
|
60
|
-
|
|
61
|
-
throw new Error(this.config.__test__throw_monitor)
|
|
62
|
-
}
|
|
61
|
+
const result = await this.#db.executeSql(sql, values)
|
|
63
62
|
|
|
64
|
-
|
|
65
|
-
return
|
|
66
|
-
}
|
|
63
|
+
const ended = Date.now()
|
|
67
64
|
|
|
68
|
-
|
|
65
|
+
const elapsed = (ended - started) / 1000
|
|
69
66
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
this.emit(events.monitorStates, states)
|
|
73
|
-
}
|
|
74
|
-
} catch (err) {
|
|
75
|
-
this.emit(events.error, err)
|
|
76
|
-
} finally {
|
|
77
|
-
this.monitoring = false
|
|
67
|
+
if (elapsed > WARNINGS.SLOW_QUERY.seconds || this.#config.__test__warn_slow_query) {
|
|
68
|
+
this.emit(events.warning, { message: WARNINGS.SLOW_QUERY.message, data: { elapsed, sql, values } })
|
|
78
69
|
}
|
|
70
|
+
|
|
71
|
+
return result
|
|
79
72
|
}
|
|
80
73
|
|
|
81
|
-
async onSupervise () {
|
|
74
|
+
async #onSupervise () {
|
|
82
75
|
try {
|
|
83
|
-
if (this
|
|
84
|
-
|
|
85
|
-
|
|
76
|
+
if (this.#stopped) return
|
|
77
|
+
if (this.#maintaining) return
|
|
78
|
+
if (this.#config.__test__throw_maint) throw new Error(this.#config.__test__throw_maint)
|
|
86
79
|
|
|
87
|
-
this
|
|
80
|
+
this.#maintaining = true
|
|
88
81
|
|
|
89
|
-
|
|
90
|
-
this.__testDelayPromise = delay(this.config.__test__delay_maintenance)
|
|
91
|
-
await this.__testDelayPromise
|
|
92
|
-
}
|
|
82
|
+
const queues = await this.#manager.getQueues()
|
|
93
83
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (this.stopped) {
|
|
99
|
-
return
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const { rows } = await this.db.executeSql(this.trySetMaintenanceTimeCommand, [this.config.maintenanceIntervalSeconds])
|
|
103
|
-
|
|
104
|
-
if (rows.length === 1 && !this.stopped) {
|
|
105
|
-
const result = await this.maintain()
|
|
106
|
-
this.emit(events.maintenance, result)
|
|
84
|
+
for (const queue of queues) {
|
|
85
|
+
!this.#stopped && await this.supervise(queue)
|
|
107
86
|
}
|
|
108
87
|
} catch (err) {
|
|
109
88
|
this.emit(events.error, err)
|
|
110
89
|
} finally {
|
|
111
|
-
this
|
|
90
|
+
this.#maintaining = false
|
|
112
91
|
}
|
|
113
92
|
}
|
|
114
93
|
|
|
115
|
-
async
|
|
116
|
-
|
|
94
|
+
async supervise (value) {
|
|
95
|
+
let queues
|
|
117
96
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
97
|
+
if (!value) {
|
|
98
|
+
queues = await this.#manager.getQueues()
|
|
99
|
+
} if (typeof value === 'string') {
|
|
100
|
+
queues = await this.#manager.getQueues(value)
|
|
101
|
+
} else if (typeof value === 'object') {
|
|
102
|
+
queues = [value]
|
|
103
|
+
}
|
|
121
104
|
|
|
122
|
-
const
|
|
105
|
+
const queueGroups = queues.reduce((acc, q) => {
|
|
106
|
+
const { table } = q
|
|
107
|
+
acc[table] = acc[table] || { table, queues: [] }
|
|
108
|
+
acc[table].queues.push(q)
|
|
109
|
+
return acc
|
|
110
|
+
}, {})
|
|
123
111
|
|
|
124
|
-
|
|
125
|
-
|
|
112
|
+
for (const queueGroup of Object.values(queueGroups)) {
|
|
113
|
+
const { table, queues } = queueGroup
|
|
114
|
+
const names = queues.map(i => i.name)
|
|
126
115
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
if (this.__testDelayPromise) this.__testDelayPromise.abort()
|
|
130
|
-
if (this.maintenanceInterval) clearInterval(this.maintenanceInterval)
|
|
131
|
-
if (this.monitorInterval) clearInterval(this.monitorInterval)
|
|
116
|
+
while (names.length) {
|
|
117
|
+
const chunk = names.splice(0, 100)
|
|
132
118
|
|
|
133
|
-
|
|
119
|
+
await this.#monitor(table, chunk)
|
|
120
|
+
await this.#maintain(table, chunk)
|
|
121
|
+
}
|
|
134
122
|
}
|
|
135
123
|
}
|
|
136
124
|
|
|
137
|
-
async
|
|
138
|
-
const
|
|
125
|
+
async #monitor (table, names) {
|
|
126
|
+
const command = plans.trySetQueueMonitorTime(this.#config.schema, names, this.#config.monitorIntervalSeconds)
|
|
127
|
+
const { rows } = await this.#executeSql(command)
|
|
139
128
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
129
|
+
if (rows.length) {
|
|
130
|
+
const queues = rows.map(q => q.name)
|
|
143
131
|
|
|
144
|
-
|
|
132
|
+
const cacheStatsSql = plans.cacheQueueStats(this.#config.schema, table, queues)
|
|
133
|
+
const results = await this.#executeSql(cacheStatsSql)
|
|
145
134
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
acc.queues[item.name] = acc.queues[item.name] || { ...stateCountDefault }
|
|
149
|
-
}
|
|
135
|
+
const inter = results.flatMap(i => i.rows)
|
|
136
|
+
const warnings = inter.filter(i => i.queuedCount > (i.warningQueueSize || WARNINGS.LARGE_QUEUE.size))
|
|
150
137
|
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
// parsing int64 since pg returns it as string
|
|
155
|
-
queue[state] = parseFloat(item.size)
|
|
156
|
-
|
|
157
|
-
return acc
|
|
158
|
-
}, { ...stateCountDefault, queues: {} })
|
|
159
|
-
|
|
160
|
-
return states
|
|
161
|
-
}
|
|
138
|
+
for (const warning of warnings) {
|
|
139
|
+
this.emit(events.warning, { message: WARNINGS.LARGE_QUEUE.mesasge, data: warning })
|
|
140
|
+
}
|
|
162
141
|
|
|
163
|
-
|
|
164
|
-
|
|
142
|
+
const sql = plans.failJobsByTimeout(this.#config.schema, table, queues)
|
|
143
|
+
await this.#executeSql(sql)
|
|
144
|
+
}
|
|
165
145
|
}
|
|
166
146
|
|
|
167
|
-
async
|
|
168
|
-
|
|
169
|
-
|
|
147
|
+
async #maintain (table, names) {
|
|
148
|
+
const command = plans.trySetQueueDeletionTime(this.#config.schema, names, this.#config.maintenanceIntervalSeconds)
|
|
149
|
+
const { rows } = await this.#executeSql(command)
|
|
170
150
|
|
|
171
|
-
|
|
172
|
-
|
|
151
|
+
if (rows.length) {
|
|
152
|
+
const queues = rows.map(q => q.name)
|
|
153
|
+
const sql = plans.deletion(this.#config.schema, table, queues)
|
|
154
|
+
await this.#executeSql(sql)
|
|
155
|
+
}
|
|
173
156
|
}
|
|
174
157
|
}
|
|
175
158
|
|
package/src/db.js
CHANGED
package/src/index.js
CHANGED
|
@@ -51,7 +51,7 @@ class PgBoss extends EventEmitter {
|
|
|
51
51
|
const db = this.getDb()
|
|
52
52
|
this.#db = db
|
|
53
53
|
|
|
54
|
-
if (db.
|
|
54
|
+
if (db._pgbdb) {
|
|
55
55
|
this.#promoteEvents(db)
|
|
56
56
|
}
|
|
57
57
|
|
|
@@ -89,9 +89,7 @@ class PgBoss extends EventEmitter {
|
|
|
89
89
|
return this.#config.db
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
|
|
93
|
-
db.isOurs = true
|
|
94
|
-
return db
|
|
92
|
+
return new Db(this.#config)
|
|
95
93
|
}
|
|
96
94
|
|
|
97
95
|
#promoteEvents (emitter) {
|
|
@@ -113,7 +111,7 @@ class PgBoss extends EventEmitter {
|
|
|
113
111
|
|
|
114
112
|
this.#starting = true
|
|
115
113
|
|
|
116
|
-
if (this.#db.
|
|
114
|
+
if (this.#db._pgbdb && !this.#db.opened) {
|
|
117
115
|
await this.#db.open()
|
|
118
116
|
}
|
|
119
117
|
|
|
@@ -123,14 +121,10 @@ class PgBoss extends EventEmitter {
|
|
|
123
121
|
await this.#contractor.check()
|
|
124
122
|
}
|
|
125
123
|
|
|
126
|
-
this.#manager.start()
|
|
124
|
+
await this.#manager.start()
|
|
127
125
|
|
|
128
126
|
if (this.#config.supervise) {
|
|
129
|
-
await this.#boss.
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (this.#config.monitorStateIntervalSeconds) {
|
|
133
|
-
await this.#boss.monitor()
|
|
127
|
+
await this.#boss.start()
|
|
134
128
|
}
|
|
135
129
|
|
|
136
130
|
if (this.#config.schedule) {
|
|
@@ -168,7 +162,7 @@ class PgBoss extends EventEmitter {
|
|
|
168
162
|
|
|
169
163
|
await this.#manager.failWip()
|
|
170
164
|
|
|
171
|
-
if (this.#db.
|
|
165
|
+
if (this.#db._pgbdb && this.#db.opened && close) {
|
|
172
166
|
await this.#db.close()
|
|
173
167
|
}
|
|
174
168
|
|