pg-boss 12.27.0 → 12.28.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 +15 -0
- package/dist/attorney.d.ts +2 -1
- package/dist/attorney.d.ts.map +1 -1
- package/dist/attorney.js +22 -4
- package/dist/cli.js +2 -2
- package/dist/contractor.d.ts +1 -1
- package/dist/contractor.d.ts.map +1 -1
- package/dist/contractor.js +8 -2
- package/dist/drifter.d.ts +2 -0
- package/dist/drifter.d.ts.map +1 -1
- package/dist/drifter.js +46 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/manager.d.ts.map +1 -1
- package/dist/manager.js +11 -3
- package/dist/migrationStore.d.ts +1 -1
- package/dist/migrationStore.d.ts.map +1 -1
- package/dist/migrationStore.js +181 -10
- package/dist/plans.d.ts +2 -2
- package/dist/plans.d.ts.map +1 -1
- package/dist/plans.js +98 -19
- package/dist/schema.json +14 -2
- package/dist/timekeeper.d.ts +1 -0
- package/dist/timekeeper.d.ts.map +1 -1
- package/dist/timekeeper.js +65 -5
- package/dist/types.d.ts +40 -8
- package/dist/types.d.ts.map +1 -1
- package/package.json +13 -12
package/dist/migrationStore.js
CHANGED
|
@@ -2,6 +2,16 @@ import assert from 'node:assert';
|
|
|
2
2
|
import * as plans from "./plans.js";
|
|
3
3
|
import { resolveSchemaName } from "./tools.js";
|
|
4
4
|
import * as types from "./types.js";
|
|
5
|
+
// 12.28.0 replaced the plain `string[]` partitionTables shape with { tableName, policy } records.
|
|
6
|
+
// TypeScript callers get a compile error; this keeps JavaScript callers from silently exporting a
|
|
7
|
+
// migration whose target list is wrong (a bare string has no policy, so every policy-scoped build
|
|
8
|
+
// would quietly drop it).
|
|
9
|
+
function assertPartitionMetadata(partitionTables) {
|
|
10
|
+
for (const partition of partitionTables) {
|
|
11
|
+
assert(typeof partition === 'object' && partition !== null &&
|
|
12
|
+
typeof partition.tableName === 'string' && typeof partition.policy === 'string', `partitionTables entries must be { tableName, policy } records, received: ${JSON.stringify(partition)}. The string[] form was removed in 12.28.0.`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
5
15
|
// Mirrors the SQL job_table_format() function (src/plans.ts): rewrites a command targeting
|
|
6
16
|
// the base `job` table to target a specific partition table.
|
|
7
17
|
function formatJobTable(command, table) {
|
|
@@ -17,16 +27,25 @@ function formatJobTable(command, table) {
|
|
|
17
27
|
// via BAM, one statement per target table, each prefixed with a provenance comment. The
|
|
18
28
|
// CONCURRENTLY keyword is preserved (these are emitted after COMMIT), and IF NOT EXISTS is
|
|
19
29
|
// added so the script is safe to re-run.
|
|
20
|
-
function inlineAsyncCommand(schema,
|
|
21
|
-
|
|
22
|
-
|
|
30
|
+
function inlineAsyncCommand(schema, asyncMigration, version, partitionTables) {
|
|
31
|
+
// Two spellings: the legacy string form embeds the whole job_table_run_async() call as SQL, so
|
|
32
|
+
// name/body/table-pin have to be parsed back out of it; the record form carries them as fields.
|
|
33
|
+
const isLegacy = typeof asyncMigration === 'string';
|
|
34
|
+
const asyncCommand = isLegacy ? asyncMigration : asyncMigration.command;
|
|
35
|
+
const nameMatch = isLegacy ? asyncCommand.match(/job_table_run_async\(\s*'([^']+)'/) : null;
|
|
36
|
+
const bodyMatch = isLegacy ? asyncCommand.match(/\$\$([\s\S]*?)\$\$/) : null;
|
|
23
37
|
// An explicit table arg after the $$ body pins the command to a single table (e.g. i8 →
|
|
24
38
|
// job_common); without it the command fans out across job_common + every partition.
|
|
25
|
-
const tableMatch = asyncCommand.match(/\$\$\s*,\s*'([^']+)'/);
|
|
26
|
-
assert(nameMatch && bodyMatch, `Unable to inline async migration command: ${asyncCommand}`);
|
|
27
|
-
const commandName = nameMatch[1];
|
|
28
|
-
const body = bodyMatch[1].trim();
|
|
29
|
-
|
|
39
|
+
const tableMatch = isLegacy ? asyncCommand.match(/\$\$\s*,\s*'([^']+)'/) : null;
|
|
40
|
+
assert(!isLegacy || (nameMatch && bodyMatch), `Unable to inline async migration command: ${asyncCommand}`);
|
|
41
|
+
const commandName = isLegacy ? nameMatch[1] : asyncMigration.name;
|
|
42
|
+
const body = (isLegacy ? bodyMatch[1] : asyncMigration.command).trim();
|
|
43
|
+
// A policy-scoped build only belongs on partitions of that policy; unscoped builds fan out to all.
|
|
44
|
+
const partitionPolicy = isLegacy ? undefined : asyncMigration.partitionPolicy;
|
|
45
|
+
const eligiblePartitions = partitionTables
|
|
46
|
+
.filter(partition => !partitionPolicy || partition.policy === partitionPolicy)
|
|
47
|
+
.map(partition => partition.tableName);
|
|
48
|
+
const targetTables = tableMatch ? [tableMatch[1]] : ['job_common', ...eligiblePartitions];
|
|
30
49
|
return targetTables.map(table => {
|
|
31
50
|
// Add IF NOT EXISTS so the exported script is re-runnable. The negative lookahead keeps it
|
|
32
51
|
// idempotent when the async command already spells out IF NOT EXISTS (the live BAM path needs it
|
|
@@ -36,6 +55,32 @@ function inlineAsyncCommand(schema, asyncCommand, version, partitionTables) {
|
|
|
36
55
|
return `${comment}\n${ddl}`;
|
|
37
56
|
});
|
|
38
57
|
}
|
|
58
|
+
function renderAsyncCommand(schema, asyncMigration, version) {
|
|
59
|
+
if (typeof asyncMigration === 'string') {
|
|
60
|
+
return asyncMigration.replace(/\$VERSION\$/g, String(version));
|
|
61
|
+
}
|
|
62
|
+
const name = asyncMigration.name.replaceAll("'", "''");
|
|
63
|
+
const policy = asyncMigration.partitionPolicy?.replaceAll("'", "''");
|
|
64
|
+
if (!policy) {
|
|
65
|
+
return `SELECT ${schema}.job_table_run_async('${name}', ${version}, $$${asyncMigration.command}$$)`;
|
|
66
|
+
}
|
|
67
|
+
// queue_name is passed alongside table_name so the enqueued rows record which queue each build
|
|
68
|
+
// belongs to, matching what the unscoped fan-out inside job_table_run_async() writes. For a
|
|
69
|
+
// partition row the function re-derives table_name from queue_name and lands on the same value;
|
|
70
|
+
// job_common has no queue of its own, so its row keeps queue NULL like the fan-out does.
|
|
71
|
+
return `SELECT ${schema}.job_table_run_async(
|
|
72
|
+
'${name}',
|
|
73
|
+
${version},
|
|
74
|
+
$$${asyncMigration.command}$$,
|
|
75
|
+
targets.table_name,
|
|
76
|
+
targets.queue_name
|
|
77
|
+
)
|
|
78
|
+
FROM (
|
|
79
|
+
SELECT 'job_common'::text AS table_name, NULL::text AS queue_name
|
|
80
|
+
UNION ALL
|
|
81
|
+
SELECT table_name, name FROM ${schema}.queue WHERE partition = true AND policy = '${policy}'
|
|
82
|
+
) targets`;
|
|
83
|
+
}
|
|
39
84
|
function flatten(schema, commands, version, noAdvisoryLocks) {
|
|
40
85
|
commands.unshift(plans.assertMigration(schema, version));
|
|
41
86
|
commands.push(plans.setVersion(schema, version));
|
|
@@ -45,7 +90,20 @@ function rollback(schema, version, migrations, noAdvisoryLocks) {
|
|
|
45
90
|
migrations = migrations || getAll(schema);
|
|
46
91
|
const result = migrations.find(i => i.version === version);
|
|
47
92
|
assert(result, `Version ${version} not found.`);
|
|
48
|
-
|
|
93
|
+
// Async (BAM) index builds are enqueued as bam rows, and the BAM runner does not filter by schema
|
|
94
|
+
// version — so a row left unfinished by a rollback would rebuild, one version later, the very index
|
|
95
|
+
// the rollback just dropped. Clear this version's unfinished rows as part of the same transaction.
|
|
96
|
+
// Only migrations that enqueue async commands can have any, and the migration that introduced BAM
|
|
97
|
+
// is also the first one with async commands, so bam is guaranteed to exist here; ordering the delete
|
|
98
|
+
// ahead of the migration's own steps keeps it before that migration's DROP TABLE bam.
|
|
99
|
+
//
|
|
100
|
+
// No guard against a build that is genuinely running: the uninstall steps drop the index the build
|
|
101
|
+
// holds a ShareUpdateExclusiveLock on, so a live build already makes the whole rollback fail on the
|
|
102
|
+
// transaction's lock_timeout, taking this delete with it.
|
|
103
|
+
const clearAsync = result.async?.length
|
|
104
|
+
? [`DELETE FROM ${schema}.bam WHERE version = ${result.version} AND status <> 'completed'`]
|
|
105
|
+
: [];
|
|
106
|
+
return flatten(schema, [...clearAsync, ...(result.uninstall || [])], result.previous, noAdvisoryLocks);
|
|
49
107
|
}
|
|
50
108
|
function next(schema, version, migrations, noAdvisoryLocks) {
|
|
51
109
|
migrations = migrations || getAll(schema);
|
|
@@ -71,6 +129,7 @@ function migrateCommands(schema, version, migrations, noAdvisoryLocks, options =
|
|
|
71
129
|
assert(version >= minPrevious, `Cannot migrate pg-boss schema from version ${version}: the oldest supported starting version is ${minPrevious}. ` +
|
|
72
130
|
'Upgrade to a schema at or above that version using an older pg-boss release first.');
|
|
73
131
|
}
|
|
132
|
+
assertPartitionMetadata(options.partitionTables || []);
|
|
74
133
|
const concurrent = [];
|
|
75
134
|
const result = migrations
|
|
76
135
|
.filter(i => i.previous >= version)
|
|
@@ -85,7 +144,7 @@ function migrateCommands(schema, version, migrations, noAdvisoryLocks, options =
|
|
|
85
144
|
}
|
|
86
145
|
}
|
|
87
146
|
else {
|
|
88
|
-
const bamCommands = migration.async.map(cmd =>
|
|
147
|
+
const bamCommands = migration.async.map(cmd => renderAsyncCommand(schema, cmd, migration.version));
|
|
89
148
|
acc.install = acc.install.concat(bamCommands);
|
|
90
149
|
}
|
|
91
150
|
}
|
|
@@ -675,6 +734,93 @@ const createQueueFn = {
|
|
|
675
734
|
END;
|
|
676
735
|
$$
|
|
677
736
|
LANGUAGE plpgsql;
|
|
737
|
+
`,
|
|
738
|
+
38: (schema) => `
|
|
739
|
+
CREATE OR REPLACE FUNCTION ${schema}.create_queue(queue_name text, options jsonb)
|
|
740
|
+
RETURNS VOID AS
|
|
741
|
+
$$
|
|
742
|
+
DECLARE
|
|
743
|
+
tablename varchar := CASE WHEN options->>'partition' = 'true'
|
|
744
|
+
THEN 'j' || encode(sha224(queue_name::bytea), 'hex')
|
|
745
|
+
ELSE 'job_common'
|
|
746
|
+
END;
|
|
747
|
+
queue_created_on timestamptz;
|
|
748
|
+
BEGIN
|
|
749
|
+
|
|
750
|
+
WITH q as (
|
|
751
|
+
INSERT INTO ${schema}.queue (
|
|
752
|
+
name,
|
|
753
|
+
policy,
|
|
754
|
+
retry_limit,
|
|
755
|
+
retry_delay,
|
|
756
|
+
retry_backoff,
|
|
757
|
+
retry_delay_max,
|
|
758
|
+
expire_seconds,
|
|
759
|
+
retention_seconds,
|
|
760
|
+
deletion_seconds,
|
|
761
|
+
warning_queued,
|
|
762
|
+
dead_letter,
|
|
763
|
+
partition,
|
|
764
|
+
table_name,
|
|
765
|
+
heartbeat_seconds,
|
|
766
|
+
notify
|
|
767
|
+
)
|
|
768
|
+
VALUES (
|
|
769
|
+
queue_name,
|
|
770
|
+
options->>'policy',
|
|
771
|
+
COALESCE((options->>'retryLimit')::int, 2),
|
|
772
|
+
COALESCE((options->>'retryDelay')::int, 0),
|
|
773
|
+
COALESCE((options->>'retryBackoff')::bool, false),
|
|
774
|
+
(options->>'retryDelayMax')::int,
|
|
775
|
+
COALESCE((options->>'expireInSeconds')::int, 900),
|
|
776
|
+
COALESCE((options->>'retentionSeconds')::int, 1209600),
|
|
777
|
+
COALESCE((options->>'deleteAfterSeconds')::int, 604800),
|
|
778
|
+
COALESCE((options->>'warningQueueSize')::int, 0),
|
|
779
|
+
options->>'deadLetter',
|
|
780
|
+
COALESCE((options->>'partition')::bool, false),
|
|
781
|
+
tablename,
|
|
782
|
+
(options->>'heartbeatSeconds')::int,
|
|
783
|
+
COALESCE((options->>'notify')::bool, false)
|
|
784
|
+
)
|
|
785
|
+
ON CONFLICT DO NOTHING
|
|
786
|
+
RETURNING created_on
|
|
787
|
+
)
|
|
788
|
+
SELECT created_on into queue_created_on from q;
|
|
789
|
+
|
|
790
|
+
IF queue_created_on IS NULL OR options->>'partition' IS DISTINCT FROM 'true' THEN
|
|
791
|
+
RETURN;
|
|
792
|
+
END IF;
|
|
793
|
+
|
|
794
|
+
EXECUTE format('CREATE TABLE ${schema}.%I (LIKE ${schema}.job INCLUDING DEFAULTS)', tablename);
|
|
795
|
+
|
|
796
|
+
EXECUTE ${schema}.job_table_format($cmd$ALTER TABLE ${schema}.job ADD PRIMARY KEY (name, id)$cmd$, tablename);
|
|
797
|
+
EXECUTE ${schema}.job_table_format($cmd$ALTER TABLE ${schema}.job ADD CONSTRAINT q_fkey FOREIGN KEY (name) REFERENCES ${schema}.queue (name) ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED$cmd$, tablename);
|
|
798
|
+
EXECUTE ${schema}.job_table_format($cmd$ALTER TABLE ${schema}.job ADD CONSTRAINT dlq_fkey FOREIGN KEY (dead_letter) REFERENCES ${schema}.queue (name) ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED$cmd$, tablename);
|
|
799
|
+
|
|
800
|
+
EXECUTE ${schema}.job_table_format($cmd$CREATE INDEX job_i5 ON ${schema}.job (name, start_after) WHERE state < 'active' AND NOT blocked$cmd$, tablename);
|
|
801
|
+
EXECUTE ${schema}.job_table_format($cmd$CREATE UNIQUE INDEX job_i4 ON ${schema}.job (name, singleton_on, COALESCE(singleton_key, '')) WHERE state <> 'cancelled' AND singleton_on IS NOT NULL$cmd$, tablename);
|
|
802
|
+
EXECUTE ${schema}.job_table_format($cmd$CREATE INDEX job_i7 ON ${schema}.job (name, group_id) WHERE state = 'active' AND group_id IS NOT NULL$cmd$, tablename);
|
|
803
|
+
EXECUTE ${schema}.job_table_format($cmd$CREATE INDEX job_i9 ON ${schema}.job (name, id) WHERE blocking AND state = 'completed'$cmd$, tablename);
|
|
804
|
+
|
|
805
|
+
IF options->>'policy' = 'short' THEN
|
|
806
|
+
EXECUTE ${schema}.job_table_format($cmd$CREATE UNIQUE INDEX job_i1 ON ${schema}.job (name, COALESCE(singleton_key, '')) WHERE state = 'created' AND policy = 'short'$cmd$, tablename);
|
|
807
|
+
ELSIF options->>'policy' = 'singleton' THEN
|
|
808
|
+
EXECUTE ${schema}.job_table_format($cmd$CREATE UNIQUE INDEX job_i2 ON ${schema}.job (name, COALESCE(singleton_key, '')) WHERE state = 'active' AND policy = 'singleton'$cmd$, tablename);
|
|
809
|
+
ELSIF options->>'policy' = 'stately' THEN
|
|
810
|
+
EXECUTE ${schema}.job_table_format($cmd$CREATE UNIQUE INDEX job_i3 ON ${schema}.job (name, state, COALESCE(singleton_key, '')) WHERE state <= 'active' AND policy = 'stately'$cmd$, tablename);
|
|
811
|
+
ELSIF options->>'policy' = 'exclusive' THEN
|
|
812
|
+
EXECUTE ${schema}.job_table_format($cmd$CREATE UNIQUE INDEX job_i6 ON ${schema}.job (name, COALESCE(singleton_key, '')) WHERE state <= 'active' AND policy = 'exclusive'$cmd$, tablename);
|
|
813
|
+
ELSIF options->>'policy' = 'key_strict_fifo' THEN
|
|
814
|
+
EXECUTE ${schema}.job_table_format($cmd$CREATE UNIQUE INDEX job_i8 ON ${schema}.job (name, singleton_key) WHERE state IN ('active', 'retry', 'failed') AND policy = 'key_strict_fifo'$cmd$, tablename);
|
|
815
|
+
EXECUTE ${schema}.job_table_format($cmd$CREATE INDEX job_i10 ON ${schema}.job (name, singleton_key, state DESC, created_on, id) INCLUDE (start_after) WHERE state < 'active' AND NOT blocked AND policy = 'key_strict_fifo'$cmd$, tablename);
|
|
816
|
+
EXECUTE ${schema}.job_table_format($cmd$ALTER TABLE ${schema}.job ADD CONSTRAINT job_key_strict_fifo_singleton_key_check CHECK (NOT (policy = 'key_strict_fifo' AND singleton_key IS NULL))$cmd$, tablename);
|
|
817
|
+
END IF;
|
|
818
|
+
|
|
819
|
+
EXECUTE format('ALTER TABLE ${schema}.%I ADD CONSTRAINT cjc CHECK (name=%L)', tablename, queue_name);
|
|
820
|
+
EXECUTE format('ALTER TABLE ${schema}.job ATTACH PARTITION ${schema}.%I FOR VALUES IN (%L)', tablename, queue_name);
|
|
821
|
+
END;
|
|
822
|
+
$$
|
|
823
|
+
LANGUAGE plpgsql;
|
|
678
824
|
`
|
|
679
825
|
};
|
|
680
826
|
// Frozen per-version snapshots of the queue_stats DDL, version-keyed like createQueueFn above. A
|
|
@@ -1211,6 +1357,31 @@ function getAll(schema, noPartitioning = false, noCovering = false) {
|
|
|
1211
1357
|
uninstall: noPartitioning
|
|
1212
1358
|
? []
|
|
1213
1359
|
: [jobTableFormatFn[36](schema)]
|
|
1360
|
+
},
|
|
1361
|
+
{
|
|
1362
|
+
release: '12.28.0',
|
|
1363
|
+
version: 38,
|
|
1364
|
+
previous: 37,
|
|
1365
|
+
install: noPartitioning
|
|
1366
|
+
// noCovering backends (CockroachDB) have no INCLUDE, so they get the narrow index. The
|
|
1367
|
+
// partitioned path is PostgreSQL-only and always covering.
|
|
1368
|
+
? [`CREATE INDEX job_i10 ON ${schema}.job (name, singleton_key, state DESC, created_on, id)${noCovering ? '' : ' INCLUDE (start_after)'} WHERE state < 'active' AND NOT blocked AND policy = 'key_strict_fifo'`]
|
|
1369
|
+
: [createQueueFn[38](schema)],
|
|
1370
|
+
async: noPartitioning
|
|
1371
|
+
? []
|
|
1372
|
+
: [
|
|
1373
|
+
{
|
|
1374
|
+
name: 'key_strict_fifo_head_index',
|
|
1375
|
+
partitionPolicy: 'key_strict_fifo',
|
|
1376
|
+
command: `CREATE INDEX CONCURRENTLY IF NOT EXISTS job_i10 ON ${schema}.job (name, singleton_key, state DESC, created_on, id) INCLUDE (start_after) WHERE state < 'active' AND NOT blocked AND policy = 'key_strict_fifo'`
|
|
1377
|
+
}
|
|
1378
|
+
],
|
|
1379
|
+
uninstall: noPartitioning
|
|
1380
|
+
? [`DROP INDEX IF EXISTS ${schema}.job_i10`]
|
|
1381
|
+
: [
|
|
1382
|
+
createQueueFn[33](schema),
|
|
1383
|
+
`SELECT ${schema}.job_table_run($cmd$DROP INDEX IF EXISTS ${schema}.job_i10$cmd$)`
|
|
1384
|
+
]
|
|
1214
1385
|
}
|
|
1215
1386
|
];
|
|
1216
1387
|
}
|
package/dist/plans.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { JobMatchStrategy,
|
|
1
|
+
import type { JobMatchStrategy, ManagedIndex, ManagedFunction } from './types.ts';
|
|
2
2
|
import type { ExpectedColumns, ExpectedConstraints } from './drifter.ts';
|
|
3
3
|
export interface SqlQuery {
|
|
4
4
|
text: string;
|
|
@@ -50,7 +50,7 @@ export declare function trySetQueueDeletionTime(schema: string, queues: string[]
|
|
|
50
50
|
export declare function trySetCronTime(schema: string, seconds: number): string;
|
|
51
51
|
export declare function trySetBamTime(schema: string, seconds: number): string;
|
|
52
52
|
export declare function trySetFlowTime(schema: string, seconds: number): string;
|
|
53
|
-
export declare function updateQueue(schema: string
|
|
53
|
+
export declare function updateQueue(schema: string): string;
|
|
54
54
|
export declare function getQueues(schema: string, names?: string[]): SqlQuery;
|
|
55
55
|
export declare function deleteJobsById(schema: string, table: string): string;
|
|
56
56
|
export declare function deleteQueuedJobs(schema: string, table: string): string;
|
package/dist/plans.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plans.d.ts","sourceRoot":"","sources":["../src/plans.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,
|
|
1
|
+
{"version":3,"file":"plans.d.ts","sourceRoot":"","sources":["../src/plans.ts"],"names":[],"mappings":"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"}
|
package/dist/plans.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { indexKeysRaw, indexPredicateRaw, displayIndexDefinition, extractFunctionBody, normalizeFunctionBody } from "./drifter.js";
|
|
1
|
+
import { indexKeysRaw, indexIncludeRaw, indexPredicateRaw, displayIndexDefinition, extractFunctionBody, normalizeFunctionBody } from "./drifter.js";
|
|
2
2
|
import schemaManifest from './schema.json' with { type: 'json' };
|
|
3
3
|
import { normalizeSchemaName, resolveSchemaName } from "./tools.js";
|
|
4
4
|
export const PG_ERROR = {
|
|
@@ -401,6 +401,7 @@ function createTableJobCommon(schema) {
|
|
|
401
401
|
SELECT ${schema}.job_table_run($cmd$${createIndexJobPolicyStately(schema)}$cmd$, '${COMMON_JOB_TABLE}');
|
|
402
402
|
SELECT ${schema}.job_table_run($cmd$${createIndexJobPolicyExclusive(schema)}$cmd$, '${COMMON_JOB_TABLE}');
|
|
403
403
|
SELECT ${schema}.job_table_run($cmd$${createIndexJobPolicyKeyStrictFifo(schema)}$cmd$, '${COMMON_JOB_TABLE}');
|
|
404
|
+
SELECT ${schema}.job_table_run($cmd$${createIndexJobPolicyKeyStrictFifoHeads(schema)}$cmd$, '${COMMON_JOB_TABLE}');
|
|
404
405
|
SELECT ${schema}.job_table_run($cmd$${createCheckConstraintKeyStrictFifo(schema)}$cmd$, '${COMMON_JOB_TABLE}');
|
|
405
406
|
SELECT ${schema}.job_table_run($cmd$${createIndexJobThrottle(schema)}$cmd$, '${COMMON_JOB_TABLE}');
|
|
406
407
|
SELECT ${schema}.job_table_run($cmd$${createIndexJobFetch(schema)}$cmd$, '${COMMON_JOB_TABLE}');
|
|
@@ -420,6 +421,7 @@ function createTableJobIndexes(schema, noDeferrableConstraints = false, noCoveri
|
|
|
420
421
|
${createIndexJobPolicyStately(schema)};
|
|
421
422
|
${createIndexJobPolicyExclusive(schema)};
|
|
422
423
|
${createIndexJobPolicyKeyStrictFifo(schema)};
|
|
424
|
+
${createIndexJobPolicyKeyStrictFifoHeads(schema, noCoveringIndex)};
|
|
423
425
|
${createCheckConstraintKeyStrictFifo(schema)};
|
|
424
426
|
${createIndexJobThrottle(schema)};
|
|
425
427
|
${createIndexJobFetch(schema, noCoveringIndex)};
|
|
@@ -550,6 +552,7 @@ function createQueueFunction(schema, noPartitioning = false) {
|
|
|
550
552
|
EXECUTE ${schema}.job_table_format($cmd$${createIndexJobPolicyExclusive(schema)}$cmd$, tablename);
|
|
551
553
|
ELSIF options->>'policy' = '${QUEUE_POLICIES.key_strict_fifo}' THEN
|
|
552
554
|
EXECUTE ${schema}.job_table_format($cmd$${createIndexJobPolicyKeyStrictFifo(schema)}$cmd$, tablename);
|
|
555
|
+
EXECUTE ${schema}.job_table_format($cmd$${createIndexJobPolicyKeyStrictFifoHeads(schema)}$cmd$, tablename);
|
|
553
556
|
EXECUTE ${schema}.job_table_format($cmd$${createCheckConstraintKeyStrictFifo(schema)}$cmd$, tablename);
|
|
554
557
|
END IF;
|
|
555
558
|
|
|
@@ -661,6 +664,21 @@ function createIndexJobPolicyExclusive(schema) {
|
|
|
661
664
|
function createIndexJobPolicyKeyStrictFifo(schema) {
|
|
662
665
|
return `CREATE UNIQUE INDEX job_i8 ON ${schema}.job (name, singleton_key) WHERE state IN ('${JOB_STATES.active}', '${JOB_STATES.retry}', '${JOB_STATES.failed}') AND policy = '${QUEUE_POLICIES.key_strict_fifo}'`;
|
|
663
666
|
}
|
|
667
|
+
function createIndexJobPolicyKeyStrictFifoHeads(schema, noCoveringIndex = false) {
|
|
668
|
+
// Column order mirrors the strict_fifo_heads ORDER BY (state DESC puts a retry job ahead
|
|
669
|
+
// of created siblings) so the DISTINCT ON head-per-key scan stays an ordered index scan.
|
|
670
|
+
//
|
|
671
|
+
// INCLUDE (start_after) is load-bearing, not a covering-index habit: `policy` and `blocked`
|
|
672
|
+
// are satisfied by the partial predicate, so start_after is the only remaining filter in the
|
|
673
|
+
// heads CTE. Without it every index entry needs a heap fetch and the scan degrades from an
|
|
674
|
+
// Index Only Scan to an Index Scan (measured at 200k queued rows / 2,000 keys: 47ms -> 111ms,
|
|
675
|
+
// ~400k extra buffer hits). It costs no HOT updates that job_i5 doesn't already cost, since
|
|
676
|
+
// job_i5 indexes start_after too, and it only widens a partial index that key_strict_fifo
|
|
677
|
+
// rows enter. Backends without covering indexes (the CockroachDB profile) fall back to the
|
|
678
|
+
// narrow form and pay the heap fetches; they take a different fetch path anyway (noSkipLocked).
|
|
679
|
+
const include = noCoveringIndex ? '' : ' INCLUDE (start_after)';
|
|
680
|
+
return `CREATE INDEX job_i10 ON ${schema}.job (name, singleton_key, state DESC, created_on, id)${include} WHERE state < '${JOB_STATES.active}' AND NOT blocked AND policy = '${QUEUE_POLICIES.key_strict_fifo}'`;
|
|
681
|
+
}
|
|
664
682
|
function createCheckConstraintKeyStrictFifo(schema) {
|
|
665
683
|
return `ALTER TABLE ${schema}.job ADD CONSTRAINT job_key_strict_fifo_singleton_key_check CHECK (NOT (policy = '${QUEUE_POLICIES.key_strict_fifo}' AND singleton_key IS NULL))`;
|
|
666
684
|
}
|
|
@@ -709,7 +727,7 @@ function trySetQueueTimestamp(schema, queues, column, seconds) {
|
|
|
709
727
|
values: [queues]
|
|
710
728
|
};
|
|
711
729
|
}
|
|
712
|
-
export function updateQueue(schema
|
|
730
|
+
export function updateQueue(schema) {
|
|
713
731
|
return `
|
|
714
732
|
WITH options as (SELECT $2::jsonb as data)
|
|
715
733
|
UPDATE ${schema}.queue SET
|
|
@@ -727,9 +745,9 @@ export function updateQueue(schema, { deadLetter } = {}) {
|
|
|
727
745
|
THEN (o.data->>'heartbeatSeconds')::int
|
|
728
746
|
ELSE heartbeat_seconds END,
|
|
729
747
|
notify = COALESCE((o.data->>'notify')::bool, notify),
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
748
|
+
dead_letter = CASE WHEN jsonb_exists(o.data, 'deadLetter')
|
|
749
|
+
THEN o.data->>'deadLetter'
|
|
750
|
+
ELSE dead_letter END,
|
|
733
751
|
updated_on = now()
|
|
734
752
|
FROM options o
|
|
735
753
|
WHERE name = $1
|
|
@@ -1108,7 +1126,7 @@ export function getSchemaCaseVariants(schema) {
|
|
|
1108
1126
|
`;
|
|
1109
1127
|
}
|
|
1110
1128
|
export function getPartitionedQueueTables(schema) {
|
|
1111
|
-
return `SELECT table_name FROM ${schema}.queue WHERE partition = true`;
|
|
1129
|
+
return `SELECT table_name, policy FROM ${schema}.queue WHERE partition = true`;
|
|
1112
1130
|
}
|
|
1113
1131
|
export function insertVersion(schema, version) {
|
|
1114
1132
|
return `INSERT INTO ${schema}.version(version) VALUES ('${version}')`;
|
|
@@ -1187,6 +1205,7 @@ function buildFetchParams(options) {
|
|
|
1187
1205
|
*/
|
|
1188
1206
|
export function fetchNextJob(options, noSkipLocked = false) {
|
|
1189
1207
|
const { schema, table, name, policy, limit, includeMetadata, priority = true, orderByCreatedOn = true, ignoreStartAfter = false, groupConcurrency, minPriority, maxPriority } = options;
|
|
1208
|
+
const keyStrictFifo = policy === QUEUE_POLICIES.key_strict_fifo;
|
|
1190
1209
|
const singletonFetch = limit > 1 && (policy === QUEUE_POLICIES.singleton || policy === QUEUE_POLICIES.stately);
|
|
1191
1210
|
const hasIgnoreSingletons = options.ignoreSingletons != null && options.ignoreSingletons.length > 0;
|
|
1192
1211
|
const hasIgnoreGroups = options.ignoreGroups != null && options.ignoreGroups.length > 0;
|
|
@@ -1247,6 +1266,23 @@ export function fetchNextJob(options, noSkipLocked = false) {
|
|
|
1247
1266
|
: `(j.group_id IS NULL
|
|
1248
1267
|
OR ${activeGroupCountExpression} < ${groupLimit})`
|
|
1249
1268
|
: '';
|
|
1269
|
+
// state DESC (retry > created in the enum) makes a retry job its key's head. job_i8
|
|
1270
|
+
// guarantees at most one job per key in active/retry/failed, and the NOT EXISTS blocker
|
|
1271
|
+
// below rejects every sibling of a retry job — so if created_on picked the head, an older
|
|
1272
|
+
// deferred job whose start_after has since arrived would claim the head slot while being
|
|
1273
|
+
// unfetchable, and the retry job (fetchable but not the head) would deadlock the key.
|
|
1274
|
+
const strictFifoHeadsCte = keyStrictFifo
|
|
1275
|
+
? `strict_fifo_heads AS MATERIALIZED (
|
|
1276
|
+
SELECT DISTINCT ON (h.singleton_key) h.id
|
|
1277
|
+
FROM ${schema}.${table} h
|
|
1278
|
+
WHERE h.name = '${name}'
|
|
1279
|
+
AND h.state < '${JOB_STATES.active}'
|
|
1280
|
+
AND NOT h.blocked
|
|
1281
|
+
AND h.policy = '${QUEUE_POLICIES.key_strict_fifo}'
|
|
1282
|
+
${!ignoreStartAfter ? 'AND h.start_after <= now()' : ''}
|
|
1283
|
+
ORDER BY h.singleton_key, h.state DESC, h.created_on, h.id
|
|
1284
|
+
), `
|
|
1285
|
+
: '';
|
|
1250
1286
|
const whereConditions = [
|
|
1251
1287
|
`j.name = '${name}'`,
|
|
1252
1288
|
`j.state < '${JOB_STATES.active}'`,
|
|
@@ -1257,6 +1293,18 @@ export function fetchNextJob(options, noSkipLocked = false) {
|
|
|
1257
1293
|
// timestamp, so `<` would leave freshly-inserted jobs invisible until the clock ticks.
|
|
1258
1294
|
// NOTIFY gating already uses `start_after <= now()` for the same reason.
|
|
1259
1295
|
!ignoreStartAfter ? 'j.start_after <= now()' : '',
|
|
1296
|
+
keyStrictFifo ? 'j.id IN (SELECT id FROM strict_fifo_heads)' : '',
|
|
1297
|
+
keyStrictFifo
|
|
1298
|
+
? `NOT EXISTS (
|
|
1299
|
+
SELECT 1
|
|
1300
|
+
FROM ${schema}.${table} b
|
|
1301
|
+
WHERE b.name = j.name
|
|
1302
|
+
AND b.singleton_key = j.singleton_key
|
|
1303
|
+
AND b.state IN ('${JOB_STATES.active}', '${JOB_STATES.retry}', '${JOB_STATES.failed}')
|
|
1304
|
+
AND b.policy = '${QUEUE_POLICIES.key_strict_fifo}'
|
|
1305
|
+
AND b.id <> j.id
|
|
1306
|
+
)`
|
|
1307
|
+
: '',
|
|
1260
1308
|
hasIgnoreSingletons ? `COALESCE(j.singleton_key, '') <> ALL(${params.ignoreSingletonsParam})` : '',
|
|
1261
1309
|
hasIgnoreGroups ? `(j.group_id IS NULL OR j.group_id <> ALL(${params.ignoreGroupsParam}))` : '',
|
|
1262
1310
|
hasMinPriority ? `j.priority >= ${params.minPriorityParam}` : '',
|
|
@@ -1315,6 +1363,7 @@ export function fetchNextJob(options, noSkipLocked = false) {
|
|
|
1315
1363
|
return {
|
|
1316
1364
|
text: `
|
|
1317
1365
|
WITH
|
|
1366
|
+
${strictFifoHeadsCte}
|
|
1318
1367
|
${activeGroupCountMapCte}
|
|
1319
1368
|
${nextCte}
|
|
1320
1369
|
${singletonCte}
|
|
@@ -1462,6 +1511,26 @@ export function restoreJobs(schema, table) {
|
|
|
1462
1511
|
AND id = ANY($2::uuid[])
|
|
1463
1512
|
`;
|
|
1464
1513
|
}
|
|
1514
|
+
// A `startAfter` string is either an absolute date time or a delay expressed as a Postgres
|
|
1515
|
+
// interval. A trailing 'Z' has always marked a date time; a leading ISO 8601 calendar date
|
|
1516
|
+
// (YYYY-MM-DD) marks one as well, which is what lets the other 8601 zone designators through
|
|
1517
|
+
// ('+00:00', '+05:30', '-08:00') along with zone-less and date-only strings — all of which
|
|
1518
|
+
// used to reach the interval cast and fail as 'invalid input syntax for type interval'.
|
|
1519
|
+
//
|
|
1520
|
+
// Recognition is only ever widened, so anything that resolves as an interval today still
|
|
1521
|
+
// does: bare seconds ('0', '300'), phrases ('5 minutes'), ISO 8601 durations ('PT1H') and
|
|
1522
|
+
// the year-month form ('2027-01', which Postgres reads as 2027 years 1 mon) carry neither
|
|
1523
|
+
// mark. A date time with an explicit offset resolves to that exact instant.
|
|
1524
|
+
//
|
|
1525
|
+
// A zone-less date time would otherwise be cast in the database session's TimeZone, so the
|
|
1526
|
+
// public entry points pin it to UTC before it gets here (Attorney.pinZonelessDateTime). This
|
|
1527
|
+
// cast is still session-TZ dependent for anything that reaches it unpinned, which is why the
|
|
1528
|
+
// pin lives at the boundary rather than in this expression: a caller may legitimately pass a
|
|
1529
|
+
// form Postgres resolves itself ('2027-01-01 08:00:00 America/New_York'), and rewriting those
|
|
1530
|
+
// in SQL would mean re-implementing timestamp parsing in a regex.
|
|
1531
|
+
function isDateTimeString(expression) {
|
|
1532
|
+
return `(right(${expression}, 1) = 'Z' OR ${expression} ~ '^[0-9]{4}-[0-9]{2}-[0-9]{2}')`;
|
|
1533
|
+
}
|
|
1465
1534
|
export function insertJobs(schema, { table, name, returnId = true, notify = false }) {
|
|
1466
1535
|
// When notify is enabled we always RETURN start_after so the wrapper below can gate
|
|
1467
1536
|
// the NOTIFY on immediate availability, regardless of whether the caller wants ids.
|
|
@@ -1520,7 +1589,7 @@ export function insertJobs(schema, { table, name, returnId = true, notify = fals
|
|
|
1520
1589
|
FROM (
|
|
1521
1590
|
SELECT *,
|
|
1522
1591
|
CASE
|
|
1523
|
-
WHEN
|
|
1592
|
+
WHEN ${isDateTimeString('"startAfter"')} THEN CAST("startAfter" as timestamp with time zone)
|
|
1524
1593
|
ELSE now() + CAST(COALESCE("startAfter",'0') as interval)
|
|
1525
1594
|
END as start_after
|
|
1526
1595
|
FROM json_to_recordset($1::json) as x (
|
|
@@ -1794,10 +1863,11 @@ function failJobsBody(schema, table, where, output, forceTerminal = false) {
|
|
|
1794
1863
|
SELECT * FROM failed_jobs
|
|
1795
1864
|
),
|
|
1796
1865
|
dlq_jobs as (
|
|
1797
|
-
INSERT INTO ${schema}.job (name, data, output, retry_limit, retry_backoff, retry_delay, keep_until, deletion_seconds,
|
|
1798
|
-
expire_seconds, source_name, source_id, source_created_on, source_retry_count, singleton_key, heartbeat_seconds)
|
|
1866
|
+
INSERT INTO ${schema}.job (name, priority, data, output, retry_limit, retry_backoff, retry_delay, keep_until, deletion_seconds,
|
|
1867
|
+
expire_seconds, source_name, source_id, source_created_on, source_retry_count, singleton_key, group_id, group_tier, heartbeat_seconds)
|
|
1799
1868
|
SELECT
|
|
1800
1869
|
r.dead_letter,
|
|
1870
|
+
r.priority,
|
|
1801
1871
|
r.data,
|
|
1802
1872
|
r.output,
|
|
1803
1873
|
q.retry_limit,
|
|
@@ -1811,7 +1881,9 @@ function failJobsBody(schema, table, where, output, forceTerminal = false) {
|
|
|
1811
1881
|
r.created_on,
|
|
1812
1882
|
r.retry_count,
|
|
1813
1883
|
r.singleton_key,
|
|
1814
|
-
r.
|
|
1884
|
+
r.group_id,
|
|
1885
|
+
r.group_tier,
|
|
1886
|
+
q.heartbeat_seconds
|
|
1815
1887
|
FROM results r
|
|
1816
1888
|
JOIN ${schema}.queue q ON q.name = r.dead_letter
|
|
1817
1889
|
WHERE state = '${JOB_STATES.failed}'
|
|
@@ -2006,9 +2078,10 @@ export function insertRetryJob(schema, table) {
|
|
|
2006
2078
|
export function insertDeadLetterJob(schema) {
|
|
2007
2079
|
return `
|
|
2008
2080
|
INSERT INTO ${schema}.job (name, data, output, retry_limit, retry_backoff, retry_delay, keep_until, deletion_seconds,
|
|
2009
|
-
expire_seconds, source_name, source_id, source_created_on, source_retry_count, singleton_key, heartbeat_seconds
|
|
2081
|
+
expire_seconds, source_name, source_id, source_created_on, source_retry_count, singleton_key, heartbeat_seconds,
|
|
2082
|
+
priority, group_id, group_tier)
|
|
2010
2083
|
SELECT $1, $2, $3, q.retry_limit, q.retry_backoff, q.retry_delay, now() + q.retention_seconds * interval '1s', q.deletion_seconds,
|
|
2011
|
-
q.expire_seconds, $4, $5, $6, $7, $8, $9
|
|
2084
|
+
q.expire_seconds, $4, $5, $6, $7, $8, q.heartbeat_seconds, $9, $10, $11
|
|
2012
2085
|
FROM ${schema}.queue q WHERE q.name = $1
|
|
2013
2086
|
`;
|
|
2014
2087
|
}
|
|
@@ -2017,7 +2090,11 @@ export function insertDeadLetterJob(schema) {
|
|
|
2017
2090
|
// oldest-first, capped at $4. The JOIN in `candidates` only matches jobs whose destination queue
|
|
2018
2091
|
// exists, so legacy/orphaned jobs (NULL source_name, no override) are never deleted — they stay
|
|
2019
2092
|
// in the DLQ rather than being lost. Re-created jobs get a new id, `created` state, retry_count 0,
|
|
2020
|
-
// cleared output, NULL source_*, and
|
|
2093
|
+
// cleared output, NULL source_*, and every queue-config column (retry/retention/policy/expiry/
|
|
2094
|
+
// heartbeat/dead_letter) from the destination queue as it is configured now — per-job overrides
|
|
2095
|
+
// from the original send() are not preserved, since the DLQ copy never stored them. `dead_letter`
|
|
2096
|
+
// is the same value send() falls back to, so a second terminal failure re-enters the DLQ.
|
|
2097
|
+
// Job-identity columns (priority, singleton_key, group_id, group_tier) are carried over instead.
|
|
2021
2098
|
export function redriveJobs(schema, table) {
|
|
2022
2099
|
return `
|
|
2023
2100
|
WITH candidates AS (
|
|
@@ -2039,11 +2116,12 @@ export function redriveJobs(schema, table) {
|
|
|
2039
2116
|
ins AS (
|
|
2040
2117
|
INSERT INTO ${schema}.job
|
|
2041
2118
|
(name, data, priority, retry_limit, retry_backoff, retry_delay, retry_delay_max,
|
|
2042
|
-
expire_seconds, keep_until, deletion_seconds, policy, singleton_key,
|
|
2119
|
+
expire_seconds, keep_until, deletion_seconds, policy, singleton_key, group_id, group_tier,
|
|
2120
|
+
heartbeat_seconds, dead_letter)
|
|
2043
2121
|
SELECT COALESCE($2, m.source_name), m.data, m.priority, q.retry_limit, q.retry_backoff,
|
|
2044
2122
|
q.retry_delay, q.retry_delay_max, q.expire_seconds,
|
|
2045
2123
|
now() + q.retention_seconds * interval '1s', q.deletion_seconds, q.policy,
|
|
2046
|
-
m.singleton_key, m.heartbeat_seconds
|
|
2124
|
+
m.singleton_key, m.group_id, m.group_tier, q.heartbeat_seconds, q.dead_letter
|
|
2047
2125
|
FROM moved m JOIN ${schema}.queue q ON q.name = COALESCE($2, m.source_name)
|
|
2048
2126
|
-- A destination queue's short/stately policy can still collide on (name, singleton_key)
|
|
2049
2127
|
-- if two redriven jobs share a key (job_i1/job_i3); dropping just that row here, matching
|
|
@@ -2099,11 +2177,11 @@ export function updateJob(schema, table, name, by, match, notify = false) {
|
|
|
2099
2177
|
const ordering = (by === 'singletonKey' && match !== 'all')
|
|
2100
2178
|
? `ORDER BY job.created_on ${match === 'oldest' ? 'ASC' : 'DESC'} LIMIT 1`
|
|
2101
2179
|
: '';
|
|
2102
|
-
// Resolve the incoming startAfter the same way insertJobs does (absolute
|
|
2180
|
+
// Resolve the incoming startAfter the same way insertJobs does (absolute date time vs.
|
|
2103
2181
|
// relative interval), falling back to the row's current start_after when not supplied.
|
|
2104
2182
|
const resolvedStartAfter = `
|
|
2105
2183
|
CASE WHEN jsonb_exists(o.data, 'startAfter')
|
|
2106
|
-
THEN CASE WHEN
|
|
2184
|
+
THEN CASE WHEN ${isDateTimeString("o.data->>'startAfter'")}
|
|
2107
2185
|
THEN (o.data->>'startAfter')::timestamptz
|
|
2108
2186
|
ELSE now() + CAST(o.data->>'startAfter' AS interval) END
|
|
2109
2187
|
ELSE job.start_after END`;
|
|
@@ -2599,7 +2677,8 @@ const POLICY_JOB_INDEXES = {
|
|
|
2599
2677
|
2: QUEUE_POLICIES.singleton,
|
|
2600
2678
|
3: QUEUE_POLICIES.stately,
|
|
2601
2679
|
6: QUEUE_POLICIES.exclusive,
|
|
2602
|
-
8: QUEUE_POLICIES.key_strict_fifo
|
|
2680
|
+
8: QUEUE_POLICIES.key_strict_fifo,
|
|
2681
|
+
10: QUEUE_POLICIES.key_strict_fifo
|
|
2603
2682
|
};
|
|
2604
2683
|
// job_iN indexes with no policy gate — created on every job table regardless of policy
|
|
2605
2684
|
// (throttle i4, fetch i5, group-concurrency i7, blocking i9).
|
|
@@ -2699,7 +2778,7 @@ export function expectedManagedFunctions(schema, partitioned) {
|
|
|
2699
2778
|
export function expectedManagedIndexes(schema, partitioned, partitions = []) {
|
|
2700
2779
|
const managed = (name, table, indexdef) => {
|
|
2701
2780
|
const def = applyManifestSchema(indexdef, schema);
|
|
2702
|
-
return { name, table, keys: indexKeysRaw(def), predicate: indexPredicateRaw(def), definition: displayIndexDefinition(def) };
|
|
2781
|
+
return { name, table, keys: indexKeysRaw(def), include: indexIncludeRaw(def), predicate: indexPredicateRaw(def), definition: displayIndexDefinition(def) };
|
|
2703
2782
|
};
|
|
2704
2783
|
const jobTable = partitioned ? COMMON_JOB_TABLE : 'job';
|
|
2705
2784
|
const out = [];
|