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/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
- monitorStates: 'monitor-states',
8
- maintenance: 'maintenance'
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.db = db
16
- this.config = config
17
- this.manager = config.manager
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.expire,
33
- this.archive,
34
- this.drop,
35
- this.countStates,
36
- this.maintain
32
+ this.supervise
37
33
  ]
38
- }
39
34
 
40
- async supervise () {
41
- this.maintenanceInterval = setInterval(() => this.onSupervise(), this.maintenanceIntervalSeconds * 1000)
42
- }
35
+ if (config.warningSlowQuerySeconds) {
36
+ WARNINGS.SLOW_QUERY.seconds = config.warningSlowQuerySeconds
37
+ }
43
38
 
44
- async monitor () {
45
- this.monitorInterval = setInterval(() => this.onMonitor(), this.monitorStateIntervalSeconds * 1000)
39
+ if (config.warningQueueSize) {
40
+ WARNINGS.LARGE_QUEUE.size = config.warningQueueSize
41
+ }
46
42
  }
47
43
 
48
- async onMonitor () {
49
- try {
50
- if (this.monitoring) {
51
- return
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
- this.monitoring = true
51
+ async stop () {
52
+ if (!this.#stopped) {
53
+ if (this.#superviseInterval) clearInterval(this.#superviseInterval)
54
+ this.#stopped = true
55
+ }
56
+ }
55
57
 
56
- if (this.config.__test__delay_monitor) {
57
- await delay(this.config.__test__delay_monitor)
58
- }
58
+ async #executeSql (sql, values) {
59
+ const started = Date.now()
59
60
 
60
- if (this.config.__test__throw_monitor) {
61
- throw new Error(this.config.__test__throw_monitor)
62
- }
61
+ const result = await this.#db.executeSql(sql, values)
63
62
 
64
- if (this.stopped) {
65
- return
66
- }
63
+ const ended = Date.now()
67
64
 
68
- const { rows } = await this.db.executeSql(this.trySetMonitorTimeCommand, [this.config.monitorStateIntervalSeconds])
65
+ const elapsed = (ended - started) / 1000
69
66
 
70
- if (rows.length === 1 && !this.stopped) {
71
- const states = await this.countStates()
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.maintaining) {
84
- return
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.maintaining = true
80
+ this.#maintaining = true
88
81
 
89
- if (this.config.__test__delay_maintenance && !this.stopped) {
90
- this.__testDelayPromise = delay(this.config.__test__delay_maintenance)
91
- await this.__testDelayPromise
92
- }
82
+ const queues = await this.#manager.getQueues()
93
83
 
94
- if (this.config.__test__throw_maint) {
95
- throw new Error(this.config.__test__throw_maint)
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.maintaining = false
90
+ this.#maintaining = false
112
91
  }
113
92
  }
114
93
 
115
- async maintain () {
116
- const started = Date.now()
94
+ async supervise (value) {
95
+ let queues
117
96
 
118
- !this.stopped && await this.expire()
119
- !this.stopped && await this.archive()
120
- !this.stopped && await this.drop()
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 ended = Date.now()
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
- return { ms: ended - started }
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
- async stop () {
128
- if (!this.stopped) {
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
- this.stopped = true
119
+ await this.#monitor(table, chunk)
120
+ await this.#maintain(table, chunk)
121
+ }
134
122
  }
135
123
  }
136
124
 
137
- async countStates () {
138
- const stateCountDefault = { ...plans.JOB_STATES }
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
- for (const key of Object.keys(stateCountDefault)) {
141
- stateCountDefault[key] = 0
142
- }
129
+ if (rows.length) {
130
+ const queues = rows.map(q => q.name)
143
131
 
144
- const counts = await this.db.executeSql(this.countStatesCommand)
132
+ const cacheStatsSql = plans.cacheQueueStats(this.#config.schema, table, queues)
133
+ const results = await this.#executeSql(cacheStatsSql)
145
134
 
146
- const states = counts.rows.reduce((acc, item) => {
147
- if (item.name) {
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 queue = item.name ? acc.queues[item.name] : acc
152
- const state = item.state || 'all'
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
- async expire () {
164
- await this.db.executeSql(this.failJobsByTimeoutCommand)
142
+ const sql = plans.failJobsByTimeout(this.#config.schema, table, queues)
143
+ await this.#executeSql(sql)
144
+ }
165
145
  }
166
146
 
167
- async archive () {
168
- await this.db.executeSql(this.archiveCommand)
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
- async drop () {
172
- await this.db.executeSql(this.dropCommand)
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
@@ -6,8 +6,11 @@ class Db extends EventEmitter {
6
6
  super()
7
7
 
8
8
  config.application_name = config.application_name || 'pgboss'
9
+ // config.maxUses = config.maxUses || 1000
9
10
 
10
11
  this.config = config
12
+ this._pgbdb = true
13
+ this.opened = false
11
14
  }
12
15
 
13
16
  events = {
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.isOurs) {
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
- const db = new Db(this.#config)
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.isOurs && !this.#db.opened) {
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.supervise()
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.isOurs && this.#db.opened && close) {
165
+ if (this.#db._pgbdb && this.#db.opened && close) {
172
166
  await this.#db.close()
173
167
  }
174
168