pg-boss 12.35.1 → 12.36.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 +1 -0
- package/dist/attorney.d.ts.map +1 -1
- package/dist/attorney.js +21 -0
- package/dist/boss.d.ts.map +1 -1
- package/dist/boss.js +35 -4
- package/dist/db.d.ts +1 -0
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +11 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -0
- package/dist/latency.d.ts +15 -0
- package/dist/latency.d.ts.map +1 -0
- package/dist/latency.js +45 -0
- package/dist/manager.d.ts.map +1 -1
- package/dist/manager.js +185 -62
- package/dist/migrationStore.d.ts.map +1 -1
- package/dist/migrationStore.js +67 -0
- package/dist/nurse.d.ts +20 -0
- package/dist/nurse.d.ts.map +1 -0
- package/dist/nurse.js +287 -0
- package/dist/plans.d.ts +19 -0
- package/dist/plans.d.ts.map +1 -1
- package/dist/plans.js +470 -43
- package/dist/registrar.d.ts +16 -0
- package/dist/registrar.d.ts.map +1 -0
- package/dist/registrar.js +221 -0
- package/dist/schema.json +480 -1
- package/dist/telemetry.d.ts +65 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +349 -0
- package/dist/types.d.ts +201 -3
- package/dist/types.d.ts.map +1 -1
- package/package.json +14 -5
package/dist/manager.js
CHANGED
|
@@ -6,11 +6,13 @@ import * as Attorney from "./attorney.js";
|
|
|
6
6
|
import { untracked } from "./activity.js";
|
|
7
7
|
import { TRANSACTION_ROLLBACK_TIMEOUT_MS } from "./db.js";
|
|
8
8
|
import * as plans from "./plans.js";
|
|
9
|
+
import { percentile } from "./latency.js";
|
|
9
10
|
import * as timekeeper from "./timekeeper.js";
|
|
10
11
|
import { resolveWithinSeconds } from "./tools.js";
|
|
11
12
|
import * as types from "./types.js";
|
|
12
13
|
import Worker from "./worker.js";
|
|
13
14
|
import { JobSpy } from "./spy.js";
|
|
15
|
+
import Telemetry, {} from "./telemetry.js";
|
|
14
16
|
const INTERNAL_QUEUES = Object.values(timekeeper.QUEUES).reduce((acc, i) => ({ ...acc, [i]: i }), {});
|
|
15
17
|
// postgres: current transaction is aborted, commands ignored until end of transaction block
|
|
16
18
|
const TRANSACTION_ABORTED = '25P02';
|
|
@@ -80,6 +82,7 @@ const NUMERIC_METADATA_FIELDS = [
|
|
|
80
82
|
];
|
|
81
83
|
// Queue rows (plans.getQueues) return these integer columns as strings on CockroachDB too.
|
|
82
84
|
const NUMERIC_QUEUE_FIELDS = [
|
|
85
|
+
'blockedCount',
|
|
83
86
|
'retryLimit',
|
|
84
87
|
'retryDelay',
|
|
85
88
|
'retryDelayMax',
|
|
@@ -97,7 +100,8 @@ const NUMERIC_QUEUE_FIELDS = [
|
|
|
97
100
|
'createdDelta',
|
|
98
101
|
'completedDelta',
|
|
99
102
|
'failedDelta',
|
|
100
|
-
'deltaSeconds'
|
|
103
|
+
'deltaSeconds',
|
|
104
|
+
'readyOldestSeconds'
|
|
101
105
|
];
|
|
102
106
|
// The gauges shared by live stats and recorded snapshots (the QueueStats shape).
|
|
103
107
|
const STATS_COUNT_FIELDS = [
|
|
@@ -108,6 +112,12 @@ const STATS_COUNT_FIELDS = [
|
|
|
108
112
|
'failedCount',
|
|
109
113
|
'totalCount'
|
|
110
114
|
];
|
|
115
|
+
// A snapshot's histogram, LATENCY_SLOTS counts from a recorded pass or added up over a bucket, with
|
|
116
|
+
// the slots stored as null (no job landed there) handed out as 0. Null when no pass counted it; all
|
|
117
|
+
// zeros when one did and nothing finished. CockroachDB hands integers over as strings.
|
|
118
|
+
function toBins(bins) {
|
|
119
|
+
return Array.isArray(bins) ? bins.map(n => (n == null ? 0 : Number(n))) : null;
|
|
120
|
+
}
|
|
111
121
|
// The throughput counters and the seconds they cover. Only recorded snapshots carry them; see
|
|
112
122
|
// getQueueStats.
|
|
113
123
|
const STATS_DELTA_FIELDS = [
|
|
@@ -187,6 +197,9 @@ class Manager extends EventEmitter {
|
|
|
187
197
|
#localGroupActive;
|
|
188
198
|
#localGroupConfig;
|
|
189
199
|
#localGroupMaxLimit;
|
|
200
|
+
#telemetry;
|
|
201
|
+
// The trace context each fetched job was sent with, kept off the job object handed to the handler.
|
|
202
|
+
#traceContexts;
|
|
190
203
|
constructor(db, config) {
|
|
191
204
|
super();
|
|
192
205
|
this.config = config;
|
|
@@ -205,6 +218,8 @@ class Manager extends EventEmitter {
|
|
|
205
218
|
this.#localGroupActive = new Map();
|
|
206
219
|
this.#localGroupConfig = new Map();
|
|
207
220
|
this.#localGroupMaxLimit = new Map();
|
|
221
|
+
this.#telemetry = new Telemetry(config.openTelemetry, config.schema, () => this.queues);
|
|
222
|
+
this.#traceContexts = new WeakMap();
|
|
208
223
|
}
|
|
209
224
|
getSpy(name) {
|
|
210
225
|
if (!this.config.__test__enableSpies) {
|
|
@@ -352,6 +367,7 @@ class Manager extends EventEmitter {
|
|
|
352
367
|
// (each output carried per-id via a JSON recordset), so batch size never drives the statement
|
|
353
368
|
// count. Any batch job the handler omits (or returns with an invalid shape) is failed with a
|
|
354
369
|
// descriptive error so it retries / dead-letters per queue config.
|
|
370
|
+
// Resolves with the error the whole batch was failed with, or undefined.
|
|
355
371
|
async #settlePerJob(name, jobs, result) {
|
|
356
372
|
if (!Array.isArray(result)) {
|
|
357
373
|
// The handler opted into perJobResults but did not return an array: a contract violation.
|
|
@@ -359,7 +375,7 @@ class Manager extends EventEmitter {
|
|
|
359
375
|
const err = new Error('perJobResults handler must resolve with an array of job results');
|
|
360
376
|
await this.fail(name, jobs, err);
|
|
361
377
|
await this.#trackJobsFailed(name, jobs, err);
|
|
362
|
-
return;
|
|
378
|
+
return err;
|
|
363
379
|
}
|
|
364
380
|
// Index the handler's dispositions by job id, keeping only valid entries that reference a job
|
|
365
381
|
// from this batch. Last write wins on duplicate ids.
|
|
@@ -391,9 +407,16 @@ class Manager extends EventEmitter {
|
|
|
391
407
|
}
|
|
392
408
|
}
|
|
393
409
|
const items = (entries) => entries.map(({ job, output }) => ({ id: job.id, retryCount: job.retryCount, output }));
|
|
394
|
-
const
|
|
395
|
-
const
|
|
396
|
-
|
|
410
|
+
const ids = (entries) => entries.map(({ job }) => job.id);
|
|
411
|
+
const completedIds = completed.length > 0
|
|
412
|
+
? await this.#telemetry.settle('complete', name, ids(completed), () => this.#completeWithOutputs(name, items(completed)))
|
|
413
|
+
: null;
|
|
414
|
+
const failedIds = failed.length > 0
|
|
415
|
+
? await this.#telemetry.settle('fail', name, ids(failed), () => this.#failWithOutputs(name, items(failed)))
|
|
416
|
+
: null;
|
|
417
|
+
const deadLetteredIds = deadLettered.length > 0
|
|
418
|
+
? await this.#telemetry.settle('fail', name, ids(deadLettered), () => this.#failWithOutputs(name, items(deadLettered), true))
|
|
419
|
+
: null;
|
|
397
420
|
// Only the jobs each statement actually settled: the attempt fence leaves a job whose claim
|
|
398
421
|
// lapsed alone, and recording it would tell a spy it settled when another attempt holds it.
|
|
399
422
|
const landed = (entries, result) => {
|
|
@@ -509,6 +532,8 @@ class Manager extends EventEmitter {
|
|
|
509
532
|
* same as any other worker's. Because the claim is outside the transaction, the jobs stay
|
|
510
533
|
* visibly `active` throughout, which is what keeps heartbeats, `expireInSeconds`, and another
|
|
511
534
|
* instance's supervisor working on them as usual.
|
|
535
|
+
*
|
|
536
|
+
* Resolves with the error the batch was failed with, or undefined when it completed.
|
|
512
537
|
*/
|
|
513
538
|
async #processJobs(name, jobs, callback, worker, heartbeatRefreshSeconds, perJobResults = false, transactional = false, transactionTimeoutSeconds) {
|
|
514
539
|
const jobIds = jobs.map(job => job.id);
|
|
@@ -569,6 +594,9 @@ class Manager extends EventEmitter {
|
|
|
569
594
|
let completedAffected = 0;
|
|
570
595
|
let failedError;
|
|
571
596
|
let didFail = false;
|
|
597
|
+
// A perJobResults batch #settlePerJob failed as a whole. Kept apart from failedError, since
|
|
598
|
+
// #settlePerJob has already failed and tracked those jobs itself.
|
|
599
|
+
let perJobError;
|
|
572
600
|
// Only for a transactional worker, and only from the begin below until it settles. rollback()
|
|
573
601
|
// is idempotent, so the catch can settle it without tracking whether the commit got there
|
|
574
602
|
// first.
|
|
@@ -619,7 +647,7 @@ class Manager extends EventEmitter {
|
|
|
619
647
|
// #settlePerJob settles each job individually and does its own (synchronous,
|
|
620
648
|
// lookup-free) spy tracking via #trackJobsSettled, so the deferred tracker below
|
|
621
649
|
// is skipped for this path.
|
|
622
|
-
await this.#settlePerJob(name, jobs, result);
|
|
650
|
+
perJobError = await this.#settlePerJob(name, jobs, result);
|
|
623
651
|
}
|
|
624
652
|
else {
|
|
625
653
|
// Read out before the completion below, which goes through the same complete() and would
|
|
@@ -676,6 +704,7 @@ class Manager extends EventEmitter {
|
|
|
676
704
|
await this.#trackJobsCompleted(name, jobs, completedResult, completedAffected);
|
|
677
705
|
}
|
|
678
706
|
}
|
|
707
|
+
return didFail ? (failedError ?? new Error('handler rejected without a reason')) : perJobError;
|
|
679
708
|
}
|
|
680
709
|
/**
|
|
681
710
|
* Gives the handler's transaction a deadline the database enforces.
|
|
@@ -822,6 +851,10 @@ class Manager extends EventEmitter {
|
|
|
822
851
|
}
|
|
823
852
|
async start() {
|
|
824
853
|
this.stopped = false;
|
|
854
|
+
if (this.#telemetry.enabled) {
|
|
855
|
+
const { rows } = await this.db.executeSql(plans.currentDatabase());
|
|
856
|
+
this.#telemetry.setDatabase(rows[0].name);
|
|
857
|
+
}
|
|
825
858
|
this.queueCacheInterval = this.config.clock.setInterval(() => this.onCacheQueues({ emit: true }), this.config.queueCacheIntervalSeconds * 1000);
|
|
826
859
|
this.wipInterval = this.config.clock.setInterval(() => {
|
|
827
860
|
const now = this.config.clock.now();
|
|
@@ -835,12 +868,14 @@ class Manager extends EventEmitter {
|
|
|
835
868
|
}
|
|
836
869
|
}, 2000);
|
|
837
870
|
await this.onCacheQueues();
|
|
871
|
+
this.#telemetry.observeQueues();
|
|
838
872
|
}
|
|
839
873
|
async onCacheQueues({ emit = false } = {}) {
|
|
840
874
|
try {
|
|
841
875
|
assert(!this.config.__test__throw_queueCache, 'test error');
|
|
842
876
|
const queues = await this.getQueues();
|
|
843
877
|
this.queues = queues.reduce((acc, i) => { acc[i.name] = i; return acc; }, {});
|
|
878
|
+
this.#telemetry.refreshInstruments();
|
|
844
879
|
}
|
|
845
880
|
catch (error) {
|
|
846
881
|
emit && this.emit(events.error, { ...error, message: error.message, stack: error.stack });
|
|
@@ -863,10 +898,24 @@ class Manager extends EventEmitter {
|
|
|
863
898
|
if (this.queues)
|
|
864
899
|
delete this.queues[name];
|
|
865
900
|
}
|
|
901
|
+
// Replaces a queue's cache entry with its row as it stands, rather than evicting it, so a queue
|
|
902
|
+
// created again or updated stays in the cache and the queue gauge while still picking up what
|
|
903
|
+
// changed: new options, or a table that changed under it (deleted and recreated elsewhere with
|
|
904
|
+
// another partition setting).
|
|
905
|
+
async #reloadQueueCache(name) {
|
|
906
|
+
if (!this.queues)
|
|
907
|
+
return;
|
|
908
|
+
const queue = await this.getQueue(name);
|
|
909
|
+
if (queue)
|
|
910
|
+
this.queues[name] = queue;
|
|
911
|
+
else
|
|
912
|
+
this.#evictQueueCache(name);
|
|
913
|
+
}
|
|
866
914
|
async stop() {
|
|
867
915
|
this.stopped = true;
|
|
868
916
|
this.config.clock.clearInterval(this.queueCacheInterval);
|
|
869
917
|
this.config.clock.clearInterval(this.wipInterval);
|
|
918
|
+
this.#telemetry.unobserveQueues();
|
|
870
919
|
// offWork stops every worker on a queue, so iterate queue names rather than workers - otherwise
|
|
871
920
|
// a localConcurrency of N re-stops all N workers N times and registers N pending cleanups.
|
|
872
921
|
const names = new Set([...this.workers.values()]
|
|
@@ -952,7 +1001,12 @@ class Manager extends EventEmitter {
|
|
|
952
1001
|
const ignoreGroups = localGroupConcurrency != null
|
|
953
1002
|
? this.#getGroupsAtLocalCapacity(name)
|
|
954
1003
|
: undefined;
|
|
955
|
-
return this
|
|
1004
|
+
return this.#fetch(name, { batchSize, includeMetadata, priority, orderByCreatedOn, groupConcurrency, ignoreGroups, minPriority, maxPriority });
|
|
1005
|
+
};
|
|
1006
|
+
// Counted here rather than on fetch: jobs past localGroupConcurrency are restored, not delivered.
|
|
1007
|
+
const processBatch = (batch, worker) => {
|
|
1008
|
+
this.#telemetry.consumed(name, batch.length);
|
|
1009
|
+
return this.#telemetry.process(name, batch, job => this.#traceContexts.get(job), () => this.#processJobs(name, batch, callback, worker, heartbeatRefreshSeconds, perJobResults, transactional, transactionTimeoutSeconds));
|
|
956
1010
|
};
|
|
957
1011
|
const onFetch = async (jobs) => {
|
|
958
1012
|
if (!jobs.length)
|
|
@@ -965,7 +1019,7 @@ class Manager extends EventEmitter {
|
|
|
965
1019
|
const worker = this.workers.get(workerId);
|
|
966
1020
|
// Skip all in-memory group tracking when localGroupConcurrency is not enabled
|
|
967
1021
|
if (localGroupConcurrency == null) {
|
|
968
|
-
await
|
|
1022
|
+
await processBatch(jobs, worker);
|
|
969
1023
|
}
|
|
970
1024
|
else {
|
|
971
1025
|
const { allowed, excess, groupedJobs } = this.#trackLocalGroupStart(name, jobs);
|
|
@@ -980,7 +1034,7 @@ class Manager extends EventEmitter {
|
|
|
980
1034
|
worker.jobs = allowed;
|
|
981
1035
|
}
|
|
982
1036
|
if (allowed.length > 0) {
|
|
983
|
-
await
|
|
1037
|
+
await processBatch(allowed, worker);
|
|
984
1038
|
}
|
|
985
1039
|
}
|
|
986
1040
|
finally {
|
|
@@ -1164,6 +1218,10 @@ class Manager extends EventEmitter {
|
|
|
1164
1218
|
}
|
|
1165
1219
|
async publish(event, data, options) {
|
|
1166
1220
|
assert(event, 'Missing required argument');
|
|
1221
|
+
// Counts no jobs of its own: each send() below records the job it creates.
|
|
1222
|
+
await this.#telemetry.send('publish', event, 0, () => this.#publish(event, data, options));
|
|
1223
|
+
}
|
|
1224
|
+
async #publish(event, data, options) {
|
|
1167
1225
|
const sql = plans.getQueuesForEvent(this.config.schema);
|
|
1168
1226
|
const { rows } = await this.db.executeSql(sql, [event]);
|
|
1169
1227
|
const results = await Promise.allSettled(rows.map(({ name }) => this.send(name, data, options)));
|
|
@@ -1230,9 +1288,12 @@ class Manager extends EventEmitter {
|
|
|
1230
1288
|
};
|
|
1231
1289
|
}
|
|
1232
1290
|
async createJob(request) {
|
|
1291
|
+
return this.#telemetry.send('send', request.name, 1, carrier => this.#createJob(request, carrier), id => id ? [id] : null);
|
|
1292
|
+
}
|
|
1293
|
+
async #createJob(request, traceContext) {
|
|
1233
1294
|
const { name, data = null, options = {} } = request;
|
|
1234
1295
|
const { db: wrapper, singletonSeconds, singletonNextSlot } = options;
|
|
1235
|
-
const job = this.#toJobPayload(name, data, options);
|
|
1296
|
+
const job = { ...this.#toJobPayload(name, data, options), __traceContext: traceContext };
|
|
1236
1297
|
const db = wrapper || this.db;
|
|
1237
1298
|
const { table, policy, notify } = await this.getQueueCache(name);
|
|
1238
1299
|
if (policy === plans.QUEUE_POLICIES.key_strict_fifo && !job.singletonKey) {
|
|
@@ -1309,9 +1370,14 @@ class Manager extends EventEmitter {
|
|
|
1309
1370
|
}
|
|
1310
1371
|
async upsert(...args) {
|
|
1311
1372
|
const request = Attorney.checkUpdateArgs(args, { upsert: true });
|
|
1373
|
+
Attorney.assertQueueName(request.name);
|
|
1374
|
+
// Only an insert counts as a send, and only an inserted job stores the trace context: an
|
|
1375
|
+
// updated job keeps the trace of the send that created it.
|
|
1376
|
+
return this.#telemetry.send('upsert', request.name, result => result.inserted, carrier => this.#upsert(request, carrier), result => result.jobs);
|
|
1377
|
+
}
|
|
1378
|
+
async #upsert(request, traceContext) {
|
|
1312
1379
|
const { name, data } = request;
|
|
1313
1380
|
const opts = (request.options ?? {});
|
|
1314
|
-
Attorney.assertQueueName(name);
|
|
1315
1381
|
const db = this.assertDb(opts);
|
|
1316
1382
|
const { table, policy, notify } = await this.getQueueCache(name);
|
|
1317
1383
|
const by = opts.id ? 'id' : 'singletonKey';
|
|
@@ -1326,7 +1392,7 @@ class Manager extends EventEmitter {
|
|
|
1326
1392
|
const insertSql = plans.insertJobs(this.config.schema, { table, name, returnId: true, notify: notifyEnabled });
|
|
1327
1393
|
const job = this.#toUpdatePayload(data, opts);
|
|
1328
1394
|
const updatePayload = JSON.stringify(job);
|
|
1329
|
-
const insertPayload = JSON.stringify([job]);
|
|
1395
|
+
const insertPayload = JSON.stringify([{ ...job, __traceContext: traceContext }]);
|
|
1330
1396
|
const result = await this.ensureTransaction(db, async (tx) => {
|
|
1331
1397
|
const { rows: updated } = await tx.executeSql(updateSql, [updatePayload]);
|
|
1332
1398
|
if (updated.length) {
|
|
@@ -1363,6 +1429,9 @@ class Manager extends EventEmitter {
|
|
|
1363
1429
|
// statement nor kept on the objects below, so this stays the raw path it has always been.
|
|
1364
1430
|
options = {}) {
|
|
1365
1431
|
assert(Array.isArray(jobs), 'jobs argument should be an array');
|
|
1432
|
+
return this.#telemetry.send('insert', name, jobs.length, carrier => this.#insert(name, jobs, options, carrier));
|
|
1433
|
+
}
|
|
1434
|
+
async #insert(name, jobs, options, traceContext) {
|
|
1366
1435
|
const slots = options.__singletonSlots === true;
|
|
1367
1436
|
const seenIds = new Set();
|
|
1368
1437
|
for (const job of jobs) {
|
|
@@ -1394,6 +1463,8 @@ class Manager extends EventEmitter {
|
|
|
1394
1463
|
const dataById = spy ? new Map() : undefined;
|
|
1395
1464
|
const insertPayload = jobs.map(j => {
|
|
1396
1465
|
const { blocked, blocking, pendingDependencies, group, __singletonSlot, ...rest } = j;
|
|
1466
|
+
// Overwrites any __traceContext the caller passed.
|
|
1467
|
+
Object.assign(rest, { __traceContext: traceContext });
|
|
1397
1468
|
// Reattached only for the caller that asked for the column, so a public insert() drops the
|
|
1398
1469
|
// field rather than handing an unvalidated value to a timestamp cast.
|
|
1399
1470
|
if (slots && __singletonSlot !== undefined) {
|
|
@@ -1441,6 +1512,11 @@ class Manager extends EventEmitter {
|
|
|
1441
1512
|
}
|
|
1442
1513
|
async flow(jobs, options = {}) {
|
|
1443
1514
|
Attorney.validateFlowJobs(jobs);
|
|
1515
|
+
const queues = new Set(jobs.map(job => job.name));
|
|
1516
|
+
const destination = queues.size === 1 ? jobs[0].name : null;
|
|
1517
|
+
return this.#telemetry.send('flow', destination, jobs.length, carrier => this.#flow(jobs, options, carrier), refToId => Object.values(refToId));
|
|
1518
|
+
}
|
|
1519
|
+
async #flow(jobs, options, traceContext) {
|
|
1444
1520
|
// validate and normalize each job's options the same way send()/insert() do
|
|
1445
1521
|
const flowJobs = jobs.map(job => ({
|
|
1446
1522
|
...job,
|
|
@@ -1503,7 +1579,8 @@ class Manager extends EventEmitter {
|
|
|
1503
1579
|
deadLetter: j.options?.deadLetter ?? undefined,
|
|
1504
1580
|
blocked: dependencyCount > 0 || undefined,
|
|
1505
1581
|
blocking: parentRefs.has(j.ref) || undefined,
|
|
1506
|
-
pendingDependencies: dependencyCount || undefined
|
|
1582
|
+
pendingDependencies: dependencyCount || undefined,
|
|
1583
|
+
__traceContext: traceContext
|
|
1507
1584
|
};
|
|
1508
1585
|
});
|
|
1509
1586
|
statements.push(plans.insertFlowJobs(this.config.schema, { table, name: queueName }, insertPayload));
|
|
@@ -1559,6 +1636,9 @@ class Manager extends EventEmitter {
|
|
|
1559
1636
|
}
|
|
1560
1637
|
async fetch(name, options = {}) {
|
|
1561
1638
|
Attorney.checkFetchArgs(name, options);
|
|
1639
|
+
return this.#telemetry.receive(name, () => this.#fetch(name, options), job => this.#traceContexts.get(job));
|
|
1640
|
+
}
|
|
1641
|
+
async #fetch(name, options) {
|
|
1562
1642
|
this.#warnDeprecatedFetchOptions(options);
|
|
1563
1643
|
const db = this.assertDb(options);
|
|
1564
1644
|
const { table, policy, singletonsActive } = await this.getQueueCache(name);
|
|
@@ -1569,7 +1649,8 @@ class Manager extends EventEmitter {
|
|
|
1569
1649
|
name,
|
|
1570
1650
|
policy,
|
|
1571
1651
|
limit: options.batchSize || 1,
|
|
1572
|
-
ignoreSingletons: singletonsActive
|
|
1652
|
+
ignoreSingletons: singletonsActive,
|
|
1653
|
+
includeTraceContext: this.#telemetry.enabled
|
|
1573
1654
|
};
|
|
1574
1655
|
const query = plans.fetchNextJob(fetchOptions, this.config.noSkipLocked);
|
|
1575
1656
|
let result;
|
|
@@ -1586,6 +1667,13 @@ class Manager extends EventEmitter {
|
|
|
1586
1667
|
throw err;
|
|
1587
1668
|
}
|
|
1588
1669
|
const rows = result?.rows || [];
|
|
1670
|
+
for (const row of rows) {
|
|
1671
|
+
// A db adapter may hand jsonb back unparsed.
|
|
1672
|
+
const carrier = typeof row.__traceContext === 'string' ? JSON.parse(row.__traceContext) : row.__traceContext;
|
|
1673
|
+
delete row.__traceContext;
|
|
1674
|
+
if (carrier)
|
|
1675
|
+
this.#traceContexts.set(row, carrier);
|
|
1676
|
+
}
|
|
1589
1677
|
// Even a minimal fetch (JOB_COLUMNS_MIN) returns numeric fields like expireInSeconds and
|
|
1590
1678
|
// heartbeatSeconds, so normalize regardless of includeMetadata.
|
|
1591
1679
|
return this.#numericJobFields(rows);
|
|
@@ -1638,21 +1726,23 @@ class Manager extends EventEmitter {
|
|
|
1638
1726
|
const db = this.assertDb(options);
|
|
1639
1727
|
const { ids, attempts: fetched } = this.mapAttemptArg(id, 'complete');
|
|
1640
1728
|
const attempts = fetched ?? this.#handlerAttempts(options, ids);
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1729
|
+
return this.#telemetry.settle('complete', name, ids, async () => {
|
|
1730
|
+
const { table } = await this.getQueueCache(name);
|
|
1731
|
+
const outputData = this.mapCompletionDataArg(data);
|
|
1732
|
+
let response;
|
|
1733
|
+
// noMultiMutationCte: split the dependency-unblocking into a separate statement to
|
|
1734
|
+
// avoid CockroachDB's multi-mutation CTE limitation (completeJobs updates two tables).
|
|
1735
|
+
if (this.config.noMultiMutationCte) {
|
|
1736
|
+
response = await this.completeDistributed(name, ids, outputData, table, db, options.includeQueued, attempts);
|
|
1737
|
+
}
|
|
1738
|
+
else {
|
|
1739
|
+
const sql = plans.completeJobs(this.config.schema, table, options.includeQueued, !!attempts);
|
|
1740
|
+
const result = await db.executeSql(sql, attempts ? [name, ids, outputData, plans.attemptPairs(ids, attempts)] : [name, ids, outputData]);
|
|
1741
|
+
response = this.mapCommandResponse(ids, result);
|
|
1742
|
+
}
|
|
1743
|
+
this.#trackHandlerSettle(options, response);
|
|
1744
|
+
return response;
|
|
1745
|
+
});
|
|
1656
1746
|
}
|
|
1657
1747
|
// Distributed complete/fail need several statements run atomically. When we own the pooled
|
|
1658
1748
|
// connection we pin a single client via withTransaction(); when the caller supplied their own
|
|
@@ -1677,22 +1767,24 @@ class Manager extends EventEmitter {
|
|
|
1677
1767
|
const db = this.assertDb(options);
|
|
1678
1768
|
const { ids, attempts: fetched } = this.mapAttemptArg(id, 'fail');
|
|
1679
1769
|
const attempts = fetched ?? this.#handlerAttempts(options, ids);
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1770
|
+
return this.#telemetry.settle('fail', name, ids, async () => {
|
|
1771
|
+
const { table } = await this.getQueueCache(name);
|
|
1772
|
+
const outputData = this.mapCompletionDataArg(data);
|
|
1773
|
+
let response;
|
|
1774
|
+
// noMultiMutationCte: use separate queries to avoid CockroachDB's multi-mutation CTE limitation.
|
|
1775
|
+
// The delete and re-insert run in a single transaction (see ensureTransaction) so the
|
|
1776
|
+
// job cannot be lost between the two statements.
|
|
1777
|
+
if (this.config.noMultiMutationCte) {
|
|
1778
|
+
response = await this.failDistributed(name, ids, outputData, table, db, attempts);
|
|
1779
|
+
}
|
|
1780
|
+
else {
|
|
1781
|
+
const sql = plans.failJobsById(this.config.schema, table, !!attempts);
|
|
1782
|
+
const result = await db.executeSql(sql, attempts ? [name, ids, outputData, plans.attemptPairs(ids, attempts)] : [name, ids, outputData]);
|
|
1783
|
+
response = this.mapCommandResponse(ids, result);
|
|
1784
|
+
}
|
|
1785
|
+
this.#trackHandlerSettle(options, response);
|
|
1786
|
+
return response;
|
|
1787
|
+
});
|
|
1696
1788
|
}
|
|
1697
1789
|
async failDistributed(name, ids, outputData, table, db, attempts) {
|
|
1698
1790
|
// CockroachDB doesn't support multi-mutation CTEs, but does support transactions, so the
|
|
@@ -1800,6 +1892,7 @@ class Manager extends EventEmitter {
|
|
|
1800
1892
|
const data = toJsonText(job.data);
|
|
1801
1893
|
const output = toJsonText(jobOutput);
|
|
1802
1894
|
const sourceOutput = toJsonText(job.source_output);
|
|
1895
|
+
const traceContext = toJsonText(job.trace_context);
|
|
1803
1896
|
// forceTerminal (perJobResults `deadletter`) skips retries so the job fails terminally and
|
|
1804
1897
|
// routes straight to the dead letter queue below.
|
|
1805
1898
|
const canRetry = !forceTerminal && retryCount < retryLimit;
|
|
@@ -1828,7 +1921,7 @@ class Manager extends EventEmitter {
|
|
|
1828
1921
|
job.deletion_seconds, createdOn, null, keepUntil, job.policy,
|
|
1829
1922
|
output, job.dead_letter,
|
|
1830
1923
|
null, job.heartbeat_seconds, job.blocked, job.blocking, job.pending_dependencies,
|
|
1831
|
-
job.source_name, job.source_id, sourceCreatedOn, job.source_retry_count, sourceOutput, job.source_root_id
|
|
1924
|
+
job.source_name, job.source_id, sourceCreatedOn, job.source_retry_count, sourceOutput, job.source_root_id, traceContext
|
|
1832
1925
|
]);
|
|
1833
1926
|
// The retry insert can be dropped by ON CONFLICT when the queue policy (e.g. stately,
|
|
1834
1927
|
// singleton, key_strict_fifo) already has a non-terminal job. Mirror the failed_jobs
|
|
@@ -1843,11 +1936,11 @@ class Manager extends EventEmitter {
|
|
|
1843
1936
|
job.deletion_seconds, createdOn, new Date(this.config.clock.now()), keepUntil, job.policy,
|
|
1844
1937
|
output, job.dead_letter,
|
|
1845
1938
|
null, job.heartbeat_seconds, job.blocked, job.blocking, job.pending_dependencies,
|
|
1846
|
-
job.source_name, job.source_id, sourceCreatedOn, job.source_retry_count, sourceOutput, job.source_root_id
|
|
1939
|
+
job.source_name, job.source_id, sourceCreatedOn, job.source_retry_count, sourceOutput, job.source_root_id, traceContext
|
|
1847
1940
|
]);
|
|
1848
1941
|
// Insert to dead letter queue if failed and has dead_letter configured
|
|
1849
1942
|
if (job.dead_letter) {
|
|
1850
|
-
await tx.executeSql(dlqSql, [job.dead_letter, data, output, job.name, job.id, createdOn, job.retry_count, job.singleton_key, job.priority, job.group_id, job.group_tier, job.source_root_id]);
|
|
1943
|
+
await tx.executeSql(dlqSql, [job.dead_letter, data, output, job.name, job.id, createdOn, job.retry_count, job.singleton_key, job.priority, job.group_id, job.group_tier, job.source_root_id, traceContext]);
|
|
1851
1944
|
}
|
|
1852
1945
|
}
|
|
1853
1946
|
count++;
|
|
@@ -1860,12 +1953,14 @@ class Manager extends EventEmitter {
|
|
|
1860
1953
|
const db = this.assertDb(options);
|
|
1861
1954
|
const { ids, attempts: fetched } = this.mapAttemptArg(id, 'deleteJob');
|
|
1862
1955
|
const attempts = fetched ?? this.#handlerAttempts(options, ids);
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1956
|
+
return this.#telemetry.settle('delete', name, ids, async () => {
|
|
1957
|
+
const { table } = await this.getQueueCache(name);
|
|
1958
|
+
const sql = plans.deleteJobsById(this.config.schema, table, !!attempts);
|
|
1959
|
+
const result = await db.executeSql(sql, attempts ? [name, ids, plans.attemptPairs(ids, attempts)] : [name, ids]);
|
|
1960
|
+
const response = this.mapCommandResponse(ids, result);
|
|
1961
|
+
this.#trackHandlerSettle(options, response);
|
|
1962
|
+
return response;
|
|
1963
|
+
});
|
|
1869
1964
|
}
|
|
1870
1965
|
// The filter half of redrive and previewRedrive, validated once and in the parameter order
|
|
1871
1966
|
// plans.redriveWhere expects ($2 through $6).
|
|
@@ -1956,12 +2051,14 @@ class Manager extends EventEmitter {
|
|
|
1956
2051
|
const db = this.assertDb(options);
|
|
1957
2052
|
const { ids, attempts: fetched } = this.mapAttemptArg(id, 'cancel');
|
|
1958
2053
|
const attempts = fetched ?? this.#handlerAttempts(options, ids);
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
2054
|
+
return this.#telemetry.settle('cancel', name, ids, async () => {
|
|
2055
|
+
const { table } = await this.getQueueCache(name);
|
|
2056
|
+
const sql = plans.cancelJobs(this.config.schema, table, !!attempts);
|
|
2057
|
+
const result = await db.executeSql(sql, attempts ? [name, ids, plans.attemptPairs(ids, attempts)] : [name, ids]);
|
|
2058
|
+
const response = this.mapCommandResponse(ids, result);
|
|
2059
|
+
this.#trackHandlerSettle(options, response);
|
|
2060
|
+
return response;
|
|
2061
|
+
});
|
|
1965
2062
|
}
|
|
1966
2063
|
async resume(name, id, options = {}) {
|
|
1967
2064
|
Attorney.assertQueueName(name);
|
|
@@ -2013,7 +2110,7 @@ class Manager extends EventEmitter {
|
|
|
2013
2110
|
}
|
|
2014
2111
|
const sql = plans.createQueue(this.config.schema, name, { ...options, policy }, this.config.noAdvisoryLocks);
|
|
2015
2112
|
await this.db.executeSql(sql);
|
|
2016
|
-
this.#
|
|
2113
|
+
await this.#reloadQueueCache(name);
|
|
2017
2114
|
}
|
|
2018
2115
|
async getBlockedKeys(name) {
|
|
2019
2116
|
Attorney.assertQueueName(name);
|
|
@@ -2043,6 +2140,11 @@ class Manager extends EventEmitter {
|
|
|
2043
2140
|
}
|
|
2044
2141
|
}
|
|
2045
2142
|
}
|
|
2143
|
+
// Every backend: the histograms' empty slots are stored as null and handed out as 0.
|
|
2144
|
+
for (const row of rows) {
|
|
2145
|
+
row.waitBins = toBins(row.waitBins);
|
|
2146
|
+
row.runBins = toBins(row.runBins);
|
|
2147
|
+
}
|
|
2046
2148
|
return rows;
|
|
2047
2149
|
}
|
|
2048
2150
|
async updateQueue(name, options = {}) {
|
|
@@ -2064,7 +2166,7 @@ class Manager extends EventEmitter {
|
|
|
2064
2166
|
}
|
|
2065
2167
|
const sql = plans.updateQueue(this.config.schema);
|
|
2066
2168
|
await this.db.executeSql(sql, [name, options]);
|
|
2067
|
-
this.#
|
|
2169
|
+
await this.#reloadQueueCache(name);
|
|
2068
2170
|
}
|
|
2069
2171
|
async getQueue(name) {
|
|
2070
2172
|
const rows = await this.getQueues([name]);
|
|
@@ -2130,6 +2232,10 @@ class Manager extends EventEmitter {
|
|
|
2130
2232
|
// getQueue(name).
|
|
2131
2233
|
async getQueueStats(name, options = {}) {
|
|
2132
2234
|
Attorney.assertQueueName(name);
|
|
2235
|
+
assert(options.percentiles === undefined || (Array.isArray(options.percentiles) && options.percentiles.length > 0 &&
|
|
2236
|
+
options.percentiles.every(p => typeof p === 'number' && p >= 1 && p <= 100)), 'getQueueStats: percentiles must be a non-empty array of percents from 1 to 100, such as [50, 95]');
|
|
2237
|
+
// Each value once, in the order first asked for.
|
|
2238
|
+
const percentiles = options.percentiles && [...new Set(options.percentiles)];
|
|
2133
2239
|
const isCockroach = this.config.backend === 'cockroachdb';
|
|
2134
2240
|
// `counted` is true for recorded snapshots. The cache path serves only gauges: the queue table's
|
|
2135
2241
|
// counters describe the last pass that counted, not this reading.
|
|
@@ -2150,6 +2256,9 @@ class Manager extends EventEmitter {
|
|
|
2150
2256
|
createdDelta: null,
|
|
2151
2257
|
deltaSeconds: null,
|
|
2152
2258
|
deltaOn: null,
|
|
2259
|
+
waitBins: null,
|
|
2260
|
+
runBins: null,
|
|
2261
|
+
readyOldestSeconds: null,
|
|
2153
2262
|
capturedOn: row?.capturedOn ?? new Date(this.config.clock.now())
|
|
2154
2263
|
};
|
|
2155
2264
|
for (const field of counted ? [...STATS_COUNT_FIELDS, ...STATS_DELTA_FIELDS] : STATS_COUNT_FIELDS) {
|
|
@@ -2161,6 +2270,20 @@ class Manager extends EventEmitter {
|
|
|
2161
2270
|
// The end of the interval the counters cover, handed on as the row holds it, like capturedOn.
|
|
2162
2271
|
if (counted && row?.deltaOn != null)
|
|
2163
2272
|
snapshot.deltaOn = row.deltaOn;
|
|
2273
|
+
if (counted) {
|
|
2274
|
+
snapshot.waitBins = toBins(row?.waitBins);
|
|
2275
|
+
snapshot.runBins = toBins(row?.runBins);
|
|
2276
|
+
if (row?.readyOldestSeconds != null)
|
|
2277
|
+
snapshot.readyOldestSeconds = Number(row.readyOldestSeconds);
|
|
2278
|
+
}
|
|
2279
|
+
// Read from this snapshot's (or bucket's) own histograms, so they are null wherever those are.
|
|
2280
|
+
if (percentiles) {
|
|
2281
|
+
snapshot.percentiles = percentiles.map(p => ({
|
|
2282
|
+
p,
|
|
2283
|
+
waitSeconds: percentile(snapshot.waitBins, p),
|
|
2284
|
+
runSeconds: percentile(snapshot.runBins, p)
|
|
2285
|
+
}));
|
|
2286
|
+
}
|
|
2164
2287
|
return snapshot;
|
|
2165
2288
|
};
|
|
2166
2289
|
if (this.config.persistQueueStats) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrationStore.d.ts","sourceRoot":"","sources":["../src/migrationStore.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,KAAK,MAAM,YAAY,CAAA;AAKnC,UAAU,cAAc;IACtB,WAAW,CAAC,EAAE,OAAO,CAAA;IAKrB,eAAe,CAAC,EAAE,KAAK,CAAC,kBAAkB,EAAE,CAAA;CAC7C;AAoGD,UAAU,iBAAiB;IACzB,GAAG,EAAE,MAAM,CAAA;IACX,UAAU,EAAE,MAAM,EAAE,CAAA;CACrB;AASD,iBAAS,QAAQ,CAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,EAAE,EAAE,eAAe,CAAC,EAAE,OAAO,UAsB5G;AAED,iBAAS,IAAI,CAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,EAAE,EAAE,eAAe,CAAC,EAAE,OAAO,UAQxG;AAKD,iBAAS,eAAe,CAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,EAAE,EAAE,eAAe,CAAC,EAAE,OAAO,EAAE,OAAO,GAAE,cAAmB,GAAG,iBAAiB,CAiDrK;AAKD,iBAAS,OAAO,CAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,EAAE,EAAE,eAAe,CAAC,EAAE,OAAO,EAAE,OAAO,GAAE,cAAmB,UAIzI;AAgoCD,iBAAS,aAAa,CAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAE9C;AAID,iBAAS,eAAe,CAAE,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,0BAA0B,EAAE,QAAQ,GAAG,qBAAqB,GAAG,mBAAmB,GAAG,qBAAqB,CAAC,GAAG,KAAK,CAAC,SAAS,EAAE,CAE3K;AAOD,iBAAS,MAAM,CAAE,MAAM,EAAE,MAAM,EAAE,cAAc,UAAQ,EAAE,UAAU,UAAQ,EAAE,mBAAmB,UAAQ,GAAG,KAAK,CAAC,SAAS,EAAE,
|
|
1
|
+
{"version":3,"file":"migrationStore.d.ts","sourceRoot":"","sources":["../src/migrationStore.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,KAAK,MAAM,YAAY,CAAA;AAKnC,UAAU,cAAc;IACtB,WAAW,CAAC,EAAE,OAAO,CAAA;IAKrB,eAAe,CAAC,EAAE,KAAK,CAAC,kBAAkB,EAAE,CAAA;CAC7C;AAoGD,UAAU,iBAAiB;IACzB,GAAG,EAAE,MAAM,CAAA;IACX,UAAU,EAAE,MAAM,EAAE,CAAA;CACrB;AASD,iBAAS,QAAQ,CAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,EAAE,EAAE,eAAe,CAAC,EAAE,OAAO,UAsB5G;AAED,iBAAS,IAAI,CAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,EAAE,EAAE,eAAe,CAAC,EAAE,OAAO,UAQxG;AAKD,iBAAS,eAAe,CAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,EAAE,EAAE,eAAe,CAAC,EAAE,OAAO,EAAE,OAAO,GAAE,cAAmB,GAAG,iBAAiB,CAiDrK;AAKD,iBAAS,OAAO,CAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,EAAE,EAAE,eAAe,CAAC,EAAE,OAAO,EAAE,OAAO,GAAE,cAAmB,UAIzI;AAgoCD,iBAAS,aAAa,CAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAE9C;AAID,iBAAS,eAAe,CAAE,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,0BAA0B,EAAE,QAAQ,GAAG,qBAAqB,GAAG,mBAAmB,GAAG,qBAAqB,CAAC,GAAG,KAAK,CAAC,SAAS,EAAE,CAE3K;AAOD,iBAAS,MAAM,CAAE,MAAM,EAAE,MAAM,EAAE,cAAc,UAAQ,EAAE,UAAU,UAAQ,EAAE,mBAAmB,UAAQ,GAAG,KAAK,CAAC,SAAS,EAAE,CA+sB3H;AAED,OAAO,EACL,QAAQ,EACR,IAAI,EACJ,OAAO,EACP,eAAe,EACf,MAAM,EACN,eAAe,EACf,aAAa,GACd,CAAA"}
|
package/dist/migrationStore.js
CHANGED
|
@@ -1963,6 +1963,73 @@ AS $function$
|
|
|
1963
1963
|
DROP COLUMN source_output,
|
|
1964
1964
|
DROP COLUMN source_root_id`
|
|
1965
1965
|
]
|
|
1966
|
+
},
|
|
1967
|
+
{
|
|
1968
|
+
release: '12.36.0',
|
|
1969
|
+
version: 44,
|
|
1970
|
+
previous: 43,
|
|
1971
|
+
// The instance table starts empty: each instance registers on its next start().
|
|
1972
|
+
// blocked_count's constant default adds it without a table rewrite, and existing queues read 0
|
|
1973
|
+
// until the next monitor pass counts them. It goes on the queue row only, not queue_stats, so
|
|
1974
|
+
// the covering index on queue_stats needs no rebuild.
|
|
1975
|
+
// The histogram and ready_oldest_seconds columns are nullable with no default, like the v43
|
|
1976
|
+
// deltas, so no statement rewrites a table, and a snapshot captured before them reads as not
|
|
1977
|
+
// counted rather than as a queue with no waits.
|
|
1978
|
+
// trace_context is nullable with no default too, so adding it rewrites no rows.
|
|
1979
|
+
install: [
|
|
1980
|
+
/* eslint-disable no-restricted-syntax -- column defaults stay on the real clock: every pg-boss write names its timestamps through job_now() */
|
|
1981
|
+
`CREATE TABLE ${schema}.instance (
|
|
1982
|
+
id uuid PRIMARY KEY,
|
|
1983
|
+
name text,
|
|
1984
|
+
host text NOT NULL,
|
|
1985
|
+
pid int NOT NULL,
|
|
1986
|
+
version text NOT NULL,
|
|
1987
|
+
node_version text NOT NULL,
|
|
1988
|
+
application_name text,
|
|
1989
|
+
heartbeat_seconds int NOT NULL,
|
|
1990
|
+
supervise bool NOT NULL,
|
|
1991
|
+
schedule bool NOT NULL,
|
|
1992
|
+
migrate bool NOT NULL,
|
|
1993
|
+
persist_queue_stats bool NOT NULL,
|
|
1994
|
+
persist_warnings bool NOT NULL,
|
|
1995
|
+
pool_max int,
|
|
1996
|
+
pool_total int,
|
|
1997
|
+
pool_idle int,
|
|
1998
|
+
pool_waiting int,
|
|
1999
|
+
workers jsonb NOT NULL DEFAULT '[]'::jsonb,
|
|
2000
|
+
metrics jsonb,
|
|
2001
|
+
config jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
2002
|
+
crash_restarts int NOT NULL DEFAULT 0,
|
|
2003
|
+
crash_restarts_since timestamptz,
|
|
2004
|
+
started_on timestamptz NOT NULL DEFAULT now(),
|
|
2005
|
+
heartbeat_on timestamptz NOT NULL DEFAULT now(),
|
|
2006
|
+
stopped_on timestamptz
|
|
2007
|
+
)`,
|
|
2008
|
+
/* eslint-enable no-restricted-syntax */
|
|
2009
|
+
`ALTER TABLE ${schema}.queue
|
|
2010
|
+
ADD COLUMN blocked_count int NOT NULL DEFAULT 0,
|
|
2011
|
+
ADD COLUMN wait_bins int[],
|
|
2012
|
+
ADD COLUMN run_bins int[],
|
|
2013
|
+
ADD COLUMN ready_oldest_seconds int`,
|
|
2014
|
+
`ALTER TABLE ${schema}.queue_stats
|
|
2015
|
+
ADD COLUMN wait_bins int[],
|
|
2016
|
+
ADD COLUMN run_bins int[],
|
|
2017
|
+
ADD COLUMN ready_oldest_seconds int`,
|
|
2018
|
+
`ALTER TABLE ${schema}.job ADD COLUMN IF NOT EXISTS trace_context jsonb`
|
|
2019
|
+
],
|
|
2020
|
+
uninstall: [
|
|
2021
|
+
`DROP TABLE ${schema}.instance`,
|
|
2022
|
+
`ALTER TABLE ${schema}.queue
|
|
2023
|
+
DROP COLUMN blocked_count,
|
|
2024
|
+
DROP COLUMN wait_bins,
|
|
2025
|
+
DROP COLUMN run_bins,
|
|
2026
|
+
DROP COLUMN ready_oldest_seconds`,
|
|
2027
|
+
`ALTER TABLE ${schema}.queue_stats
|
|
2028
|
+
DROP COLUMN wait_bins,
|
|
2029
|
+
DROP COLUMN run_bins,
|
|
2030
|
+
DROP COLUMN ready_oldest_seconds`,
|
|
2031
|
+
`ALTER TABLE ${schema}.job DROP COLUMN trace_context`
|
|
2032
|
+
]
|
|
1966
2033
|
}
|
|
1967
2034
|
];
|
|
1968
2035
|
}
|