pg-boss 9.0.3 → 10.0.0-beta10
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 +19 -40
- package/package.json +7 -11
- package/src/attorney.js +81 -105
- package/src/boss.js +88 -139
- package/src/contractor.js +25 -4
- package/src/db.js +14 -8
- package/src/index.js +128 -93
- package/src/manager.js +222 -142
- package/src/migrationStore.js +0 -105
- package/src/plans.js +437 -361
- package/src/timekeeper.js +45 -82
- package/src/tools.js +28 -0
- package/src/worker.js +4 -4
- package/types.d.ts +76 -70
- package/version.json +1 -1
package/src/plans.js
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
const
|
|
1
|
+
const DEFAULT_SCHEMA = 'pgboss'
|
|
2
|
+
const MIGRATE_RACE_MESSAGE = 'division by zero'
|
|
3
|
+
const CREATE_RACE_MESSAGE = 'already exists'
|
|
2
4
|
|
|
3
|
-
const
|
|
5
|
+
const JOB_STATES = Object.freeze({
|
|
4
6
|
created: 'created',
|
|
5
7
|
retry: 'retry',
|
|
6
8
|
active: 'active',
|
|
7
9
|
completed: 'completed',
|
|
8
|
-
expired: 'expired',
|
|
9
10
|
cancelled: 'cancelled',
|
|
10
11
|
failed: 'failed'
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const DEFAULT_SCHEMA = 'pgboss'
|
|
14
|
-
const COMPLETION_JOB_PREFIX = `__state__${states.completed}__`
|
|
15
|
-
const SINGLETON_QUEUE_KEY = '__pgboss__singleton_queue'
|
|
16
|
-
const SINGLETON_QUEUE_KEY_ESCAPED = SINGLETON_QUEUE_KEY.replace(/_/g, '\\_')
|
|
12
|
+
})
|
|
17
13
|
|
|
18
|
-
const
|
|
19
|
-
|
|
14
|
+
const QUEUE_POLICIES = Object.freeze({
|
|
15
|
+
standard: 'standard',
|
|
16
|
+
short: 'short',
|
|
17
|
+
singleton: 'singleton',
|
|
18
|
+
stately: 'stately'
|
|
19
|
+
})
|
|
20
20
|
|
|
21
21
|
module.exports = {
|
|
22
22
|
create,
|
|
@@ -28,7 +28,8 @@ module.exports = {
|
|
|
28
28
|
completeJobs,
|
|
29
29
|
cancelJobs,
|
|
30
30
|
resumeJobs,
|
|
31
|
-
|
|
31
|
+
failJobsById,
|
|
32
|
+
failJobsByTimeout,
|
|
32
33
|
insertJob,
|
|
33
34
|
insertJobs,
|
|
34
35
|
getTime,
|
|
@@ -38,62 +39,61 @@ module.exports = {
|
|
|
38
39
|
subscribe,
|
|
39
40
|
unsubscribe,
|
|
40
41
|
getQueuesForEvent,
|
|
41
|
-
expire,
|
|
42
42
|
archive,
|
|
43
|
-
|
|
43
|
+
drop,
|
|
44
44
|
countStates,
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
createQueue,
|
|
46
|
+
updateQueue,
|
|
47
|
+
createPartition,
|
|
48
|
+
dropPartition,
|
|
49
|
+
deleteQueueRecords,
|
|
50
|
+
getQueueByName,
|
|
48
51
|
getQueueSize,
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
purgeQueue,
|
|
53
|
+
clearStorage,
|
|
54
|
+
trySetMaintenanceTime,
|
|
55
|
+
trySetMonitorTime,
|
|
56
|
+
trySetCronTime,
|
|
53
57
|
locked,
|
|
54
58
|
assertMigration,
|
|
55
59
|
getArchivedJobById,
|
|
56
60
|
getJobById,
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
SINGLETON_QUEUE_KEY,
|
|
61
|
+
QUEUE_POLICIES,
|
|
62
|
+
JOB_STATES,
|
|
60
63
|
MIGRATE_RACE_MESSAGE,
|
|
61
64
|
CREATE_RACE_MESSAGE,
|
|
62
65
|
DEFAULT_SCHEMA
|
|
63
66
|
}
|
|
64
67
|
|
|
65
|
-
|
|
66
|
-
if (Array.isArray(query)) {
|
|
67
|
-
query = query.join(';\n')
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return `
|
|
71
|
-
BEGIN;
|
|
72
|
-
SET LOCAL statement_timeout = '30s';
|
|
73
|
-
${advisoryLock(schema)};
|
|
74
|
-
${query};
|
|
75
|
-
COMMIT;
|
|
76
|
-
`
|
|
77
|
-
}
|
|
68
|
+
const assert = require('assert')
|
|
78
69
|
|
|
79
70
|
function create (schema, version) {
|
|
80
71
|
const commands = [
|
|
81
72
|
createSchema(schema),
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
cloneJobTableForArchive(schema),
|
|
86
|
-
createScheduleTable(schema),
|
|
87
|
-
createSubscriptionTable(schema),
|
|
88
|
-
addIdIndexToArchive(schema),
|
|
89
|
-
addArchivedOnToArchive(schema),
|
|
90
|
-
addArchivedOnIndexToArchive(schema),
|
|
91
|
-
createIndexJobName(schema),
|
|
73
|
+
createEnumJobState(schema),
|
|
74
|
+
|
|
75
|
+
createTableJob(schema),
|
|
92
76
|
createIndexJobFetch(schema),
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
77
|
+
createIndexJobPolicyStately(schema),
|
|
78
|
+
createIndexJobPolicyShort(schema),
|
|
79
|
+
createIndexJobPolicySingleton(schema),
|
|
80
|
+
createIndexJobThrottleOn(schema),
|
|
81
|
+
createIndexJobThrottleKey(schema),
|
|
82
|
+
|
|
83
|
+
createTableArchive(schema),
|
|
84
|
+
createPrimaryKeyArchive(schema),
|
|
85
|
+
createColumnArchiveArchivedOn(schema),
|
|
86
|
+
createIndexArchiveArchivedOn(schema),
|
|
87
|
+
|
|
88
|
+
createTableVersion(schema),
|
|
89
|
+
createTableQueue(schema),
|
|
90
|
+
createTableSchedule(schema),
|
|
91
|
+
createTableSubscription(schema),
|
|
92
|
+
|
|
93
|
+
getPartitionFunction(schema),
|
|
94
|
+
createPartitionFunction(schema),
|
|
95
|
+
dropPartitionFunction(schema),
|
|
96
|
+
|
|
97
97
|
insertVersion(schema, version)
|
|
98
98
|
]
|
|
99
99
|
|
|
@@ -106,154 +106,252 @@ function createSchema (schema) {
|
|
|
106
106
|
`
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
function
|
|
109
|
+
function createTableVersion (schema) {
|
|
110
110
|
return `
|
|
111
111
|
CREATE TABLE ${schema}.version (
|
|
112
112
|
version int primary key,
|
|
113
113
|
maintained_on timestamp with time zone,
|
|
114
|
-
cron_on timestamp with time zone
|
|
114
|
+
cron_on timestamp with time zone,
|
|
115
|
+
monitored_on timestamp with time zone
|
|
115
116
|
)
|
|
116
117
|
`
|
|
117
118
|
}
|
|
118
119
|
|
|
119
|
-
function
|
|
120
|
+
function createEnumJobState (schema) {
|
|
120
121
|
// ENUM definition order is important
|
|
121
122
|
// base type is numeric and first values are less than last values
|
|
122
123
|
return `
|
|
123
124
|
CREATE TYPE ${schema}.job_state AS ENUM (
|
|
124
|
-
'${
|
|
125
|
-
'${
|
|
126
|
-
'${
|
|
127
|
-
'${
|
|
128
|
-
'${
|
|
129
|
-
'${
|
|
130
|
-
'${states.failed}'
|
|
125
|
+
'${JOB_STATES.created}',
|
|
126
|
+
'${JOB_STATES.retry}',
|
|
127
|
+
'${JOB_STATES.active}',
|
|
128
|
+
'${JOB_STATES.completed}',
|
|
129
|
+
'${JOB_STATES.cancelled}',
|
|
130
|
+
'${JOB_STATES.failed}'
|
|
131
131
|
)
|
|
132
132
|
`
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
-
function
|
|
135
|
+
function createTableJob (schema) {
|
|
136
136
|
return `
|
|
137
137
|
CREATE TABLE ${schema}.job (
|
|
138
|
-
id uuid
|
|
138
|
+
id uuid not null default gen_random_uuid(),
|
|
139
139
|
name text not null,
|
|
140
140
|
priority integer not null default(0),
|
|
141
141
|
data jsonb,
|
|
142
|
-
state ${schema}.job_state not null default('${
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
142
|
+
state ${schema}.job_state not null default('${JOB_STATES.created}'),
|
|
143
|
+
retry_limit integer not null default(0),
|
|
144
|
+
retry_count integer not null default(0),
|
|
145
|
+
retry_delay integer not null default(0),
|
|
146
|
+
retry_backoff boolean not null default false,
|
|
147
|
+
start_after timestamp with time zone not null default now(),
|
|
148
|
+
started_on timestamp with time zone,
|
|
149
|
+
singleton_key text,
|
|
150
|
+
singleton_on timestamp without time zone,
|
|
151
|
+
expire_in interval not null default interval '15 minutes',
|
|
152
|
+
created_on timestamp with time zone not null default now(),
|
|
153
|
+
completed_on timestamp with time zone,
|
|
154
|
+
keep_until timestamp with time zone NOT NULL default now() + interval '14 days',
|
|
155
|
+
output jsonb,
|
|
156
|
+
dead_letter text,
|
|
157
|
+
policy text,
|
|
158
|
+
CONSTRAINT job_pkey PRIMARY KEY (name, id)
|
|
159
|
+
) PARTITION BY LIST (name)
|
|
158
160
|
`
|
|
159
161
|
}
|
|
160
162
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
+
const baseJobColumns = 'id, name, data, EXTRACT(epoch FROM expire_in) as "expireInSeconds"'
|
|
164
|
+
const allJobColumns = `${baseJobColumns}, policy, state, priority,
|
|
165
|
+
retry_limit as "retryLimit",
|
|
166
|
+
retry_count as "retryCount",
|
|
167
|
+
retry_delay as "retryDelay",
|
|
168
|
+
retry_backoff as "retryBackoff",
|
|
169
|
+
start_after as "startAfter",
|
|
170
|
+
started_on as "startedOn",
|
|
171
|
+
singleton_key as "singletonKey",
|
|
172
|
+
singleton_on as "singletonOn",
|
|
173
|
+
expire_in as "expireIn",
|
|
174
|
+
created_on as "createdOn",
|
|
175
|
+
completed_on as "completedOn",
|
|
176
|
+
keep_until as "keepUntil",
|
|
177
|
+
dead_letter as "deadLetter",
|
|
178
|
+
output
|
|
179
|
+
`
|
|
180
|
+
|
|
181
|
+
function createPartition (schema, name) {
|
|
182
|
+
return `SELECT ${schema}.create_partition('${name}');`
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function getPartitionFunction (schema) {
|
|
186
|
+
return `
|
|
187
|
+
CREATE FUNCTION ${schema}.get_partition(queue_name text, out name text) AS
|
|
188
|
+
$$
|
|
189
|
+
SELECT 'job_' || encode(sha224(queue_name::bytea), 'hex');
|
|
190
|
+
$$
|
|
191
|
+
LANGUAGE SQL
|
|
192
|
+
IMMUTABLE
|
|
193
|
+
`
|
|
163
194
|
}
|
|
164
195
|
|
|
165
|
-
function
|
|
166
|
-
return `
|
|
196
|
+
function createPartitionFunction (schema) {
|
|
197
|
+
return `
|
|
198
|
+
CREATE FUNCTION ${schema}.create_partition(queue_name text)
|
|
199
|
+
RETURNS VOID AS
|
|
200
|
+
$$
|
|
201
|
+
DECLARE
|
|
202
|
+
table_name varchar := ${schema}.get_partition(queue_name);
|
|
203
|
+
BEGIN
|
|
204
|
+
EXECUTE format('CREATE TABLE ${schema}.%I (LIKE ${schema}.job INCLUDING DEFAULTS INCLUDING CONSTRAINTS)', table_name);
|
|
205
|
+
EXECUTE format('ALTER TABLE ${schema}.%I ADD CHECK (name=%L)', table_name, queue_name);
|
|
206
|
+
EXECUTE format('ALTER TABLE ${schema}.job ATTACH PARTITION ${schema}.%I FOR VALUES IN (%L)', table_name, queue_name);
|
|
207
|
+
END;
|
|
208
|
+
$$
|
|
209
|
+
LANGUAGE plpgsql;
|
|
210
|
+
`
|
|
167
211
|
}
|
|
168
212
|
|
|
169
|
-
function
|
|
170
|
-
return `
|
|
213
|
+
function dropPartitionFunction (schema) {
|
|
214
|
+
return `
|
|
215
|
+
CREATE FUNCTION ${schema}.drop_partition(queue_name text)
|
|
216
|
+
RETURNS VOID AS
|
|
217
|
+
$$
|
|
218
|
+
BEGIN
|
|
219
|
+
EXECUTE format('DROP TABLE IF EXISTS %I', ${schema}.get_partition(queue_name));
|
|
220
|
+
END;
|
|
221
|
+
$$
|
|
222
|
+
LANGUAGE plpgsql;
|
|
223
|
+
`
|
|
171
224
|
}
|
|
172
225
|
|
|
173
|
-
function
|
|
174
|
-
return `
|
|
226
|
+
function dropPartition (schema, name) {
|
|
227
|
+
return `SELECT ${schema}.drop_partition('${name}');`
|
|
175
228
|
}
|
|
176
229
|
|
|
177
|
-
function
|
|
178
|
-
return `
|
|
230
|
+
function createPrimaryKeyArchive (schema) {
|
|
231
|
+
return `ALTER TABLE ${schema}.archive ADD CONSTRAINT archive_pkey PRIMARY KEY (name, id)`
|
|
179
232
|
}
|
|
180
233
|
|
|
181
|
-
function
|
|
182
|
-
return `
|
|
234
|
+
function createIndexJobPolicyShort (schema) {
|
|
235
|
+
return `CREATE UNIQUE INDEX job_policy_short ON ${schema}.job (name) WHERE state = '${JOB_STATES.created}' AND policy = '${QUEUE_POLICIES.short}'`
|
|
183
236
|
}
|
|
184
237
|
|
|
185
|
-
function
|
|
186
|
-
|
|
187
|
-
return `UPDATE ${schema}.version SET cron_on = ${time}`
|
|
238
|
+
function createIndexJobPolicySingleton (schema) {
|
|
239
|
+
return `CREATE UNIQUE INDEX job_policy_singleton ON ${schema}.job (name) WHERE state = '${JOB_STATES.active}' AND policy = '${QUEUE_POLICIES.singleton}'`
|
|
188
240
|
}
|
|
189
241
|
|
|
190
|
-
function
|
|
191
|
-
return `
|
|
242
|
+
function createIndexJobPolicyStately (schema) {
|
|
243
|
+
return `CREATE UNIQUE INDEX job_policy_stately ON ${schema}.job (name, state) WHERE state <= '${JOB_STATES.active}' AND policy = '${QUEUE_POLICIES.stately}'`
|
|
192
244
|
}
|
|
193
245
|
|
|
194
|
-
function
|
|
195
|
-
|
|
196
|
-
assert(options.before in states, `${options.before} is not a valid state`)
|
|
197
|
-
return `DELETE FROM ${schema}.job WHERE name = $1 and state < '${options.before}'`
|
|
246
|
+
function createIndexJobThrottleOn (schema) {
|
|
247
|
+
return `CREATE UNIQUE INDEX job_throttle_on ON ${schema}.job (name, singleton_on, COALESCE(singleton_key, '')) WHERE state <= '${JOB_STATES.completed}' AND singleton_on IS NOT NULL`
|
|
198
248
|
}
|
|
199
249
|
|
|
200
|
-
function
|
|
201
|
-
|
|
202
|
-
assert(options.before in states, `${options.before} is not a valid state`)
|
|
203
|
-
return `DELETE FROM ${schema}.job WHERE state < '${options.before}'`
|
|
250
|
+
function createIndexJobThrottleKey (schema) {
|
|
251
|
+
return `CREATE UNIQUE INDEX job_throttle_key ON ${schema}.job (name, singleton_key) WHERE state <= '${JOB_STATES.completed}' AND singleton_on IS NULL`
|
|
204
252
|
}
|
|
205
253
|
|
|
206
|
-
function
|
|
207
|
-
return `
|
|
254
|
+
function createIndexJobFetch (schema) {
|
|
255
|
+
return `CREATE INDEX job_fetch ON ${schema}.job (name, start_after) INCLUDE (priority, created_on, id) WHERE state < '${JOB_STATES.active}'`
|
|
208
256
|
}
|
|
209
257
|
|
|
210
|
-
function
|
|
211
|
-
|
|
212
|
-
assert(options.before in states, `${options.before} is not a valid state`)
|
|
213
|
-
return `SELECT count(*) as count FROM ${schema}.job WHERE name = $1 AND state < '${options.before}'`
|
|
258
|
+
function createTableArchive (schema) {
|
|
259
|
+
return `CREATE TABLE ${schema}.archive (LIKE ${schema}.job)`
|
|
214
260
|
}
|
|
215
261
|
|
|
216
|
-
function
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
262
|
+
function createColumnArchiveArchivedOn (schema) {
|
|
263
|
+
return `ALTER TABLE ${schema}.archive ADD archived_on timestamptz NOT NULL DEFAULT now()`
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function createIndexArchiveArchivedOn (schema) {
|
|
267
|
+
return `CREATE INDEX archive_archived_on_idx ON ${schema}.archive(archived_on)`
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function trySetMaintenanceTime (schema) {
|
|
271
|
+
return trySetTimestamp(schema, 'maintained_on')
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function trySetMonitorTime (schema) {
|
|
275
|
+
return trySetTimestamp(schema, 'monitored_on')
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function trySetCronTime (schema) {
|
|
279
|
+
return trySetTimestamp(schema, 'cron_on')
|
|
221
280
|
}
|
|
222
281
|
|
|
223
|
-
function
|
|
224
|
-
// "singleton queue" means "only 1 job can be queued at a time"
|
|
282
|
+
function trySetTimestamp (schema, column) {
|
|
225
283
|
return `
|
|
226
|
-
|
|
284
|
+
UPDATE ${schema}.version SET ${column} = now()
|
|
285
|
+
WHERE EXTRACT( EPOCH FROM (now() - COALESCE(${column}, now() - interval '1 week') ) ) > $1
|
|
286
|
+
RETURNING true
|
|
227
287
|
`
|
|
228
288
|
}
|
|
229
289
|
|
|
230
|
-
function
|
|
231
|
-
// anything with singletonOn means "only 1 job within this time period, queued, active or completed"
|
|
290
|
+
function createQueue (schema) {
|
|
232
291
|
return `
|
|
233
|
-
|
|
292
|
+
INSERT INTO ${schema}.queue (name, policy, retry_limit, retry_delay, retry_backoff, expire_seconds, retention_minutes, dead_letter)
|
|
293
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
|
234
294
|
`
|
|
235
295
|
}
|
|
236
296
|
|
|
237
|
-
function
|
|
238
|
-
// anything with both singletonOn and singletonKey means "only 1 job within this time period with this key, queued, active or completed"
|
|
297
|
+
function updateQueue (schema) {
|
|
239
298
|
return `
|
|
240
|
-
|
|
299
|
+
UPDATE ${schema}.queue SET
|
|
300
|
+
retry_limit = COALESCE($2, retry_limit),
|
|
301
|
+
retry_delay = COALESCE($3, retry_delay),
|
|
302
|
+
retry_backoff = COALESCE($4, retry_backoff),
|
|
303
|
+
expire_seconds = COALESCE($5, expire_seconds),
|
|
304
|
+
retention_minutes = COALESCE($6, retention_minutes),
|
|
305
|
+
dead_letter = COALESCE($7, dead_letter)
|
|
306
|
+
WHERE name = $1
|
|
241
307
|
`
|
|
242
308
|
}
|
|
243
309
|
|
|
244
|
-
function
|
|
245
|
-
return `
|
|
246
|
-
|
|
310
|
+
function getQueueByName (schema) {
|
|
311
|
+
return `SELECT * FROM ${schema}.queue WHERE name = $1`
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function deleteQueueRecords (schema) {
|
|
315
|
+
return `WITH dq AS (
|
|
316
|
+
DELETE FROM ${schema}.queue WHERE name = $1
|
|
317
|
+
), ds AS (
|
|
318
|
+
DELETE FROM ${schema}.schedule WHERE name = $1
|
|
319
|
+
)
|
|
320
|
+
DELETE FROM ${schema}.job WHERE name = $1
|
|
247
321
|
`
|
|
248
322
|
}
|
|
249
323
|
|
|
250
|
-
function
|
|
324
|
+
function purgeQueue (schema) {
|
|
325
|
+
return `DELETE from ${schema}.job WHERE name = $1 and state < '${JOB_STATES.active}'`
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function clearStorage (schema) {
|
|
329
|
+
return `TRUNCATE ${schema}.job, ${schema}.archive`
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function getQueueSize (schema, options = {}) {
|
|
333
|
+
options.before = options.before || JOB_STATES.active
|
|
334
|
+
assert(options.before in JOB_STATES, `${options.before} is not a valid state`)
|
|
335
|
+
return `SELECT count(*) as count FROM ${schema}.job WHERE name = $1 AND state < '${options.before}'`
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function createTableQueue (schema) {
|
|
251
339
|
return `
|
|
252
|
-
CREATE
|
|
340
|
+
CREATE TABLE ${schema}.queue (
|
|
341
|
+
name text primary key,
|
|
342
|
+
policy text,
|
|
343
|
+
retry_limit int,
|
|
344
|
+
retry_delay int,
|
|
345
|
+
retry_backoff bool,
|
|
346
|
+
expire_seconds int,
|
|
347
|
+
retention_minutes int,
|
|
348
|
+
dead_letter text,
|
|
349
|
+
created_on timestamp with time zone not null default now()
|
|
350
|
+
)
|
|
253
351
|
`
|
|
254
352
|
}
|
|
255
353
|
|
|
256
|
-
function
|
|
354
|
+
function createTableSchedule (schema) {
|
|
257
355
|
return `
|
|
258
356
|
CREATE TABLE ${schema}.schedule (
|
|
259
357
|
name text primary key,
|
|
@@ -267,7 +365,7 @@ function createScheduleTable (schema) {
|
|
|
267
365
|
`
|
|
268
366
|
}
|
|
269
367
|
|
|
270
|
-
function
|
|
368
|
+
function createTableSubscription (schema) {
|
|
271
369
|
return `
|
|
272
370
|
CREATE TABLE ${schema}.subscription (
|
|
273
371
|
event text not null,
|
|
@@ -281,7 +379,9 @@ function createSubscriptionTable (schema) {
|
|
|
281
379
|
|
|
282
380
|
function getSchedules (schema) {
|
|
283
381
|
return `
|
|
284
|
-
SELECT
|
|
382
|
+
SELECT s.*
|
|
383
|
+
FROM ${schema}.schedule s
|
|
384
|
+
JOIN ${schema}.queue q on s.name = q.name
|
|
285
385
|
`
|
|
286
386
|
}
|
|
287
387
|
|
|
@@ -351,167 +451,105 @@ function insertVersion (schema, version) {
|
|
|
351
451
|
}
|
|
352
452
|
|
|
353
453
|
function fetchNextJob (schema) {
|
|
354
|
-
return (includeMetadata,
|
|
355
|
-
WITH
|
|
454
|
+
return ({ includeMetadata, priority = true } = {}) => `
|
|
455
|
+
WITH next as (
|
|
356
456
|
SELECT id
|
|
357
|
-
FROM ${schema}.job
|
|
358
|
-
WHERE
|
|
359
|
-
AND
|
|
360
|
-
AND
|
|
361
|
-
|
|
362
|
-
? `AND (
|
|
363
|
-
CASE
|
|
364
|
-
WHEN singletonKey IS NOT NULL
|
|
365
|
-
AND singletonKey LIKE '${SINGLETON_QUEUE_KEY_ESCAPED}%'
|
|
366
|
-
THEN NOT EXISTS (
|
|
367
|
-
SELECT 1
|
|
368
|
-
FROM ${schema}.job active_job
|
|
369
|
-
WHERE active_job.state = '${states.active}'
|
|
370
|
-
AND active_job.name = j.name
|
|
371
|
-
AND active_job.singletonKey = j.singletonKey
|
|
372
|
-
LIMIT 1
|
|
373
|
-
)
|
|
374
|
-
ELSE
|
|
375
|
-
true
|
|
376
|
-
END
|
|
377
|
-
)`
|
|
378
|
-
: ''}
|
|
379
|
-
ORDER BY priority desc, createdOn, id
|
|
457
|
+
FROM ${schema}.job
|
|
458
|
+
WHERE name = $1
|
|
459
|
+
AND state < '${JOB_STATES.active}'
|
|
460
|
+
AND start_after < now()
|
|
461
|
+
ORDER BY ${priority && 'priority desc, '} created_on, id
|
|
380
462
|
LIMIT $2
|
|
381
463
|
FOR UPDATE SKIP LOCKED
|
|
382
464
|
)
|
|
383
465
|
UPDATE ${schema}.job j SET
|
|
384
|
-
state = '${
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
FROM
|
|
388
|
-
WHERE j.id =
|
|
389
|
-
RETURNING
|
|
466
|
+
state = '${JOB_STATES.active}',
|
|
467
|
+
started_on = now(),
|
|
468
|
+
retry_count = CASE WHEN started_on IS NOT NULL THEN retry_count + 1 ELSE retry_count END
|
|
469
|
+
FROM next
|
|
470
|
+
WHERE name = $1 AND j.id = next.id
|
|
471
|
+
RETURNING j.${includeMetadata ? allJobColumns : baseJobColumns}
|
|
390
472
|
`
|
|
391
473
|
}
|
|
392
474
|
|
|
393
|
-
function buildJsonCompletionObject (withResponse) {
|
|
394
|
-
// job completion contract
|
|
395
|
-
return `jsonb_build_object(
|
|
396
|
-
'request', jsonb_build_object('id', id, 'name', name, 'data', data),
|
|
397
|
-
'response', ${withResponse ? '$2::jsonb' : 'null'},
|
|
398
|
-
'state', state,
|
|
399
|
-
'retryCount', retryCount,
|
|
400
|
-
'createdOn', createdOn,
|
|
401
|
-
'startedOn', startedOn,
|
|
402
|
-
'completedOn', completedOn,
|
|
403
|
-
'failed', CASE WHEN state = '${states.completed}' THEN false ELSE true END
|
|
404
|
-
)`
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
const retryCompletedOnCase = `CASE
|
|
408
|
-
WHEN retryCount < retryLimit
|
|
409
|
-
THEN NULL
|
|
410
|
-
ELSE now()
|
|
411
|
-
END`
|
|
412
|
-
|
|
413
|
-
const retryStartAfterCase = `CASE
|
|
414
|
-
WHEN retryCount = retryLimit THEN startAfter
|
|
415
|
-
WHEN NOT retryBackoff THEN now() + retryDelay * interval '1'
|
|
416
|
-
ELSE now() +
|
|
417
|
-
(
|
|
418
|
-
retryDelay * 2 ^ LEAST(16, retryCount + 1) / 2
|
|
419
|
-
+
|
|
420
|
-
retryDelay * 2 ^ LEAST(16, retryCount + 1) / 2 * random()
|
|
421
|
-
)
|
|
422
|
-
* interval '1'
|
|
423
|
-
END`
|
|
424
|
-
|
|
425
|
-
const keepUntilInheritance = 'keepUntil + (keepUntil - startAfter)'
|
|
426
|
-
|
|
427
475
|
function completeJobs (schema) {
|
|
428
476
|
return `
|
|
429
477
|
WITH results AS (
|
|
430
478
|
UPDATE ${schema}.job
|
|
431
|
-
SET
|
|
432
|
-
state = '${
|
|
433
|
-
output = $
|
|
434
|
-
WHERE
|
|
435
|
-
AND
|
|
479
|
+
SET completed_on = now(),
|
|
480
|
+
state = '${JOB_STATES.completed}',
|
|
481
|
+
output = $3::jsonb
|
|
482
|
+
WHERE name = $1
|
|
483
|
+
AND id IN (SELECT UNNEST($2::uuid[]))
|
|
484
|
+
AND state = '${JOB_STATES.active}'
|
|
436
485
|
RETURNING *
|
|
437
|
-
), completion_jobs as (
|
|
438
|
-
INSERT INTO ${schema}.job (name, data, keepUntil)
|
|
439
|
-
SELECT
|
|
440
|
-
'${COMPLETION_JOB_PREFIX}' || name,
|
|
441
|
-
${buildJsonCompletionObject(true)},
|
|
442
|
-
${keepUntilInheritance}
|
|
443
|
-
FROM results
|
|
444
|
-
WHERE NOT name LIKE '${COMPLETION_JOB_PREFIX}%'
|
|
445
|
-
AND on_complete
|
|
446
486
|
)
|
|
447
487
|
SELECT COUNT(*) FROM results
|
|
448
488
|
`
|
|
449
489
|
}
|
|
450
490
|
|
|
451
|
-
function
|
|
491
|
+
function failJobsById (schema) {
|
|
492
|
+
const where = `name = $1 AND id IN (SELECT UNNEST($2::uuid[])) AND state < '${JOB_STATES.completed}'`
|
|
493
|
+
const output = '$3::jsonb'
|
|
494
|
+
|
|
495
|
+
return failJobs(schema, where, output)
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function failJobsByTimeout (schema) {
|
|
499
|
+
const where = `state = '${JOB_STATES.active}' AND (started_on + expire_in) < now()`
|
|
500
|
+
const output = '\'{ "value": { "message": "job failed by timeout in active state" } }\'::jsonb'
|
|
501
|
+
return failJobs(schema, where, output)
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function failJobs (schema, where, output) {
|
|
452
505
|
return `
|
|
453
506
|
WITH results AS (
|
|
454
|
-
UPDATE ${schema}.job
|
|
455
|
-
|
|
456
|
-
WHEN
|
|
457
|
-
|
|
458
|
-
|
|
507
|
+
UPDATE ${schema}.job SET
|
|
508
|
+
state = CASE
|
|
509
|
+
WHEN retry_count < retry_limit THEN '${JOB_STATES.retry}'::${schema}.job_state
|
|
510
|
+
ELSE '${JOB_STATES.failed}'::${schema}.job_state
|
|
511
|
+
END,
|
|
512
|
+
completed_on = CASE
|
|
513
|
+
WHEN retry_count < retry_limit THEN NULL
|
|
514
|
+
ELSE now()
|
|
459
515
|
END,
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
516
|
+
start_after = CASE
|
|
517
|
+
WHEN retry_count = retry_limit THEN start_after
|
|
518
|
+
WHEN NOT retry_backoff THEN now() + retry_delay * interval '1'
|
|
519
|
+
ELSE now() + (
|
|
520
|
+
retry_delay * 2 ^ LEAST(16, retry_count + 1) / 2 +
|
|
521
|
+
retry_delay * 2 ^ LEAST(16, retry_count + 1) / 2 * random()
|
|
522
|
+
) * interval '1'
|
|
523
|
+
END,
|
|
524
|
+
output = ${output}
|
|
525
|
+
WHERE ${where}
|
|
465
526
|
RETURNING *
|
|
466
|
-
),
|
|
467
|
-
INSERT INTO ${schema}.job (name, data,
|
|
527
|
+
), dlq_jobs as (
|
|
528
|
+
INSERT INTO ${schema}.job (name, data, output, retry_limit, keep_until)
|
|
468
529
|
SELECT
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
530
|
+
dead_letter,
|
|
531
|
+
data,
|
|
532
|
+
output,
|
|
533
|
+
retry_limit,
|
|
534
|
+
keep_until + (keep_until - start_after)
|
|
472
535
|
FROM results
|
|
473
|
-
WHERE state = '${
|
|
474
|
-
AND
|
|
475
|
-
AND
|
|
536
|
+
WHERE state = '${JOB_STATES.failed}'
|
|
537
|
+
AND dead_letter IS NOT NULL
|
|
538
|
+
AND NOT name = dead_letter
|
|
476
539
|
)
|
|
477
540
|
SELECT COUNT(*) FROM results
|
|
478
541
|
`
|
|
479
542
|
}
|
|
480
543
|
|
|
481
|
-
function expire (schema) {
|
|
482
|
-
return `
|
|
483
|
-
WITH results AS (
|
|
484
|
-
UPDATE ${schema}.job
|
|
485
|
-
SET state = CASE
|
|
486
|
-
WHEN retryCount < retryLimit THEN '${states.retry}'::${schema}.job_state
|
|
487
|
-
ELSE '${states.expired}'::${schema}.job_state
|
|
488
|
-
END,
|
|
489
|
-
completedOn = ${retryCompletedOnCase},
|
|
490
|
-
startAfter = ${retryStartAfterCase}
|
|
491
|
-
WHERE state = '${states.active}'
|
|
492
|
-
AND (startedOn + expireIn) < now()
|
|
493
|
-
RETURNING *
|
|
494
|
-
)
|
|
495
|
-
INSERT INTO ${schema}.job (name, data, keepUntil)
|
|
496
|
-
SELECT
|
|
497
|
-
'${COMPLETION_JOB_PREFIX}' || name,
|
|
498
|
-
${buildJsonCompletionObject()},
|
|
499
|
-
${keepUntilInheritance}
|
|
500
|
-
FROM results
|
|
501
|
-
WHERE state = '${states.expired}'
|
|
502
|
-
AND NOT name LIKE '${COMPLETION_JOB_PREFIX}%'
|
|
503
|
-
AND on_complete
|
|
504
|
-
`
|
|
505
|
-
}
|
|
506
|
-
|
|
507
544
|
function cancelJobs (schema) {
|
|
508
545
|
return `
|
|
509
546
|
with results as (
|
|
510
547
|
UPDATE ${schema}.job
|
|
511
|
-
SET
|
|
512
|
-
state = '${
|
|
513
|
-
WHERE
|
|
514
|
-
AND
|
|
548
|
+
SET completed_on = now(),
|
|
549
|
+
state = '${JOB_STATES.cancelled}'
|
|
550
|
+
WHERE name = $1
|
|
551
|
+
AND id IN (SELECT UNNEST($2::uuid[]))
|
|
552
|
+
AND state < '${JOB_STATES.completed}'
|
|
515
553
|
RETURNING 1
|
|
516
554
|
)
|
|
517
555
|
SELECT COUNT(*) from results
|
|
@@ -522,9 +560,10 @@ function resumeJobs (schema) {
|
|
|
522
560
|
return `
|
|
523
561
|
with results as (
|
|
524
562
|
UPDATE ${schema}.job
|
|
525
|
-
SET
|
|
526
|
-
state = '${
|
|
527
|
-
WHERE
|
|
563
|
+
SET completed_on = NULL,
|
|
564
|
+
state = '${JOB_STATES.created}'
|
|
565
|
+
WHERE name = $1
|
|
566
|
+
AND id IN (SELECT UNNEST($2::uuid[]))
|
|
528
567
|
RETURNING 1
|
|
529
568
|
)
|
|
530
569
|
SELECT COUNT(*) from results
|
|
@@ -536,68 +575,73 @@ function insertJob (schema) {
|
|
|
536
575
|
INSERT INTO ${schema}.job (
|
|
537
576
|
id,
|
|
538
577
|
name,
|
|
539
|
-
priority,
|
|
540
|
-
state,
|
|
541
|
-
retryLimit,
|
|
542
|
-
startAfter,
|
|
543
|
-
expireIn,
|
|
544
578
|
data,
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
579
|
+
priority,
|
|
580
|
+
start_after,
|
|
581
|
+
singleton_key,
|
|
582
|
+
singleton_on,
|
|
583
|
+
dead_letter,
|
|
584
|
+
expire_in,
|
|
585
|
+
keep_until,
|
|
586
|
+
retry_limit,
|
|
587
|
+
retry_delay,
|
|
588
|
+
retry_backoff,
|
|
589
|
+
policy
|
|
551
590
|
)
|
|
552
591
|
SELECT
|
|
553
592
|
id,
|
|
554
|
-
name,
|
|
555
|
-
priority,
|
|
556
|
-
state,
|
|
557
|
-
retryLimit,
|
|
558
|
-
startAfter,
|
|
559
|
-
expireIn,
|
|
593
|
+
j.name,
|
|
560
594
|
data,
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
595
|
+
priority,
|
|
596
|
+
start_after,
|
|
597
|
+
singleton_key,
|
|
598
|
+
singleton_on,
|
|
599
|
+
COALESCE(j.dead_letter, q.dead_letter) as dead_letter,
|
|
600
|
+
CASE
|
|
601
|
+
WHEN expire_in IS NOT NULL THEN CAST(expire_in as interval)
|
|
602
|
+
WHEN q.expire_seconds IS NOT NULL THEN q.expire_seconds * interval '1s'
|
|
603
|
+
WHEN expire_in_default IS NOT NULL THEN CAST(expire_in_default as interval)
|
|
604
|
+
ELSE interval '15 minutes'
|
|
605
|
+
END as expire_in,
|
|
606
|
+
CASE
|
|
607
|
+
WHEN right(keep_until, 1) = 'Z' THEN CAST(keep_until as timestamp with time zone)
|
|
608
|
+
ELSE start_after + CAST(COALESCE(keep_until, (q.retention_minutes * 60)::text, keep_until_default, '14 days') as interval)
|
|
609
|
+
END as keep_until,
|
|
610
|
+
COALESCE(j.retry_limit, q.retry_limit, retry_limit_default, 2) as retry_limit,
|
|
611
|
+
CASE
|
|
612
|
+
WHEN COALESCE(j.retry_backoff, q.retry_backoff, retry_backoff_default, false)
|
|
613
|
+
THEN GREATEST(COALESCE(j.retry_delay, q.retry_delay, retry_delay_default), 1)
|
|
614
|
+
ELSE COALESCE(j.retry_delay, q.retry_delay, retry_delay_default, 0)
|
|
615
|
+
END as retry_delay,
|
|
616
|
+
COALESCE(j.retry_backoff, q.retry_backoff, retry_backoff_default, false) as retry_backoff,
|
|
617
|
+
q.policy
|
|
567
618
|
FROM
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
619
|
+
( SELECT
|
|
620
|
+
COALESCE($1::uuid, gen_random_uuid()) as id,
|
|
621
|
+
$2 as name,
|
|
622
|
+
$3::jsonb as data,
|
|
623
|
+
COALESCE($4::int, 0) as priority,
|
|
624
|
+
CASE
|
|
625
|
+
WHEN right($5, 1) = 'Z' THEN CAST($5 as timestamp with time zone)
|
|
626
|
+
ELSE now() + CAST(COALESCE($5,'0') as interval)
|
|
627
|
+
END as start_after,
|
|
628
|
+
$6 as singleton_key,
|
|
575
629
|
CASE
|
|
576
|
-
WHEN
|
|
577
|
-
ELSE
|
|
578
|
-
END as
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
WHEN $9::integer IS NOT NULL THEN 'epoch'::timestamp + '1 second'::interval * ($9 * floor((date_part('epoch', now()) + $10) / $9))
|
|
592
|
-
ELSE NULL
|
|
593
|
-
END as singletonOn,
|
|
594
|
-
$11::int as retryDelay,
|
|
595
|
-
$12::bool as retryBackoff,
|
|
596
|
-
$13::text as keepUntilValue,
|
|
597
|
-
$14::boolean as on_complete
|
|
598
|
-
) j1
|
|
599
|
-
) j2
|
|
600
|
-
) j3
|
|
630
|
+
WHEN $7::integer IS NOT NULL THEN 'epoch'::timestamp + '1 second'::interval * ($7 * floor((date_part('epoch', now()) + $8) / $7))
|
|
631
|
+
ELSE NULL
|
|
632
|
+
END as singleton_on,
|
|
633
|
+
$9 as dead_letter,
|
|
634
|
+
$10 as expire_in,
|
|
635
|
+
$11 as expire_in_default,
|
|
636
|
+
$12 as keep_until,
|
|
637
|
+
$13 as keep_until_default,
|
|
638
|
+
$14::int as retry_limit,
|
|
639
|
+
$15::int as retry_limit_default,
|
|
640
|
+
$16::int as retry_delay,
|
|
641
|
+
$17::int as retry_delay_default,
|
|
642
|
+
$18::bool as retry_backoff,
|
|
643
|
+
$19::bool as retry_backoff_default
|
|
644
|
+
) j LEFT JOIN ${schema}.queue q ON j.name = q.name
|
|
601
645
|
ON CONFLICT DO NOTHING
|
|
602
646
|
RETURNING id
|
|
603
647
|
`
|
|
@@ -605,78 +649,95 @@ function insertJob (schema) {
|
|
|
605
649
|
|
|
606
650
|
function insertJobs (schema) {
|
|
607
651
|
return `
|
|
652
|
+
WITH defaults as (
|
|
653
|
+
SELECT
|
|
654
|
+
$2 as expire_in,
|
|
655
|
+
$3 as keep_until,
|
|
656
|
+
$4::int as retry_limit,
|
|
657
|
+
$5::int as retry_delay,
|
|
658
|
+
$6::bool as retry_backoff
|
|
659
|
+
)
|
|
608
660
|
INSERT INTO ${schema}.job (
|
|
609
661
|
id,
|
|
610
662
|
name,
|
|
611
663
|
data,
|
|
612
664
|
priority,
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
665
|
+
start_after,
|
|
666
|
+
singleton_key,
|
|
667
|
+
dead_letter,
|
|
668
|
+
expire_in,
|
|
669
|
+
keep_until,
|
|
670
|
+
retry_limit,
|
|
671
|
+
retry_delay,
|
|
672
|
+
retry_backoff,
|
|
673
|
+
policy
|
|
621
674
|
)
|
|
622
675
|
SELECT
|
|
623
676
|
COALESCE(id, gen_random_uuid()) as id,
|
|
624
|
-
name,
|
|
677
|
+
j.name,
|
|
625
678
|
data,
|
|
626
|
-
COALESCE(priority, 0)
|
|
627
|
-
COALESCE("startAfter", now())
|
|
628
|
-
COALESCE("expireInSeconds", 15 * 60) * interval '1s' as expireIn,
|
|
629
|
-
COALESCE("retryLimit", 0) as retryLimit,
|
|
630
|
-
COALESCE("retryDelay", 0) as retryDelay,
|
|
631
|
-
COALESCE("retryBackoff", false) as retryBackoff,
|
|
679
|
+
COALESCE(priority, 0),
|
|
680
|
+
COALESCE("startAfter", now()),
|
|
632
681
|
"singletonKey",
|
|
633
|
-
COALESCE("
|
|
634
|
-
|
|
635
|
-
|
|
682
|
+
COALESCE("deadLetter", q.dead_letter),
|
|
683
|
+
CASE
|
|
684
|
+
WHEN "expireInSeconds" IS NOT NULL THEN "expireInSeconds" * interval '1s'
|
|
685
|
+
WHEN q.expire_seconds IS NOT NULL THEN q.expire_seconds * interval '1s'
|
|
686
|
+
WHEN defaults.expire_in IS NOT NULL THEN CAST(defaults.expire_in as interval)
|
|
687
|
+
ELSE interval '15 minutes'
|
|
688
|
+
END as expire_in,
|
|
689
|
+
CASE
|
|
690
|
+
WHEN "keepUntil" IS NOT NULL THEN "keepUntil"
|
|
691
|
+
ELSE COALESCE("startAfter", now()) + CAST(COALESCE((q.retention_minutes * 60)::text, defaults.keep_until, '14 days') as interval)
|
|
692
|
+
END as keep_until,
|
|
693
|
+
COALESCE("retryLimit", q.retry_limit, defaults.retry_limit, 2),
|
|
694
|
+
CASE
|
|
695
|
+
WHEN COALESCE("retryBackoff", q.retry_backoff, defaults.retry_backoff, false)
|
|
696
|
+
THEN GREATEST(COALESCE("retryDelay", q.retry_delay, defaults.retry_delay), 1)
|
|
697
|
+
ELSE COALESCE("retryDelay", q.retry_delay, defaults.retry_delay, 0)
|
|
698
|
+
END as retry_delay,
|
|
699
|
+
COALESCE("retryBackoff", q.retry_backoff, defaults.retry_backoff, false) as retry_backoff,
|
|
700
|
+
q.policy
|
|
701
|
+
FROM json_to_recordset($1) as j (
|
|
636
702
|
id uuid,
|
|
637
703
|
name text,
|
|
638
704
|
priority integer,
|
|
639
705
|
data jsonb,
|
|
706
|
+
"startAfter" timestamp with time zone,
|
|
640
707
|
"retryLimit" integer,
|
|
641
708
|
"retryDelay" integer,
|
|
642
709
|
"retryBackoff" boolean,
|
|
643
|
-
"startAfter" timestamp with time zone,
|
|
644
710
|
"singletonKey" text,
|
|
645
711
|
"expireInSeconds" integer,
|
|
646
712
|
"keepUntil" timestamp with time zone,
|
|
647
|
-
"
|
|
713
|
+
"deadLetter" text
|
|
648
714
|
)
|
|
715
|
+
LEFT JOIN ${schema}.queue q ON j.name = q.name,
|
|
716
|
+
defaults
|
|
649
717
|
ON CONFLICT DO NOTHING
|
|
650
718
|
`
|
|
651
719
|
}
|
|
652
720
|
|
|
653
|
-
function
|
|
721
|
+
function drop (schema, interval) {
|
|
654
722
|
return `
|
|
655
723
|
DELETE FROM ${schema}.archive
|
|
656
|
-
WHERE
|
|
724
|
+
WHERE archived_on < (now() - interval '${interval}')
|
|
657
725
|
`
|
|
658
726
|
}
|
|
659
727
|
|
|
660
728
|
function archive (schema, completedInterval, failedInterval = completedInterval) {
|
|
729
|
+
const columns = 'id, name, priority, data, state, retry_limit, retry_count, retry_delay, retry_backoff, start_after, started_on, singleton_key, singleton_on, expire_in, created_on, completed_on, keep_until, dead_letter, policy, output'
|
|
730
|
+
|
|
661
731
|
return `
|
|
662
732
|
WITH archived_rows AS (
|
|
663
733
|
DELETE FROM ${schema}.job
|
|
664
|
-
WHERE (
|
|
665
|
-
|
|
666
|
-
)
|
|
667
|
-
OR (
|
|
668
|
-
state = '${states.failed}' AND completedOn < (now() - interval '${failedInterval}')
|
|
669
|
-
)
|
|
670
|
-
OR (
|
|
671
|
-
state < '${states.active}' AND keepUntil < now()
|
|
672
|
-
)
|
|
734
|
+
WHERE (state <> '${JOB_STATES.failed}' AND completed_on < (now() - interval '${completedInterval}'))
|
|
735
|
+
OR (state = '${JOB_STATES.failed}' AND completed_on < (now() - interval '${failedInterval}'))
|
|
736
|
+
OR (state < '${JOB_STATES.active}' AND keep_until < now())
|
|
673
737
|
RETURNING *
|
|
674
738
|
)
|
|
675
|
-
INSERT INTO ${schema}.archive (
|
|
676
|
-
|
|
677
|
-
)
|
|
678
|
-
SELECT
|
|
679
|
-
id, name, priority, data, state, retryLimit, retryCount, retryDelay, retryBackoff, startAfter, startedOn, singletonKey, singletonOn, expireIn, createdOn, completedOn, keepUntil, on_complete, output
|
|
739
|
+
INSERT INTO ${schema}.archive (${columns})
|
|
740
|
+
SELECT ${columns}
|
|
680
741
|
FROM archived_rows
|
|
681
742
|
`
|
|
682
743
|
}
|
|
@@ -689,9 +750,24 @@ function countStates (schema) {
|
|
|
689
750
|
`
|
|
690
751
|
}
|
|
691
752
|
|
|
692
|
-
function
|
|
693
|
-
|
|
694
|
-
|
|
753
|
+
function locked (schema, query) {
|
|
754
|
+
if (Array.isArray(query)) {
|
|
755
|
+
query = query.join(';\n')
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
return `
|
|
759
|
+
BEGIN;
|
|
760
|
+
SET LOCAL lock_timeout = '30s';
|
|
761
|
+
SET LOCAL idle_in_transaction_session_timeout = '30s';
|
|
762
|
+
${advisoryLock(schema)};
|
|
763
|
+
${query};
|
|
764
|
+
COMMIT;
|
|
765
|
+
`
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
function advisoryLock (schema, key) {
|
|
769
|
+
return `SELECT pg_advisory_xact_lock(
|
|
770
|
+
('x' || encode(sha224((current_database() || '.pgboss.${schema}${key || ''}')::bytea), 'hex'))::bit(64)::bigint
|
|
695
771
|
)`
|
|
696
772
|
}
|
|
697
773
|
|
|
@@ -701,13 +777,13 @@ function assertMigration (schema, version) {
|
|
|
701
777
|
}
|
|
702
778
|
|
|
703
779
|
function getJobById (schema) {
|
|
704
|
-
return
|
|
780
|
+
return getJobByTableQueueId(schema, 'job')
|
|
705
781
|
}
|
|
706
782
|
|
|
707
783
|
function getArchivedJobById (schema) {
|
|
708
|
-
return
|
|
784
|
+
return getJobByTableQueueId(schema, 'archive')
|
|
709
785
|
}
|
|
710
786
|
|
|
711
|
-
function
|
|
712
|
-
return `SELECT
|
|
787
|
+
function getJobByTableQueueId (schema, table) {
|
|
788
|
+
return `SELECT ${allJobColumns} FROM ${schema}.${table} WHERE name = $1 AND id = $2`
|
|
713
789
|
}
|