bunqueue 2.9.0 → 2.9.2
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 +7 -3
- package/dist/application/background/recovery/active.d.ts +1 -0
- package/dist/application/background/recovery/active.js +12 -0
- package/dist/application/background/recovery/index.js +3 -1
- package/dist/application/background/recovery/pending.js +1 -0
- package/dist/application/cleanupTasks.js +4 -16
- package/dist/application/contextFactory.js +3 -0
- package/dist/application/dlqManager.js +1 -0
- package/dist/application/dlqRetry.js +1 -0
- package/dist/application/eventsManager.d.ts +8 -1
- package/dist/application/eventsManager.js +76 -29
- package/dist/application/operations/ackHelpers.d.ts +1 -4
- package/dist/application/operations/ackHelpers.js +39 -14
- package/dist/application/operations/flowPush.js +1 -0
- package/dist/application/operations/flowValidation.js +4 -4
- package/dist/application/operations/pull.d.ts +7 -2
- package/dist/application/operations/pull.js +15 -57
- package/dist/application/operations/pullFinalization.d.ts +6 -0
- package/dist/application/operations/pullFinalization.js +69 -0
- package/dist/application/operations/pullStateTransition.d.ts +8 -7
- package/dist/application/operations/pullStateTransition.js +25 -18
- package/dist/application/operations/push.js +3 -0
- package/dist/application/operations/pushBatch.js +14 -7
- package/dist/application/operations/pushContext.d.ts +2 -1
- package/dist/application/postgres-queue-manager/control.d.ts +4 -0
- package/dist/application/postgres-queue-manager/control.js +24 -0
- package/dist/application/postgres-queue-manager/delivery.d.ts +5 -4
- package/dist/application/postgres-queue-manager/delivery.js +13 -10
- package/dist/application/postgres-queue-manager/groupQueries.d.ts +10 -0
- package/dist/application/postgres-queue-manager/groupQueries.js +40 -0
- package/dist/application/postgres-queue-manager/queries.d.ts +2 -2
- package/dist/application/postgres-queue-manager/queries.js +2 -2
- package/dist/application/queue-manager/control.js +1 -0
- package/dist/application/queue-manager/delivery.d.ts +5 -4
- package/dist/application/queue-manager/delivery.js +15 -8
- package/dist/application/queue-manager/limits.d.ts +11 -0
- package/dist/application/queue-manager/limits.js +61 -0
- package/dist/application/queue-manager/state.js +1 -1
- package/dist/application/queueTelemetryJournal.d.ts +2 -0
- package/dist/application/queueTelemetryJournal.js +23 -0
- package/dist/application/types/ack.d.ts +3 -1
- package/dist/cli/commandRouter.d.ts +1 -1
- package/dist/client/flowPush.js +6 -0
- package/dist/client/groupId.d.ts +1 -0
- package/dist/client/groupId.js +1 -0
- package/dist/client/index.d.ts +1 -1
- package/dist/client/jobHelpers.js +1 -0
- package/dist/client/queue/operations/add/bulk.js +4 -4
- package/dist/client/queue/operations/add/payload.d.ts +1 -0
- package/dist/client/queue/operations/add/payload.js +7 -1
- package/dist/client/queue/operations/add/single.js +3 -3
- package/dist/client/queue/operations/groups.d.ts +21 -0
- package/dist/client/queue/operations/groups.js +88 -0
- package/dist/client/queue/runtime/queries.d.ts +13 -0
- package/dist/client/queue/runtime/queries.js +31 -0
- package/dist/client/queue/runtime/state.d.ts +4 -0
- package/dist/client/queue/runtime/state.js +1 -0
- package/dist/client/types/index.d.ts +2 -2
- package/dist/client/types/options.d.ts +4 -0
- package/dist/client/types/worker.d.ts +8 -0
- package/dist/client/worker/runtime/execution.js +1 -0
- package/dist/client/worker/runtime/options.js +3 -0
- package/dist/client/worker/types/options.d.ts +2 -1
- package/dist/client/worker/workerPull.d.ts +2 -0
- package/dist/client/worker/workerPull.js +6 -4
- package/dist/domain/job/groupFifoOrder.d.ts +9 -0
- package/dist/domain/job/groupFifoOrder.js +26 -0
- package/dist/domain/queue/groupLimiterManager.d.ts +23 -0
- package/dist/domain/queue/groupLimiterManager.js +107 -0
- package/dist/domain/queue/groupScheduler.d.ts +31 -0
- package/dist/domain/queue/groupScheduler.js +256 -0
- package/dist/domain/queue/groupSchedulerOrder.d.ts +6 -0
- package/dist/domain/queue/groupSchedulerOrder.js +30 -0
- package/dist/domain/queue/priorityQueue.d.ts +13 -23
- package/dist/domain/queue/priorityQueue.js +53 -42
- package/dist/domain/queue/shard/keys.d.ts +1 -0
- package/dist/domain/queue/shard/keys.js +25 -2
- package/dist/domain/queue/shard/lifecycle.js +4 -0
- package/dist/domain/queue/shard/limits.d.ts +20 -1
- package/dist/domain/queue/shard/limits.js +56 -0
- package/dist/domain/queue/shard/state.d.ts +5 -0
- package/dist/domain/queue/shard/state.js +9 -1
- package/dist/domain/types/commands/core.d.ts +3 -0
- package/dist/domain/types/commands/limits.d.ts +41 -0
- package/dist/domain/types/commands/union.d.ts +1 -1
- package/dist/domain/types/group.d.ts +21 -0
- package/dist/domain/types/group.js +78 -0
- package/dist/domain/types/priorityQueue.d.ts +1 -0
- package/dist/infrastructure/persistence/jobOptionsBlob.d.ts +1 -0
- package/dist/infrastructure/persistence/jobOptionsBlob.js +10 -2
- package/dist/infrastructure/persistence/migrations.js +11 -0
- package/dist/infrastructure/persistence/postgres/admission.js +5 -2
- package/dist/infrastructure/persistence/postgres/batchAdmission.js +22 -4
- package/dist/infrastructure/persistence/postgres/claimBatch.d.ts +3 -0
- package/dist/infrastructure/persistence/postgres/claimBatch.js +16 -3
- package/dist/infrastructure/persistence/postgres/claimSelection.d.ts +8 -1
- package/dist/infrastructure/persistence/postgres/claimSelection.js +73 -131
- package/dist/infrastructure/persistence/postgres/claims.d.ts +8 -1
- package/dist/infrastructure/persistence/postgres/claims.js +19 -3
- package/dist/infrastructure/persistence/postgres/eventCatchupCursors.d.ts +32 -0
- package/dist/infrastructure/persistence/postgres/eventCatchupCursors.js +66 -0
- package/dist/infrastructure/persistence/postgres/eventJournalSchema.d.ts +2 -2
- package/dist/infrastructure/persistence/postgres/eventJournalSchema.js +16 -2
- package/dist/infrastructure/persistence/postgres/eventPruneWatermarks.d.ts +2 -0
- package/dist/infrastructure/persistence/postgres/eventPruneWatermarks.js +21 -3
- package/dist/infrastructure/persistence/postgres/events.d.ts +3 -3
- package/dist/infrastructure/persistence/postgres/events.js +44 -17
- package/dist/infrastructure/persistence/postgres/groupClaims.d.ts +15 -0
- package/dist/infrastructure/persistence/postgres/groupClaims.js +95 -0
- package/dist/infrastructure/persistence/postgres/groupSchema.d.ts +2 -0
- package/dist/infrastructure/persistence/postgres/groupSchema.js +155 -0
- package/dist/infrastructure/persistence/postgres/groupSchemaFingerprint.d.ts +3 -0
- package/dist/infrastructure/persistence/postgres/groupSchemaFingerprint.js +127 -0
- package/dist/infrastructure/persistence/postgres/groupStateRetention.d.ts +3 -0
- package/dist/infrastructure/persistence/postgres/groupStateRetention.js +93 -0
- package/dist/infrastructure/persistence/postgres/groups.d.ts +11 -0
- package/dist/infrastructure/persistence/postgres/groups.js +117 -0
- package/dist/infrastructure/persistence/postgres/queueDestruction.js +4 -0
- package/dist/infrastructure/persistence/postgres/runtime.d.ts +0 -1
- package/dist/infrastructure/persistence/postgres/runtime.js +3 -1
- package/dist/infrastructure/persistence/postgres/schema.d.ts +2 -2
- package/dist/infrastructure/persistence/postgres/schema.js +8 -4
- package/dist/infrastructure/persistence/postgres/schemaInitialization.js +6 -1
- package/dist/infrastructure/persistence/postgres/store.d.ts +11 -1
- package/dist/infrastructure/persistence/postgres/store.js +47 -3
- package/dist/infrastructure/persistence/postgres/telemetry.d.ts +2 -0
- package/dist/infrastructure/persistence/postgres/telemetry.js +33 -21
- package/dist/infrastructure/persistence/postgres/types.d.ts +1 -0
- package/dist/infrastructure/persistence/schema.d.ts +2 -2
- package/dist/infrastructure/persistence/schema.js +12 -1
- package/dist/infrastructure/persistence/sqlite/control.d.ts +14 -0
- package/dist/infrastructure/persistence/sqlite/control.js +69 -0
- package/dist/infrastructure/persistence/sqlite/jobs.d.ts +12 -0
- package/dist/infrastructure/persistence/sqlite/jobs.js +36 -0
- package/dist/infrastructure/persistence/sqlite/telemetry.d.ts +2 -1
- package/dist/infrastructure/persistence/sqlite/telemetry.js +5 -64
- package/dist/infrastructure/persistence/sqlite/telemetryWrites.d.ts +4 -0
- package/dist/infrastructure/persistence/sqlite/telemetryWrites.js +123 -0
- package/dist/infrastructure/persistence/sqliteSerializer.js +2 -0
- package/dist/infrastructure/server/handler-routes/control.js +13 -0
- package/dist/infrastructure/server/handlers/core.d.ts +0 -5
- package/dist/infrastructure/server/handlers/core.js +15 -18
- package/dist/infrastructure/server/handlers/groups.d.ts +11 -0
- package/dist/infrastructure/server/handlers/groups.js +55 -0
- package/dist/infrastructure/server/handlers/pushBatchValidation.js +4 -1
- package/dist/infrastructure/server/protocol/validation.d.ts +1 -0
- package/dist/infrastructure/server/protocol/validation.js +1 -0
- package/dist/infrastructure/server/protocol.d.ts +1 -1
- package/dist/infrastructure/server/protocol.js +1 -1
- package/package.json +2 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export declare const POSTGRES_REGISTER_EVENT_ROWS_BODY = "BEGIN\n INSERT INTO bunqueue_event_commits (namespace, transaction_id)\n SELECT DISTINCT namespace, transaction_id FROM new_event_rows\n ORDER BY namespace, transaction_id\n ON CONFLICT DO NOTHING;\n RETURN NULL;\nEND;";
|
|
2
2
|
export declare const POSTGRES_REGISTER_WATERMARK_ROWS_BODY = "BEGIN\n INSERT INTO bunqueue_event_commits (namespace, transaction_id)\n SELECT DISTINCT namespace, transaction_id FROM new_watermark_rows\n ORDER BY namespace, transaction_id\n ON CONFLICT DO NOTHING;\n RETURN NULL;\nEND;";
|
|
3
|
-
export declare const POSTGRES_ASSIGN_EVENT_COMMIT_BODY = "DECLARE\n assigned_commit_seq BIGINT;\nBEGIN\n PERFORM pg_advisory_xact_lock(hashtextextended(\n 'bunqueue:event-commit:' || length(NEW.namespace)::text || ':' || NEW.namespace,\n 0\n ));\n assigned_commit_seq := nextval('bunqueue_event_commit_seq');\n\n UPDATE bunqueue_event_commits\n SET commit_seq = assigned_commit_seq\n WHERE namespace = NEW.namespace\n AND transaction_id = NEW.transaction_id\n AND commit_seq IS NULL;\n UPDATE bunqueue_event_prune_watermarks\n SET commit_seq = assigned_commit_seq,\n pruned_commit_seq = CASE\n WHEN prunes_current_transaction THEN GREATEST(\n COALESCE(pruned_commit_seq, 0), assigned_commit_seq\n )\n ELSE pruned_commit_seq\n END,\n prunes_current_transaction = FALSE\n WHERE namespace = NEW.namespace\n AND transaction_id = NEW.transaction_id\n AND commit_seq IS NULL;\n RETURN NULL;\nEND;";
|
|
3
|
+
export declare const POSTGRES_ASSIGN_EVENT_COMMIT_BODY = "DECLARE\n assigned_commit_seq BIGINT;\nBEGIN\n PERFORM pg_advisory_xact_lock(hashtextextended(\n 'bunqueue:event-commit:' || length(NEW.namespace)::text || ':' || NEW.namespace,\n 0\n ));\n assigned_commit_seq := nextval('bunqueue_event_commit_seq');\n\n UPDATE bunqueue_event_commits\n SET commit_seq = assigned_commit_seq\n WHERE namespace = NEW.namespace\n AND transaction_id = NEW.transaction_id\n AND commit_seq IS NULL;\n UPDATE bunqueue_event_prune_watermarks\n SET commit_seq = assigned_commit_seq,\n pruned_commit_seq = CASE\n WHEN prunes_current_transaction THEN GREATEST(\n COALESCE(pruned_commit_seq, 0), assigned_commit_seq\n )\n ELSE pruned_commit_seq\n END,\n self_pruned_commit_seq = CASE\n WHEN prunes_current_transaction THEN GREATEST(\n COALESCE(self_pruned_commit_seq, 0), assigned_commit_seq\n )\n ELSE self_pruned_commit_seq\n END,\n prunes_current_transaction = FALSE\n WHERE namespace = NEW.namespace\n AND transaction_id = NEW.transaction_id\n AND commit_seq IS NULL;\n RETURN NULL;\nEND;";
|
|
4
4
|
/** Commit-time journal sequencer installed by the PostgreSQL schema migration. */
|
|
5
|
-
export declare const POSTGRES_EVENT_JOURNAL_SCHEMA = "\nCREATE SEQUENCE IF NOT EXISTS bunqueue_event_commit_seq AS BIGINT CACHE 1;\nALTER SEQUENCE bunqueue_event_commit_seq\n AS BIGINT INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 1;\n\nCREATE TABLE IF NOT EXISTS bunqueue_event_commits (\n namespace TEXT NOT NULL,\n transaction_id BIGINT NOT NULL,\n commit_seq BIGINT,\n PRIMARY KEY (namespace, transaction_id)\n);\n\nALTER TABLE bunqueue_event_commits\n ADD COLUMN IF NOT EXISTS namespace TEXT;\nALTER TABLE bunqueue_event_commits\n ADD COLUMN IF NOT EXISTS transaction_id BIGINT;\nALTER TABLE bunqueue_event_commits\n ADD COLUMN IF NOT EXISTS commit_seq BIGINT;\nALTER TABLE bunqueue_event_commits\n ALTER COLUMN namespace TYPE TEXT USING namespace::text,\n ALTER COLUMN transaction_id TYPE BIGINT USING transaction_id::bigint,\n ALTER COLUMN commit_seq TYPE BIGINT USING commit_seq::bigint;\n\nALTER TABLE bunqueue_events\n ADD COLUMN IF NOT EXISTS transaction_id BIGINT;\nALTER TABLE bunqueue_events\n ALTER COLUMN transaction_id DROP DEFAULT;\nALTER TABLE bunqueue_events\n ALTER COLUMN transaction_id TYPE BIGINT USING transaction_id::bigint;\nALTER TABLE bunqueue_events\n ALTER COLUMN transaction_id SET DEFAULT (pg_current_xact_id()::text::bigint);\n\nALTER TABLE bunqueue_event_prune_watermarks\n ADD COLUMN IF NOT EXISTS transaction_id BIGINT;\nALTER TABLE bunqueue_event_prune_watermarks\n ADD COLUMN IF NOT EXISTS commit_seq BIGINT;\nALTER TABLE bunqueue_event_prune_watermarks\n ADD COLUMN IF NOT EXISTS pruned_commit_seq BIGINT;\nALTER TABLE bunqueue_event_prune_watermarks\n ADD COLUMN IF NOT EXISTS prunes_current_transaction BOOLEAN NOT NULL DEFAULT FALSE;\nALTER TABLE bunqueue_event_prune_watermarks\n ALTER COLUMN transaction_id DROP DEFAULT,\n ALTER COLUMN pruned_commit_seq DROP DEFAULT,\n ALTER COLUMN prunes_current_transaction DROP DEFAULT;\nALTER TABLE bunqueue_event_prune_watermarks\n ALTER COLUMN transaction_id TYPE BIGINT USING transaction_id::bigint,\n ALTER COLUMN commit_seq TYPE BIGINT USING commit_seq::bigint,\n ALTER COLUMN pruned_commit_seq TYPE BIGINT USING pruned_commit_seq::bigint,\n ALTER COLUMN prunes_current_transaction TYPE BOOLEAN\n USING prunes_current_transaction::boolean;\nALTER TABLE bunqueue_event_prune_watermarks\n ALTER COLUMN transaction_id SET DEFAULT (pg_current_xact_id()::text::bigint);\nALTER TABLE bunqueue_event_prune_watermarks\n ALTER COLUMN pruned_commit_seq SET DEFAULT 0,\n ALTER COLUMN prunes_current_transaction SET DEFAULT FALSE;\n\nUPDATE bunqueue_events\nSET transaction_id = -id\nWHERE transaction_id IS NULL;\nUPDATE bunqueue_event_prune_watermarks\nSET transaction_id = COALESCE(transaction_id, -source_event_id),\n commit_seq = COALESCE(commit_seq, source_event_id),\n pruned_commit_seq = COALESCE(pruned_commit_seq, commit_seq, source_event_id)\nWHERE transaction_id IS NULL OR commit_seq IS NULL OR pruned_commit_seq IS NULL;\n\nALTER TABLE bunqueue_events\n ALTER COLUMN transaction_id SET NOT NULL;\nALTER TABLE bunqueue_event_commits\n ALTER COLUMN namespace SET NOT NULL,\n ALTER COLUMN transaction_id SET NOT NULL;\nALTER TABLE bunqueue_event_prune_watermarks\n ALTER COLUMN transaction_id SET NOT NULL,\n ALTER COLUMN pruned_commit_seq SET NOT NULL,\n ALTER COLUMN prunes_current_transaction SET NOT NULL;\n\nDO $migration$\nBEGIN\n IF EXISTS (\n SELECT 1 FROM pg_attribute\n WHERE attrelid = to_regclass('bunqueue_events')\n AND attname = 'commit_seq' AND NOT attisdropped\n ) THEN\n EXECUTE $sql$\n INSERT INTO bunqueue_event_commits (namespace, transaction_id, commit_seq)\n SELECT namespace, transaction_id, MAX(commit_seq)\n FROM bunqueue_events\n WHERE commit_seq IS NOT NULL\n GROUP BY namespace, transaction_id\n ON CONFLICT (namespace, transaction_id) DO UPDATE SET\n commit_seq = GREATEST(\n COALESCE(bunqueue_event_commits.commit_seq, 0),\n excluded.commit_seq\n )\n $sql$;\n ELSE\n INSERT INTO bunqueue_event_commits (namespace, transaction_id, commit_seq)\n SELECT namespace, transaction_id, MAX(id)\n FROM bunqueue_events\n GROUP BY namespace, transaction_id\n ON CONFLICT (namespace, transaction_id) DO NOTHING;\n END IF;\nEND;\n$migration$;\n\nINSERT INTO bunqueue_event_commits (namespace, transaction_id, commit_seq)\nSELECT namespace, transaction_id, MAX(commit_seq)\nFROM bunqueue_event_prune_watermarks\nWHERE commit_seq IS NOT NULL\nGROUP BY namespace, transaction_id\nON CONFLICT (namespace, transaction_id) DO UPDATE SET\n commit_seq = GREATEST(\n COALESCE(bunqueue_event_commits.commit_seq, 0),\n excluded.commit_seq\n );\n\nDO $block$\nDECLARE\n journal_floor BIGINT := 0;\n legacy_head_floor BIGINT := 0;\n sequence_floor BIGINT := 0;\nBEGIN\n SELECT COALESCE(MAX(commit_seq), 0) INTO journal_floor\n FROM bunqueue_event_commits;\n SELECT last_value INTO sequence_floor FROM bunqueue_event_commit_seq;\n IF to_regclass('bunqueue_event_commit_heads') IS NOT NULL THEN\n EXECUTE 'SELECT COALESCE(MAX(commit_seq), 0) FROM bunqueue_event_commit_heads'\n INTO legacy_head_floor;\n END IF;\n PERFORM setval(\n 'bunqueue_event_commit_seq',\n GREATEST(journal_floor, legacy_head_floor, sequence_floor, 1),\n TRUE\n );\nEND;\n$block$;\n\nDROP TABLE IF EXISTS bunqueue_event_commit_heads;\n\nDROP TRIGGER IF EXISTS bunqueue_events_register_commit ON bunqueue_events;\nDROP TRIGGER IF EXISTS bunqueue_watermarks_insert_register_commit\n ON bunqueue_event_prune_watermarks;\nDROP TRIGGER IF EXISTS bunqueue_watermarks_update_register_commit\n ON bunqueue_event_prune_watermarks;\nDROP TABLE IF EXISTS bunqueue_event_commit_assignments CASCADE;\nDROP INDEX IF EXISTS bunqueue_events_commit_idx;\nDROP INDEX IF EXISTS bunqueue_events_pending_commit_idx;\nALTER TABLE bunqueue_events DROP COLUMN IF EXISTS commit_seq;\n\nDROP INDEX IF EXISTS bunqueue_events_transaction_idx;\nDROP INDEX IF EXISTS bunqueue_event_commits_replay_idx;\nDROP INDEX IF EXISTS bunqueue_event_prune_watermarks_commit_idx;\nDROP INDEX IF EXISTS bunqueue_event_prune_watermarks_pending_commit_idx;\nDROP INDEX IF EXISTS bunqueue_event_prune_watermarks_transaction_idx;\n\nCREATE INDEX bunqueue_events_transaction_idx\n ON bunqueue_events(namespace, transaction_id, id);\nCREATE INDEX bunqueue_event_commits_replay_idx\n ON bunqueue_event_commits(namespace, commit_seq, transaction_id)\n WHERE commit_seq IS NOT NULL;\nCREATE INDEX bunqueue_event_prune_watermarks_commit_idx\n ON bunqueue_event_prune_watermarks(namespace, queue, commit_seq DESC)\n WHERE commit_seq IS NOT NULL;\nCREATE INDEX bunqueue_event_prune_watermarks_pending_commit_idx\n ON bunqueue_event_prune_watermarks(namespace, transaction_id)\n WHERE commit_seq IS NULL;\nCREATE INDEX bunqueue_event_prune_watermarks_transaction_idx\n ON bunqueue_event_prune_watermarks(namespace, transaction_id);\n\nCREATE OR REPLACE FUNCTION bunqueue_register_event_rows()\nRETURNS TRIGGER LANGUAGE plpgsql AS $function$\nBEGIN\n INSERT INTO bunqueue_event_commits (namespace, transaction_id)\n SELECT DISTINCT namespace, transaction_id FROM new_event_rows\n ORDER BY namespace, transaction_id\n ON CONFLICT DO NOTHING;\n RETURN NULL;\nEND;\n$function$;\n\nCREATE OR REPLACE FUNCTION bunqueue_register_watermark_rows()\nRETURNS TRIGGER LANGUAGE plpgsql AS $function$\nBEGIN\n INSERT INTO bunqueue_event_commits (namespace, transaction_id)\n SELECT DISTINCT namespace, transaction_id FROM new_watermark_rows\n ORDER BY namespace, transaction_id\n ON CONFLICT DO NOTHING;\n RETURN NULL;\nEND;\n$function$;\n\nCREATE OR REPLACE FUNCTION bunqueue_assign_event_commit()\nRETURNS TRIGGER LANGUAGE plpgsql AS $function$\nDECLARE\n assigned_commit_seq BIGINT;\nBEGIN\n PERFORM pg_advisory_xact_lock(hashtextextended(\n 'bunqueue:event-commit:' || length(NEW.namespace)::text || ':' || NEW.namespace,\n 0\n ));\n assigned_commit_seq := nextval('bunqueue_event_commit_seq');\n\n UPDATE bunqueue_event_commits\n SET commit_seq = assigned_commit_seq\n WHERE namespace = NEW.namespace\n AND transaction_id = NEW.transaction_id\n AND commit_seq IS NULL;\n UPDATE bunqueue_event_prune_watermarks\n SET commit_seq = assigned_commit_seq,\n pruned_commit_seq = CASE\n WHEN prunes_current_transaction THEN GREATEST(\n COALESCE(pruned_commit_seq, 0), assigned_commit_seq\n )\n ELSE pruned_commit_seq\n END,\n prunes_current_transaction = FALSE\n WHERE namespace = NEW.namespace\n AND transaction_id = NEW.transaction_id\n AND commit_seq IS NULL;\n RETURN NULL;\nEND;\n$function$;\n\nDROP TRIGGER IF EXISTS bunqueue_events_register_commit ON bunqueue_events;\nCREATE TRIGGER bunqueue_events_register_commit\nAFTER INSERT ON bunqueue_events\nREFERENCING NEW TABLE AS new_event_rows\nFOR EACH STATEMENT EXECUTE FUNCTION bunqueue_register_event_rows();\n\nDROP TRIGGER IF EXISTS bunqueue_watermarks_insert_register_commit\n ON bunqueue_event_prune_watermarks;\nCREATE TRIGGER bunqueue_watermarks_insert_register_commit\nAFTER INSERT ON bunqueue_event_prune_watermarks\nREFERENCING NEW TABLE AS new_watermark_rows\nFOR EACH STATEMENT EXECUTE FUNCTION bunqueue_register_watermark_rows();\n\nDROP TRIGGER IF EXISTS bunqueue_watermarks_update_register_commit\n ON bunqueue_event_prune_watermarks;\nCREATE TRIGGER bunqueue_watermarks_update_register_commit\nAFTER UPDATE ON bunqueue_event_prune_watermarks\nREFERENCING NEW TABLE AS new_watermark_rows\nFOR EACH STATEMENT EXECUTE FUNCTION bunqueue_register_watermark_rows();\n\nDROP TRIGGER IF EXISTS bunqueue_assign_event_commit\n ON bunqueue_event_commits;\nCREATE CONSTRAINT TRIGGER bunqueue_assign_event_commit\nAFTER INSERT ON bunqueue_event_commits\nDEFERRABLE INITIALLY DEFERRED\nFOR EACH ROW EXECUTE FUNCTION bunqueue_assign_event_commit();\n";
|
|
5
|
+
export declare const POSTGRES_EVENT_JOURNAL_SCHEMA = "\nCREATE SEQUENCE IF NOT EXISTS bunqueue_event_commit_seq AS BIGINT CACHE 1;\nALTER SEQUENCE bunqueue_event_commit_seq\n AS BIGINT INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 1;\n\nCREATE TABLE IF NOT EXISTS bunqueue_event_commits (\n namespace TEXT NOT NULL,\n transaction_id BIGINT NOT NULL,\n commit_seq BIGINT,\n PRIMARY KEY (namespace, transaction_id)\n);\n\nALTER TABLE bunqueue_event_commits\n ADD COLUMN IF NOT EXISTS namespace TEXT;\nALTER TABLE bunqueue_event_commits\n ADD COLUMN IF NOT EXISTS transaction_id BIGINT;\nALTER TABLE bunqueue_event_commits\n ADD COLUMN IF NOT EXISTS commit_seq BIGINT;\nALTER TABLE bunqueue_event_commits\n ALTER COLUMN namespace TYPE TEXT USING namespace::text,\n ALTER COLUMN transaction_id TYPE BIGINT USING transaction_id::bigint,\n ALTER COLUMN commit_seq TYPE BIGINT USING commit_seq::bigint;\n\nALTER TABLE bunqueue_events\n ADD COLUMN IF NOT EXISTS transaction_id BIGINT;\nALTER TABLE bunqueue_events\n ALTER COLUMN transaction_id DROP DEFAULT;\nALTER TABLE bunqueue_events\n ALTER COLUMN transaction_id TYPE BIGINT USING transaction_id::bigint;\nALTER TABLE bunqueue_events\n ALTER COLUMN transaction_id SET DEFAULT (pg_current_xact_id()::text::bigint);\n\nALTER TABLE bunqueue_event_prune_watermarks\n ADD COLUMN IF NOT EXISTS transaction_id BIGINT;\nALTER TABLE bunqueue_event_prune_watermarks\n ADD COLUMN IF NOT EXISTS commit_seq BIGINT;\nALTER TABLE bunqueue_event_prune_watermarks\n ADD COLUMN IF NOT EXISTS pruned_commit_seq BIGINT;\nALTER TABLE bunqueue_event_prune_watermarks\n ADD COLUMN IF NOT EXISTS self_pruned_commit_seq BIGINT;\nALTER TABLE bunqueue_event_prune_watermarks\n ADD COLUMN IF NOT EXISTS prunes_current_transaction BOOLEAN NOT NULL DEFAULT FALSE;\nALTER TABLE bunqueue_event_prune_watermarks\n ALTER COLUMN transaction_id DROP DEFAULT,\n ALTER COLUMN pruned_commit_seq DROP DEFAULT,\n ALTER COLUMN self_pruned_commit_seq DROP DEFAULT,\n ALTER COLUMN prunes_current_transaction DROP DEFAULT;\nALTER TABLE bunqueue_event_prune_watermarks\n ALTER COLUMN transaction_id TYPE BIGINT USING transaction_id::bigint,\n ALTER COLUMN commit_seq TYPE BIGINT USING commit_seq::bigint,\n ALTER COLUMN pruned_commit_seq TYPE BIGINT USING pruned_commit_seq::bigint,\n ALTER COLUMN self_pruned_commit_seq TYPE BIGINT USING self_pruned_commit_seq::bigint,\n ALTER COLUMN prunes_current_transaction TYPE BOOLEAN\n USING prunes_current_transaction::boolean;\nALTER TABLE bunqueue_event_prune_watermarks\n ALTER COLUMN transaction_id SET DEFAULT (pg_current_xact_id()::text::bigint);\nALTER TABLE bunqueue_event_prune_watermarks\n ALTER COLUMN pruned_commit_seq SET DEFAULT 0,\n ALTER COLUMN self_pruned_commit_seq SET DEFAULT 0,\n ALTER COLUMN prunes_current_transaction SET DEFAULT FALSE;\n\nUPDATE bunqueue_events\nSET transaction_id = -id\nWHERE transaction_id IS NULL;\nUPDATE bunqueue_event_prune_watermarks\nSET transaction_id = COALESCE(transaction_id, -source_event_id),\n commit_seq = COALESCE(commit_seq, source_event_id),\n pruned_commit_seq = COALESCE(pruned_commit_seq, commit_seq, source_event_id),\n self_pruned_commit_seq = COALESCE(self_pruned_commit_seq, 0)\nWHERE transaction_id IS NULL OR commit_seq IS NULL OR pruned_commit_seq IS NULL\n OR self_pruned_commit_seq IS NULL;\n\nALTER TABLE bunqueue_events\n ALTER COLUMN transaction_id SET NOT NULL;\nALTER TABLE bunqueue_event_commits\n ALTER COLUMN namespace SET NOT NULL,\n ALTER COLUMN transaction_id SET NOT NULL;\nALTER TABLE bunqueue_event_prune_watermarks\n ALTER COLUMN transaction_id SET NOT NULL,\n ALTER COLUMN pruned_commit_seq SET NOT NULL,\n ALTER COLUMN self_pruned_commit_seq SET NOT NULL,\n ALTER COLUMN prunes_current_transaction SET NOT NULL;\n\nDO $migration$\nBEGIN\n IF EXISTS (\n SELECT 1 FROM pg_attribute\n WHERE attrelid = to_regclass('bunqueue_events')\n AND attname = 'commit_seq' AND NOT attisdropped\n ) THEN\n EXECUTE $sql$\n INSERT INTO bunqueue_event_commits (namespace, transaction_id, commit_seq)\n SELECT namespace, transaction_id, MAX(commit_seq)\n FROM bunqueue_events\n WHERE commit_seq IS NOT NULL\n GROUP BY namespace, transaction_id\n ON CONFLICT (namespace, transaction_id) DO UPDATE SET\n commit_seq = GREATEST(\n COALESCE(bunqueue_event_commits.commit_seq, 0),\n excluded.commit_seq\n )\n $sql$;\n ELSE\n INSERT INTO bunqueue_event_commits (namespace, transaction_id, commit_seq)\n SELECT namespace, transaction_id, MAX(id)\n FROM bunqueue_events\n GROUP BY namespace, transaction_id\n ON CONFLICT (namespace, transaction_id) DO NOTHING;\n END IF;\nEND;\n$migration$;\n\nINSERT INTO bunqueue_event_commits (namespace, transaction_id, commit_seq)\nSELECT namespace, transaction_id, MAX(commit_seq)\nFROM bunqueue_event_prune_watermarks\nWHERE commit_seq IS NOT NULL\nGROUP BY namespace, transaction_id\nON CONFLICT (namespace, transaction_id) DO UPDATE SET\n commit_seq = GREATEST(\n COALESCE(bunqueue_event_commits.commit_seq, 0),\n excluded.commit_seq\n );\n\nDO $block$\nDECLARE\n journal_floor BIGINT := 0;\n legacy_head_floor BIGINT := 0;\n sequence_floor BIGINT := 0;\nBEGIN\n SELECT COALESCE(MAX(commit_seq), 0) INTO journal_floor\n FROM bunqueue_event_commits;\n SELECT last_value INTO sequence_floor FROM bunqueue_event_commit_seq;\n IF to_regclass('bunqueue_event_commit_heads') IS NOT NULL THEN\n EXECUTE 'SELECT COALESCE(MAX(commit_seq), 0) FROM bunqueue_event_commit_heads'\n INTO legacy_head_floor;\n END IF;\n PERFORM setval(\n 'bunqueue_event_commit_seq',\n GREATEST(journal_floor, legacy_head_floor, sequence_floor, 1),\n TRUE\n );\nEND;\n$block$;\n\nDROP TABLE IF EXISTS bunqueue_event_commit_heads;\n\nDROP TRIGGER IF EXISTS bunqueue_events_register_commit ON bunqueue_events;\nDROP TRIGGER IF EXISTS bunqueue_watermarks_insert_register_commit\n ON bunqueue_event_prune_watermarks;\nDROP TRIGGER IF EXISTS bunqueue_watermarks_update_register_commit\n ON bunqueue_event_prune_watermarks;\nDROP TABLE IF EXISTS bunqueue_event_commit_assignments CASCADE;\nDROP INDEX IF EXISTS bunqueue_events_commit_idx;\nDROP INDEX IF EXISTS bunqueue_events_pending_commit_idx;\nALTER TABLE bunqueue_events DROP COLUMN IF EXISTS commit_seq;\n\nDROP INDEX IF EXISTS bunqueue_events_transaction_idx;\nDROP INDEX IF EXISTS bunqueue_event_commits_replay_idx;\nDROP INDEX IF EXISTS bunqueue_event_prune_watermarks_commit_idx;\nDROP INDEX IF EXISTS bunqueue_event_prune_watermarks_pending_commit_idx;\nDROP INDEX IF EXISTS bunqueue_event_prune_watermarks_transaction_idx;\n\nCREATE INDEX bunqueue_events_transaction_idx\n ON bunqueue_events(namespace, transaction_id, id);\nCREATE INDEX bunqueue_event_commits_replay_idx\n ON bunqueue_event_commits(namespace, commit_seq, transaction_id)\n WHERE commit_seq IS NOT NULL;\nCREATE INDEX bunqueue_event_prune_watermarks_commit_idx\n ON bunqueue_event_prune_watermarks(namespace, queue, commit_seq DESC)\n WHERE commit_seq IS NOT NULL;\nCREATE INDEX bunqueue_event_prune_watermarks_pending_commit_idx\n ON bunqueue_event_prune_watermarks(namespace, transaction_id)\n WHERE commit_seq IS NULL;\nCREATE INDEX bunqueue_event_prune_watermarks_transaction_idx\n ON bunqueue_event_prune_watermarks(namespace, transaction_id);\n\nCREATE OR REPLACE FUNCTION bunqueue_register_event_rows()\nRETURNS TRIGGER LANGUAGE plpgsql AS $function$\nBEGIN\n INSERT INTO bunqueue_event_commits (namespace, transaction_id)\n SELECT DISTINCT namespace, transaction_id FROM new_event_rows\n ORDER BY namespace, transaction_id\n ON CONFLICT DO NOTHING;\n RETURN NULL;\nEND;\n$function$;\n\nCREATE OR REPLACE FUNCTION bunqueue_register_watermark_rows()\nRETURNS TRIGGER LANGUAGE plpgsql AS $function$\nBEGIN\n INSERT INTO bunqueue_event_commits (namespace, transaction_id)\n SELECT DISTINCT namespace, transaction_id FROM new_watermark_rows\n ORDER BY namespace, transaction_id\n ON CONFLICT DO NOTHING;\n RETURN NULL;\nEND;\n$function$;\n\nCREATE OR REPLACE FUNCTION bunqueue_assign_event_commit()\nRETURNS TRIGGER LANGUAGE plpgsql AS $function$\nDECLARE\n assigned_commit_seq BIGINT;\nBEGIN\n PERFORM pg_advisory_xact_lock(hashtextextended(\n 'bunqueue:event-commit:' || length(NEW.namespace)::text || ':' || NEW.namespace,\n 0\n ));\n assigned_commit_seq := nextval('bunqueue_event_commit_seq');\n\n UPDATE bunqueue_event_commits\n SET commit_seq = assigned_commit_seq\n WHERE namespace = NEW.namespace\n AND transaction_id = NEW.transaction_id\n AND commit_seq IS NULL;\n UPDATE bunqueue_event_prune_watermarks\n SET commit_seq = assigned_commit_seq,\n pruned_commit_seq = CASE\n WHEN prunes_current_transaction THEN GREATEST(\n COALESCE(pruned_commit_seq, 0), assigned_commit_seq\n )\n ELSE pruned_commit_seq\n END,\n self_pruned_commit_seq = CASE\n WHEN prunes_current_transaction THEN GREATEST(\n COALESCE(self_pruned_commit_seq, 0), assigned_commit_seq\n )\n ELSE self_pruned_commit_seq\n END,\n prunes_current_transaction = FALSE\n WHERE namespace = NEW.namespace\n AND transaction_id = NEW.transaction_id\n AND commit_seq IS NULL;\n RETURN NULL;\nEND;\n$function$;\n\nDROP TRIGGER IF EXISTS bunqueue_events_register_commit ON bunqueue_events;\nCREATE TRIGGER bunqueue_events_register_commit\nAFTER INSERT ON bunqueue_events\nREFERENCING NEW TABLE AS new_event_rows\nFOR EACH STATEMENT EXECUTE FUNCTION bunqueue_register_event_rows();\n\nDROP TRIGGER IF EXISTS bunqueue_watermarks_insert_register_commit\n ON bunqueue_event_prune_watermarks;\nCREATE TRIGGER bunqueue_watermarks_insert_register_commit\nAFTER INSERT ON bunqueue_event_prune_watermarks\nREFERENCING NEW TABLE AS new_watermark_rows\nFOR EACH STATEMENT EXECUTE FUNCTION bunqueue_register_watermark_rows();\n\nDROP TRIGGER IF EXISTS bunqueue_watermarks_update_register_commit\n ON bunqueue_event_prune_watermarks;\nCREATE TRIGGER bunqueue_watermarks_update_register_commit\nAFTER UPDATE ON bunqueue_event_prune_watermarks\nREFERENCING NEW TABLE AS new_watermark_rows\nFOR EACH STATEMENT EXECUTE FUNCTION bunqueue_register_watermark_rows();\n\nDROP TRIGGER IF EXISTS bunqueue_assign_event_commit\n ON bunqueue_event_commits;\nCREATE CONSTRAINT TRIGGER bunqueue_assign_event_commit\nAFTER INSERT ON bunqueue_event_commits\nDEFERRABLE INITIALLY DEFERRED\nFOR EACH ROW EXECUTE FUNCTION bunqueue_assign_event_commit();\n";
|
|
@@ -34,6 +34,12 @@ BEGIN
|
|
|
34
34
|
)
|
|
35
35
|
ELSE pruned_commit_seq
|
|
36
36
|
END,
|
|
37
|
+
self_pruned_commit_seq = CASE
|
|
38
|
+
WHEN prunes_current_transaction THEN GREATEST(
|
|
39
|
+
COALESCE(self_pruned_commit_seq, 0), assigned_commit_seq
|
|
40
|
+
)
|
|
41
|
+
ELSE self_pruned_commit_seq
|
|
42
|
+
END,
|
|
37
43
|
prunes_current_transaction = FALSE
|
|
38
44
|
WHERE namespace = NEW.namespace
|
|
39
45
|
AND transaction_id = NEW.transaction_id
|
|
@@ -79,22 +85,27 @@ ALTER TABLE bunqueue_event_prune_watermarks
|
|
|
79
85
|
ADD COLUMN IF NOT EXISTS commit_seq BIGINT;
|
|
80
86
|
ALTER TABLE bunqueue_event_prune_watermarks
|
|
81
87
|
ADD COLUMN IF NOT EXISTS pruned_commit_seq BIGINT;
|
|
88
|
+
ALTER TABLE bunqueue_event_prune_watermarks
|
|
89
|
+
ADD COLUMN IF NOT EXISTS self_pruned_commit_seq BIGINT;
|
|
82
90
|
ALTER TABLE bunqueue_event_prune_watermarks
|
|
83
91
|
ADD COLUMN IF NOT EXISTS prunes_current_transaction BOOLEAN NOT NULL DEFAULT FALSE;
|
|
84
92
|
ALTER TABLE bunqueue_event_prune_watermarks
|
|
85
93
|
ALTER COLUMN transaction_id DROP DEFAULT,
|
|
86
94
|
ALTER COLUMN pruned_commit_seq DROP DEFAULT,
|
|
95
|
+
ALTER COLUMN self_pruned_commit_seq DROP DEFAULT,
|
|
87
96
|
ALTER COLUMN prunes_current_transaction DROP DEFAULT;
|
|
88
97
|
ALTER TABLE bunqueue_event_prune_watermarks
|
|
89
98
|
ALTER COLUMN transaction_id TYPE BIGINT USING transaction_id::bigint,
|
|
90
99
|
ALTER COLUMN commit_seq TYPE BIGINT USING commit_seq::bigint,
|
|
91
100
|
ALTER COLUMN pruned_commit_seq TYPE BIGINT USING pruned_commit_seq::bigint,
|
|
101
|
+
ALTER COLUMN self_pruned_commit_seq TYPE BIGINT USING self_pruned_commit_seq::bigint,
|
|
92
102
|
ALTER COLUMN prunes_current_transaction TYPE BOOLEAN
|
|
93
103
|
USING prunes_current_transaction::boolean;
|
|
94
104
|
ALTER TABLE bunqueue_event_prune_watermarks
|
|
95
105
|
ALTER COLUMN transaction_id SET DEFAULT (pg_current_xact_id()::text::bigint);
|
|
96
106
|
ALTER TABLE bunqueue_event_prune_watermarks
|
|
97
107
|
ALTER COLUMN pruned_commit_seq SET DEFAULT 0,
|
|
108
|
+
ALTER COLUMN self_pruned_commit_seq SET DEFAULT 0,
|
|
98
109
|
ALTER COLUMN prunes_current_transaction SET DEFAULT FALSE;
|
|
99
110
|
|
|
100
111
|
UPDATE bunqueue_events
|
|
@@ -103,8 +114,10 @@ WHERE transaction_id IS NULL;
|
|
|
103
114
|
UPDATE bunqueue_event_prune_watermarks
|
|
104
115
|
SET transaction_id = COALESCE(transaction_id, -source_event_id),
|
|
105
116
|
commit_seq = COALESCE(commit_seq, source_event_id),
|
|
106
|
-
pruned_commit_seq = COALESCE(pruned_commit_seq, commit_seq, source_event_id)
|
|
107
|
-
|
|
117
|
+
pruned_commit_seq = COALESCE(pruned_commit_seq, commit_seq, source_event_id),
|
|
118
|
+
self_pruned_commit_seq = COALESCE(self_pruned_commit_seq, 0)
|
|
119
|
+
WHERE transaction_id IS NULL OR commit_seq IS NULL OR pruned_commit_seq IS NULL
|
|
120
|
+
OR self_pruned_commit_seq IS NULL;
|
|
108
121
|
|
|
109
122
|
ALTER TABLE bunqueue_events
|
|
110
123
|
ALTER COLUMN transaction_id SET NOT NULL;
|
|
@@ -114,6 +127,7 @@ ALTER TABLE bunqueue_event_commits
|
|
|
114
127
|
ALTER TABLE bunqueue_event_prune_watermarks
|
|
115
128
|
ALTER COLUMN transaction_id SET NOT NULL,
|
|
116
129
|
ALTER COLUMN pruned_commit_seq SET NOT NULL,
|
|
130
|
+
ALTER COLUMN self_pruned_commit_seq SET NOT NULL,
|
|
117
131
|
ALTER COLUMN prunes_current_transaction SET NOT NULL;
|
|
118
132
|
|
|
119
133
|
DO $migration$
|
|
@@ -10,6 +10,8 @@ export interface PostgresEventPruneWatermarkInput {
|
|
|
10
10
|
export interface PostgresEventPruneWatermark extends PostgresEventPruneWatermarkInput {
|
|
11
11
|
readonly commitSeq: number;
|
|
12
12
|
readonly prunedCommitSeq: number;
|
|
13
|
+
/** Newest commit that pruned part of its own queue events, 0 when none did. */
|
|
14
|
+
readonly selfPrunedCommitSeq: number;
|
|
13
15
|
}
|
|
14
16
|
export declare function maxPostgresEventId(rows: readonly {
|
|
15
17
|
id: number | string | bigint;
|
|
@@ -48,7 +48,7 @@ export async function recordPostgresEventPruneWatermarks(tx, ctx, inputs) {
|
|
|
48
48
|
), inserted AS (
|
|
49
49
|
INSERT INTO bunqueue_event_prune_watermarks
|
|
50
50
|
(namespace, queue, source_event_id, pruned_through, pruned_commit_seq,
|
|
51
|
-
prunes_current_transaction)
|
|
51
|
+
self_pruned_commit_seq, prunes_current_transaction)
|
|
52
52
|
SELECT ${ctx.config.namespace}, queue, source_event_id, pruned_through,
|
|
53
53
|
GREATEST(
|
|
54
54
|
pruned_commit_seq,
|
|
@@ -59,7 +59,19 @@ export async function recordPostgresEventPruneWatermarks(tx, ctx, inputs) {
|
|
|
59
59
|
AND existing.queue = incoming.queue
|
|
60
60
|
), 0)
|
|
61
61
|
),
|
|
62
|
-
|
|
62
|
+
COALESCE((
|
|
63
|
+
SELECT MAX(existing.self_pruned_commit_seq)
|
|
64
|
+
FROM bunqueue_event_prune_watermarks AS existing
|
|
65
|
+
WHERE existing.namespace = ${ctx.config.namespace}
|
|
66
|
+
AND existing.queue = incoming.queue
|
|
67
|
+
), 0),
|
|
68
|
+
prunes_current_transaction OR COALESCE((
|
|
69
|
+
SELECT bool_or(existing.prunes_current_transaction)
|
|
70
|
+
FROM bunqueue_event_prune_watermarks AS existing
|
|
71
|
+
WHERE existing.namespace = ${ctx.config.namespace}
|
|
72
|
+
AND existing.queue = incoming.queue
|
|
73
|
+
AND existing.commit_seq IS NULL
|
|
74
|
+
), FALSE)
|
|
63
75
|
FROM incoming
|
|
64
76
|
ON CONFLICT (namespace, queue, source_event_id) DO UPDATE SET
|
|
65
77
|
pruned_through = GREATEST(
|
|
@@ -70,6 +82,10 @@ export async function recordPostgresEventPruneWatermarks(tx, ctx, inputs) {
|
|
|
70
82
|
COALESCE(bunqueue_event_prune_watermarks.pruned_commit_seq, 0),
|
|
71
83
|
COALESCE(excluded.pruned_commit_seq, 0)
|
|
72
84
|
),
|
|
85
|
+
self_pruned_commit_seq = GREATEST(
|
|
86
|
+
COALESCE(bunqueue_event_prune_watermarks.self_pruned_commit_seq, 0),
|
|
87
|
+
COALESCE(excluded.self_pruned_commit_seq, 0)
|
|
88
|
+
),
|
|
73
89
|
prunes_current_transaction =
|
|
74
90
|
bunqueue_event_prune_watermarks.prunes_current_transaction
|
|
75
91
|
OR excluded.prunes_current_transaction,
|
|
@@ -89,7 +105,8 @@ export async function loadPostgresEventPruneWatermarks(ctx) {
|
|
|
89
105
|
const rows = await ctx.sql `
|
|
90
106
|
SELECT DISTINCT ON (queue)
|
|
91
107
|
queue, source_event_id, pruned_through, commit_seq,
|
|
92
|
-
MAX(pruned_commit_seq) OVER (PARTITION BY queue) AS pruned_commit_seq
|
|
108
|
+
MAX(pruned_commit_seq) OVER (PARTITION BY queue) AS pruned_commit_seq,
|
|
109
|
+
MAX(self_pruned_commit_seq) OVER (PARTITION BY queue) AS self_pruned_commit_seq
|
|
93
110
|
FROM bunqueue_event_prune_watermarks
|
|
94
111
|
WHERE namespace = ${ctx.config.namespace}
|
|
95
112
|
AND commit_seq IS NOT NULL
|
|
@@ -101,6 +118,7 @@ export async function loadPostgresEventPruneWatermarks(ctx) {
|
|
|
101
118
|
prunedThrough: numeric(row.pruned_through) ?? 0,
|
|
102
119
|
commitSeq: numeric(row.commit_seq) ?? 0,
|
|
103
120
|
prunedCommitSeq: numeric(row.pruned_commit_seq) ?? 0,
|
|
121
|
+
selfPrunedCommitSeq: numeric(row.self_pruned_commit_seq) ?? 0,
|
|
104
122
|
}));
|
|
105
123
|
}
|
|
106
124
|
export async function deletePostgresEventPruneWatermark(tx, ctx, queue) {
|
|
@@ -13,14 +13,14 @@ export declare class PostgresEventStream {
|
|
|
13
13
|
private readonly invalidationListeners;
|
|
14
14
|
private readonly drainStatusListeners;
|
|
15
15
|
private readonly waiters;
|
|
16
|
-
private readonly
|
|
16
|
+
private readonly queueCursors;
|
|
17
17
|
private readonly retentionQueues;
|
|
18
18
|
private journalCursor;
|
|
19
19
|
private cursor;
|
|
20
|
-
private baselineCursor;
|
|
21
20
|
private checkpointScanRequested;
|
|
22
21
|
private drainRequested;
|
|
23
22
|
private draining;
|
|
23
|
+
private retentionRetryTimer;
|
|
24
24
|
private closed;
|
|
25
25
|
constructor(ctx: PostgresContext);
|
|
26
26
|
start(): Promise<void>;
|
|
@@ -36,9 +36,9 @@ export declare class PostgresEventStream {
|
|
|
36
36
|
private drain;
|
|
37
37
|
private drainLoop;
|
|
38
38
|
private convergeEventRetention;
|
|
39
|
+
private scheduleRetentionRetry;
|
|
39
40
|
private applyEvent;
|
|
40
41
|
private invalidateQueue;
|
|
41
|
-
private applyPruneWatermark;
|
|
42
42
|
private scanPruneWatermarks;
|
|
43
43
|
private reportDrainStatus;
|
|
44
44
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { latestPostgresEventJournalBaseline, loadPostgresJournalEventsAfter, } from './eventJournal';
|
|
2
|
+
import { PostgresEventCatchupCursors } from './eventCatchupCursors';
|
|
2
3
|
import { loadPostgresEventPruneWatermarks } from './eventPruneWatermarks';
|
|
3
4
|
import { POSTGRES_EVENT_CHANNEL } from './schema';
|
|
4
|
-
import {
|
|
5
|
+
import { tryTrimPostgresQueueEvents } from './telemetry';
|
|
5
6
|
const MIN_EVENT_DRAIN_BATCH = 1_000;
|
|
6
7
|
const MAX_EVENT_DRAIN_BATCH = 4_096;
|
|
7
8
|
export function postgresEventDrainBatchSize(maxQueueEvents) {
|
|
@@ -19,14 +20,14 @@ export class PostgresEventStream {
|
|
|
19
20
|
invalidationListeners = new Set();
|
|
20
21
|
drainStatusListeners = new Set();
|
|
21
22
|
waiters = new Map();
|
|
22
|
-
|
|
23
|
+
queueCursors = new PostgresEventCatchupCursors();
|
|
23
24
|
retentionQueues = new Set();
|
|
24
25
|
journalCursor = { commitSeq: 0, eventId: 0 };
|
|
25
26
|
cursor = 0;
|
|
26
|
-
baselineCursor = 0;
|
|
27
27
|
checkpointScanRequested = false;
|
|
28
28
|
drainRequested = false;
|
|
29
29
|
draining = null;
|
|
30
|
+
retentionRetryTimer = null;
|
|
30
31
|
closed = false;
|
|
31
32
|
constructor(ctx) {
|
|
32
33
|
this.ctx = ctx;
|
|
@@ -37,12 +38,16 @@ export class PostgresEventStream {
|
|
|
37
38
|
loadPostgresEventPruneWatermarks(this.ctx),
|
|
38
39
|
]);
|
|
39
40
|
this.cursor = baseline.latestEventId;
|
|
40
|
-
this.baselineCursor = baseline.commitSeq;
|
|
41
41
|
this.journalCursor = { commitSeq: baseline.commitSeq, eventId: Number.MAX_SAFE_INTEGER };
|
|
42
|
+
this.queueCursors.reset(baseline.commitSeq);
|
|
42
43
|
for (const watermark of watermarks) {
|
|
43
44
|
if (watermark.commitSeq > baseline.commitSeq) {
|
|
44
|
-
this.
|
|
45
|
+
this.queueCursors.applied(watermark.queue, watermark.commitSeq);
|
|
45
46
|
}
|
|
47
|
+
// `PostgresQueueManagerState` loads a full snapshot of every queue after
|
|
48
|
+
// this call, so recorded prunes are already covered and must not refresh
|
|
49
|
+
// the freshly loaded read model.
|
|
50
|
+
this.queueCursors.remember(watermark);
|
|
46
51
|
}
|
|
47
52
|
this.subscription = await this.ctx.sql.listen(POSTGRES_EVENT_CHANNEL, (payload) => this.handleNotification(payload), () => void this.drain());
|
|
48
53
|
this.pollTimer = setInterval(() => void this.drain(), this.ctx.config.pollIntervalMs);
|
|
@@ -64,6 +69,9 @@ export class PostgresEventStream {
|
|
|
64
69
|
if (this.pollTimer)
|
|
65
70
|
clearInterval(this.pollTimer);
|
|
66
71
|
this.pollTimer = null;
|
|
72
|
+
if (this.retentionRetryTimer)
|
|
73
|
+
clearTimeout(this.retentionRetryTimer);
|
|
74
|
+
this.retentionRetryTimer = null;
|
|
67
75
|
await this.subscription?.unlisten();
|
|
68
76
|
this.subscription = null;
|
|
69
77
|
for (const registrations of this.waiters.values()) {
|
|
@@ -148,10 +156,13 @@ export class PostgresEventStream {
|
|
|
148
156
|
return this.draining;
|
|
149
157
|
this.draining = this.drainLoop()
|
|
150
158
|
.then(() => {
|
|
151
|
-
if (!this.closed)
|
|
159
|
+
if (!this.closed && !this.retentionRetryTimer)
|
|
152
160
|
this.reportDrainStatus(null);
|
|
153
161
|
})
|
|
154
|
-
.catch((error) =>
|
|
162
|
+
.catch((error) => {
|
|
163
|
+
this.reportDrainStatus(error instanceof Error ? error : new Error(String(error)));
|
|
164
|
+
this.scheduleRetentionRetry();
|
|
165
|
+
})
|
|
155
166
|
.finally(() => {
|
|
156
167
|
this.draining = null;
|
|
157
168
|
if (this.drainRequested && !this.closed)
|
|
@@ -166,6 +177,11 @@ export class PostgresEventStream {
|
|
|
166
177
|
await this.convergeEventRetention();
|
|
167
178
|
await this.scanPruneWatermarks();
|
|
168
179
|
const entries = await loadPostgresJournalEventsAfter(this.ctx, this.journalCursor, batchSize);
|
|
180
|
+
// Always re-check retention against the pre-batch position: a prune that
|
|
181
|
+
// committed after the previous scan can have removed queue history this
|
|
182
|
+
// batch does not contain, and applying the batch would hide the gap.
|
|
183
|
+
if (entries.length > 0)
|
|
184
|
+
this.checkpointScanRequested = true;
|
|
169
185
|
await this.scanPruneWatermarks();
|
|
170
186
|
for (const entry of entries)
|
|
171
187
|
this.applyEvent(entry);
|
|
@@ -175,11 +191,18 @@ export class PostgresEventStream {
|
|
|
175
191
|
}
|
|
176
192
|
}
|
|
177
193
|
async convergeEventRetention() {
|
|
194
|
+
if (this.retentionRetryTimer)
|
|
195
|
+
return;
|
|
196
|
+
const maxLength = this.ctx.config.maxQueueEvents;
|
|
178
197
|
const queues = [...this.retentionQueues].sort();
|
|
179
198
|
for (const queue of queues) {
|
|
180
199
|
this.retentionQueues.delete(queue);
|
|
181
200
|
try {
|
|
182
|
-
await
|
|
201
|
+
const removed = await tryTrimPostgresQueueEvents(this.ctx, queue, maxLength);
|
|
202
|
+
if (removed === null) {
|
|
203
|
+
this.retentionQueues.add(queue);
|
|
204
|
+
this.scheduleRetentionRetry();
|
|
205
|
+
}
|
|
183
206
|
}
|
|
184
207
|
catch (error) {
|
|
185
208
|
this.retentionQueues.add(queue);
|
|
@@ -187,11 +210,21 @@ export class PostgresEventStream {
|
|
|
187
210
|
}
|
|
188
211
|
}
|
|
189
212
|
}
|
|
213
|
+
scheduleRetentionRetry() {
|
|
214
|
+
if (this.closed || this.retentionRetryTimer || this.retentionQueues.size === 0)
|
|
215
|
+
return;
|
|
216
|
+
const delay = Math.min(this.ctx.config.pollIntervalMs, 250);
|
|
217
|
+
this.retentionRetryTimer = setTimeout(() => {
|
|
218
|
+
this.retentionRetryTimer = null;
|
|
219
|
+
if (!this.closed && this.retentionQueues.size > 0)
|
|
220
|
+
void this.drain(false);
|
|
221
|
+
}, delay);
|
|
222
|
+
}
|
|
190
223
|
applyEvent(entry) {
|
|
191
224
|
const { event, commitSeq } = entry;
|
|
192
225
|
this.journalCursor = { commitSeq, eventId: event.id };
|
|
193
226
|
this.cursor = Math.max(this.cursor, event.id);
|
|
194
|
-
this.
|
|
227
|
+
this.queueCursors.applied(event.queue, commitSeq);
|
|
195
228
|
if (requiresQueueInvalidation(event))
|
|
196
229
|
this.invalidateQueue(event.queue);
|
|
197
230
|
const delivered = { ...event, commitSeq };
|
|
@@ -214,13 +247,6 @@ export class PostgresEventStream {
|
|
|
214
247
|
}
|
|
215
248
|
}
|
|
216
249
|
}
|
|
217
|
-
applyPruneWatermark(queue, commitSeq, prunedCommitSeq) {
|
|
218
|
-
const appliedThrough = this.queueEventCursors.get(queue) ?? this.baselineCursor;
|
|
219
|
-
if (appliedThrough >= prunedCommitSeq)
|
|
220
|
-
return;
|
|
221
|
-
this.queueEventCursors.set(queue, commitSeq);
|
|
222
|
-
this.invalidateQueue(queue);
|
|
223
|
-
}
|
|
224
250
|
async scanPruneWatermarks() {
|
|
225
251
|
if (!this.checkpointScanRequested)
|
|
226
252
|
return;
|
|
@@ -228,7 +254,8 @@ export class PostgresEventStream {
|
|
|
228
254
|
try {
|
|
229
255
|
const watermarks = await loadPostgresEventPruneWatermarks(this.ctx);
|
|
230
256
|
for (const watermark of watermarks) {
|
|
231
|
-
this.
|
|
257
|
+
if (this.queueCursors.requiresRefresh(watermark))
|
|
258
|
+
this.invalidateQueue(watermark.queue);
|
|
232
259
|
}
|
|
233
260
|
}
|
|
234
261
|
catch (error) {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { TransactionSQL } from 'bun';
|
|
2
|
+
import type { GroupPullOptions } from '../../../domain/types/group';
|
|
3
|
+
import type { PostgresContext } from './context';
|
|
4
|
+
import type { PostgresJobRow } from './types';
|
|
5
|
+
/** Assign durable rotation positions to every pending group lacking one. */
|
|
6
|
+
export declare function ensurePostgresGroupStates(tx: TransactionSQL, ctx: PostgresContext, queue: string, sequence: number): Promise<number>;
|
|
7
|
+
/** Reset fixed windows when they expire or a worker changes the effective defaults. */
|
|
8
|
+
export declare function refreshPostgresGroupRateWindows(tx: TransactionSQL, ctx: PostgresContext, queue: string, options: GroupPullOptions | undefined, now: number): Promise<void>;
|
|
9
|
+
/** Consume group budgets and move the durable round-robin cursor after row locks are secured. */
|
|
10
|
+
export declare function commitPostgresGroupClaims(tx: TransactionSQL, ctx: PostgresContext, input: {
|
|
11
|
+
queue: string;
|
|
12
|
+
rows: readonly PostgresJobRow[];
|
|
13
|
+
options?: GroupPullOptions;
|
|
14
|
+
sequence: number;
|
|
15
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/** Assign durable rotation positions to every pending group lacking one. */
|
|
2
|
+
export async function ensurePostgresGroupStates(tx, ctx, queue, sequence) {
|
|
3
|
+
const assigned = await tx `
|
|
4
|
+
WITH unsequenced AS (
|
|
5
|
+
SELECT job.group_id, MIN(job.group_order) AS first_order
|
|
6
|
+
FROM bunqueue_jobs AS job
|
|
7
|
+
LEFT JOIN bunqueue_group_state AS groups
|
|
8
|
+
ON groups.namespace = job.namespace AND groups.queue = job.queue
|
|
9
|
+
AND groups.group_id = job.group_id
|
|
10
|
+
WHERE job.namespace = ${ctx.config.namespace} AND job.queue = ${queue}
|
|
11
|
+
AND job.group_id IS NOT NULL AND groups.last_served IS NULL
|
|
12
|
+
AND job.state IN ('waiting', 'prioritized', 'delayed')
|
|
13
|
+
GROUP BY job.group_id
|
|
14
|
+
), ordered AS (
|
|
15
|
+
SELECT group_id,
|
|
16
|
+
row_number() OVER (ORDER BY first_order, group_id) AS position
|
|
17
|
+
FROM unsequenced
|
|
18
|
+
)
|
|
19
|
+
INSERT INTO bunqueue_group_state (namespace, queue, group_id, last_served)
|
|
20
|
+
SELECT ${ctx.config.namespace}, ${queue}, group_id, ${sequence} + position
|
|
21
|
+
FROM ordered
|
|
22
|
+
ON CONFLICT (namespace, queue, group_id) DO UPDATE
|
|
23
|
+
SET last_served = EXCLUDED.last_served
|
|
24
|
+
WHERE bunqueue_group_state.last_served IS NULL
|
|
25
|
+
RETURNING group_id
|
|
26
|
+
`;
|
|
27
|
+
if (assigned.length === 0)
|
|
28
|
+
return sequence;
|
|
29
|
+
const next = sequence + assigned.length;
|
|
30
|
+
await tx `
|
|
31
|
+
UPDATE bunqueue_queue_state SET group_sequence = ${next}
|
|
32
|
+
WHERE namespace = ${ctx.config.namespace} AND queue = ${queue}
|
|
33
|
+
`;
|
|
34
|
+
return next;
|
|
35
|
+
}
|
|
36
|
+
/** Reset fixed windows when they expire or a worker changes the effective defaults. */
|
|
37
|
+
export async function refreshPostgresGroupRateWindows(tx, ctx, queue, options, now) {
|
|
38
|
+
if (!options?.limit)
|
|
39
|
+
return;
|
|
40
|
+
const { max, duration } = options.limit;
|
|
41
|
+
await tx `
|
|
42
|
+
UPDATE bunqueue_group_state AS groups
|
|
43
|
+
SET rate_window_started_at = ${now}, rate_count = 0,
|
|
44
|
+
rate_effective_max = COALESCE(groups.rate_limit, ${max}),
|
|
45
|
+
rate_effective_duration_ms = COALESCE(groups.rate_duration_ms, ${duration})
|
|
46
|
+
WHERE groups.namespace = ${ctx.config.namespace} AND groups.queue = ${queue}
|
|
47
|
+
AND EXISTS (
|
|
48
|
+
SELECT 1 FROM bunqueue_jobs AS job
|
|
49
|
+
WHERE job.namespace = groups.namespace AND job.queue = groups.queue
|
|
50
|
+
AND job.group_id = groups.group_id
|
|
51
|
+
AND job.state IN ('waiting', 'prioritized', 'delayed')
|
|
52
|
+
)
|
|
53
|
+
AND (
|
|
54
|
+
groups.rate_window_started_at IS NULL
|
|
55
|
+
OR groups.rate_effective_max IS DISTINCT FROM COALESCE(groups.rate_limit, ${max})
|
|
56
|
+
OR groups.rate_effective_duration_ms IS DISTINCT FROM
|
|
57
|
+
COALESCE(groups.rate_duration_ms, ${duration})
|
|
58
|
+
OR groups.rate_window_started_at + COALESCE(groups.rate_duration_ms, ${duration}) <= ${now}
|
|
59
|
+
)
|
|
60
|
+
`;
|
|
61
|
+
}
|
|
62
|
+
/** Consume group budgets and move the durable round-robin cursor after row locks are secured. */
|
|
63
|
+
export async function commitPostgresGroupClaims(tx, ctx, input) {
|
|
64
|
+
const { queue, rows, options, sequence } = input;
|
|
65
|
+
const grouped = rows.filter((row) => row.group_id !== null);
|
|
66
|
+
if (grouped.length === 0)
|
|
67
|
+
return;
|
|
68
|
+
const turns = new Map();
|
|
69
|
+
for (let index = 0; index < grouped.length; index++) {
|
|
70
|
+
const groupId = grouped[index].group_id;
|
|
71
|
+
const current = turns.get(groupId);
|
|
72
|
+
turns.set(groupId, {
|
|
73
|
+
last: sequence + index + 1,
|
|
74
|
+
count: (current?.count ?? 0) + 1,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
const entries = [...turns.entries()];
|
|
78
|
+
await tx `
|
|
79
|
+
UPDATE bunqueue_group_state AS groups
|
|
80
|
+
SET last_served = batch.last_served,
|
|
81
|
+
rate_count = groups.rate_count + CASE WHEN ${options?.limit !== undefined}
|
|
82
|
+
THEN batch.claimed_count ELSE 0 END
|
|
83
|
+
FROM unnest(
|
|
84
|
+
${tx.array(entries.map(([groupId]) => groupId), 'TEXT')},
|
|
85
|
+
${tx.array(entries.map(([, value]) => value.last), 'BIGINT')},
|
|
86
|
+
${tx.array(entries.map(([, value]) => value.count), 'INTEGER')}
|
|
87
|
+
) AS batch(group_id, last_served, claimed_count)
|
|
88
|
+
WHERE groups.namespace = ${ctx.config.namespace} AND groups.queue = ${queue}
|
|
89
|
+
AND groups.group_id = batch.group_id
|
|
90
|
+
`;
|
|
91
|
+
await tx `
|
|
92
|
+
UPDATE bunqueue_queue_state SET group_sequence = ${sequence + grouped.length}
|
|
93
|
+
WHERE namespace = ${ctx.config.namespace} AND queue = ${queue}
|
|
94
|
+
`;
|
|
95
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/** Correctness-critical group schema and idempotent v19 repair operations. */
|
|
2
|
+
export declare const POSTGRES_GROUP_SCHEMA = "\nCREATE SEQUENCE IF NOT EXISTS bunqueue_group_order_seq AS BIGINT CACHE 1;\nALTER SEQUENCE bunqueue_group_order_seq\n AS BIGINT INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 1;\n\nALTER TABLE bunqueue_jobs ADD COLUMN IF NOT EXISTS group_order BIGINT;\nALTER TABLE bunqueue_jobs\n ALTER COLUMN group_order TYPE BIGINT USING group_order::bigint,\n ALTER COLUMN group_order DROP DEFAULT,\n ALTER COLUMN group_order DROP NOT NULL;\n\nDO $group_order_backfill$\nDECLARE\n target RECORD;\nBEGIN\n PERFORM setval(\n 'bunqueue_group_order_seq',\n GREATEST(\n (SELECT last_value FROM bunqueue_group_order_seq),\n COALESCE((SELECT MAX(group_order) FROM bunqueue_jobs), 0),\n 1\n ),\n TRUE\n );\n FOR target IN\n SELECT namespace, id\n FROM bunqueue_jobs\n WHERE group_id IS NOT NULL AND group_order IS NULL\n ORDER BY created_at, id, namespace\n LOOP\n UPDATE bunqueue_jobs\n SET group_order = nextval('bunqueue_group_order_seq')\n WHERE namespace = target.namespace AND id = target.id;\n END LOOP;\nEND;\n$group_order_backfill$;\n\nCREATE TABLE IF NOT EXISTS bunqueue_group_state (\n namespace TEXT NOT NULL,\n queue TEXT NOT NULL,\n group_id TEXT NOT NULL,\n rate_limit BIGINT,\n rate_duration_ms BIGINT,\n rate_window_started_at BIGINT,\n rate_count BIGINT NOT NULL DEFAULT 0,\n rate_effective_max BIGINT,\n rate_effective_duration_ms BIGINT,\n concurrency_limit BIGINT,\n last_served BIGINT,\n PRIMARY KEY (namespace, queue, group_id)\n);\nALTER TABLE bunqueue_group_state\n ADD COLUMN IF NOT EXISTS namespace TEXT,\n ADD COLUMN IF NOT EXISTS queue TEXT,\n ADD COLUMN IF NOT EXISTS group_id TEXT,\n ADD COLUMN IF NOT EXISTS rate_limit BIGINT,\n ADD COLUMN IF NOT EXISTS rate_duration_ms BIGINT,\n ADD COLUMN IF NOT EXISTS rate_window_started_at BIGINT,\n ADD COLUMN IF NOT EXISTS rate_count BIGINT DEFAULT 0,\n ADD COLUMN IF NOT EXISTS rate_effective_max BIGINT,\n ADD COLUMN IF NOT EXISTS rate_effective_duration_ms BIGINT,\n ADD COLUMN IF NOT EXISTS concurrency_limit BIGINT,\n ADD COLUMN IF NOT EXISTS last_served BIGINT;\nALTER TABLE bunqueue_group_state\n ALTER COLUMN namespace TYPE TEXT USING namespace::text,\n ALTER COLUMN queue TYPE TEXT USING queue::text,\n ALTER COLUMN group_id TYPE TEXT USING group_id::text,\n ALTER COLUMN rate_limit TYPE BIGINT USING rate_limit::bigint,\n ALTER COLUMN rate_duration_ms TYPE BIGINT USING rate_duration_ms::bigint,\n ALTER COLUMN rate_window_started_at TYPE BIGINT USING rate_window_started_at::bigint,\n ALTER COLUMN rate_count TYPE BIGINT USING rate_count::bigint,\n ALTER COLUMN rate_effective_max TYPE BIGINT USING rate_effective_max::bigint,\n ALTER COLUMN rate_effective_duration_ms TYPE BIGINT USING rate_effective_duration_ms::bigint,\n ALTER COLUMN concurrency_limit TYPE BIGINT USING concurrency_limit::bigint,\n ALTER COLUMN last_served TYPE BIGINT USING last_served::bigint;\nUPDATE bunqueue_group_state SET rate_count = 0 WHERE rate_count IS NULL;\nALTER TABLE bunqueue_group_state\n ALTER COLUMN namespace DROP DEFAULT,\n ALTER COLUMN namespace SET NOT NULL,\n ALTER COLUMN queue DROP DEFAULT,\n ALTER COLUMN queue SET NOT NULL,\n ALTER COLUMN group_id DROP DEFAULT,\n ALTER COLUMN group_id SET NOT NULL,\n ALTER COLUMN rate_limit DROP DEFAULT,\n ALTER COLUMN rate_limit DROP NOT NULL,\n ALTER COLUMN rate_duration_ms DROP DEFAULT,\n ALTER COLUMN rate_duration_ms DROP NOT NULL,\n ALTER COLUMN rate_window_started_at DROP DEFAULT,\n ALTER COLUMN rate_window_started_at DROP NOT NULL,\n ALTER COLUMN rate_count SET DEFAULT 0,\n ALTER COLUMN rate_count SET NOT NULL,\n ALTER COLUMN rate_effective_max DROP DEFAULT,\n ALTER COLUMN rate_effective_max DROP NOT NULL,\n ALTER COLUMN rate_effective_duration_ms DROP DEFAULT,\n ALTER COLUMN rate_effective_duration_ms DROP NOT NULL,\n ALTER COLUMN concurrency_limit DROP DEFAULT,\n ALTER COLUMN concurrency_limit DROP NOT NULL,\n ALTER COLUMN last_served DROP DEFAULT,\n ALTER COLUMN last_served DROP NOT NULL;\n\nDO $group_state_primary_key$\nDECLARE\n constraint_name TEXT;\nBEGIN\n IF NOT EXISTS (\n SELECT 1\n FROM pg_constraint AS constraint_state\n JOIN pg_index AS index_state ON index_state.indexrelid = constraint_state.conindid\n JOIN pg_class AS index_class ON index_class.oid = index_state.indexrelid\n JOIN pg_am AS access_method ON access_method.oid = index_class.relam\n WHERE constraint_state.conrelid = to_regclass('bunqueue_group_state')\n AND constraint_state.contype = 'p'\n AND constraint_state.convalidated\n AND NOT constraint_state.condeferrable\n AND NOT constraint_state.condeferred\n AND index_state.indisprimary AND index_state.indisunique\n AND index_state.indisvalid AND index_state.indisready\n AND access_method.amname = 'btree'\n AND ARRAY(\n SELECT attribute.attname::text\n FROM unnest(constraint_state.conkey) WITH ORDINALITY AS key(attnum, position)\n JOIN pg_attribute AS attribute\n ON attribute.attrelid = constraint_state.conrelid\n AND attribute.attnum = key.attnum\n ORDER BY key.position\n ) = ARRAY['namespace', 'queue', 'group_id']\n ) THEN\n FOR constraint_name IN\n SELECT conname FROM pg_constraint\n WHERE conrelid = to_regclass('bunqueue_group_state')\n AND (contype = 'p' OR conname = 'bunqueue_group_state_pkey')\n LOOP\n EXECUTE format(\n 'ALTER TABLE bunqueue_group_state DROP CONSTRAINT %I',\n constraint_name\n );\n END LOOP;\n IF to_regclass('bunqueue_group_state_pkey') IS NOT NULL THEN\n DROP INDEX bunqueue_group_state_pkey;\n END IF;\n ALTER TABLE bunqueue_group_state\n ADD CONSTRAINT bunqueue_group_state_pkey PRIMARY KEY (namespace, queue, group_id);\n END IF;\nEND;\n$group_state_primary_key$;\n\nDROP INDEX IF EXISTS bunqueue_jobs_group_ready_idx;\nCREATE INDEX bunqueue_jobs_group_ready_idx\n ON bunqueue_jobs(namespace, queue, group_id, run_at ASC, group_order ASC, id ASC)\n WHERE group_id IS NOT NULL AND state IN ('waiting', 'prioritized', 'delayed');\nDROP INDEX IF EXISTS bunqueue_group_state_rotation_idx;\nCREATE INDEX bunqueue_group_state_rotation_idx\n ON bunqueue_group_state(namespace, queue, last_served, group_id);\n";
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/** Correctness-critical group schema and idempotent v19 repair operations. */
|
|
2
|
+
export const POSTGRES_GROUP_SCHEMA = `
|
|
3
|
+
CREATE SEQUENCE IF NOT EXISTS bunqueue_group_order_seq AS BIGINT CACHE 1;
|
|
4
|
+
ALTER SEQUENCE bunqueue_group_order_seq
|
|
5
|
+
AS BIGINT INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 1;
|
|
6
|
+
|
|
7
|
+
ALTER TABLE bunqueue_jobs ADD COLUMN IF NOT EXISTS group_order BIGINT;
|
|
8
|
+
ALTER TABLE bunqueue_jobs
|
|
9
|
+
ALTER COLUMN group_order TYPE BIGINT USING group_order::bigint,
|
|
10
|
+
ALTER COLUMN group_order DROP DEFAULT,
|
|
11
|
+
ALTER COLUMN group_order DROP NOT NULL;
|
|
12
|
+
|
|
13
|
+
DO $group_order_backfill$
|
|
14
|
+
DECLARE
|
|
15
|
+
target RECORD;
|
|
16
|
+
BEGIN
|
|
17
|
+
PERFORM setval(
|
|
18
|
+
'bunqueue_group_order_seq',
|
|
19
|
+
GREATEST(
|
|
20
|
+
(SELECT last_value FROM bunqueue_group_order_seq),
|
|
21
|
+
COALESCE((SELECT MAX(group_order) FROM bunqueue_jobs), 0),
|
|
22
|
+
1
|
|
23
|
+
),
|
|
24
|
+
TRUE
|
|
25
|
+
);
|
|
26
|
+
FOR target IN
|
|
27
|
+
SELECT namespace, id
|
|
28
|
+
FROM bunqueue_jobs
|
|
29
|
+
WHERE group_id IS NOT NULL AND group_order IS NULL
|
|
30
|
+
ORDER BY created_at, id, namespace
|
|
31
|
+
LOOP
|
|
32
|
+
UPDATE bunqueue_jobs
|
|
33
|
+
SET group_order = nextval('bunqueue_group_order_seq')
|
|
34
|
+
WHERE namespace = target.namespace AND id = target.id;
|
|
35
|
+
END LOOP;
|
|
36
|
+
END;
|
|
37
|
+
$group_order_backfill$;
|
|
38
|
+
|
|
39
|
+
CREATE TABLE IF NOT EXISTS bunqueue_group_state (
|
|
40
|
+
namespace TEXT NOT NULL,
|
|
41
|
+
queue TEXT NOT NULL,
|
|
42
|
+
group_id TEXT NOT NULL,
|
|
43
|
+
rate_limit BIGINT,
|
|
44
|
+
rate_duration_ms BIGINT,
|
|
45
|
+
rate_window_started_at BIGINT,
|
|
46
|
+
rate_count BIGINT NOT NULL DEFAULT 0,
|
|
47
|
+
rate_effective_max BIGINT,
|
|
48
|
+
rate_effective_duration_ms BIGINT,
|
|
49
|
+
concurrency_limit BIGINT,
|
|
50
|
+
last_served BIGINT,
|
|
51
|
+
PRIMARY KEY (namespace, queue, group_id)
|
|
52
|
+
);
|
|
53
|
+
ALTER TABLE bunqueue_group_state
|
|
54
|
+
ADD COLUMN IF NOT EXISTS namespace TEXT,
|
|
55
|
+
ADD COLUMN IF NOT EXISTS queue TEXT,
|
|
56
|
+
ADD COLUMN IF NOT EXISTS group_id TEXT,
|
|
57
|
+
ADD COLUMN IF NOT EXISTS rate_limit BIGINT,
|
|
58
|
+
ADD COLUMN IF NOT EXISTS rate_duration_ms BIGINT,
|
|
59
|
+
ADD COLUMN IF NOT EXISTS rate_window_started_at BIGINT,
|
|
60
|
+
ADD COLUMN IF NOT EXISTS rate_count BIGINT DEFAULT 0,
|
|
61
|
+
ADD COLUMN IF NOT EXISTS rate_effective_max BIGINT,
|
|
62
|
+
ADD COLUMN IF NOT EXISTS rate_effective_duration_ms BIGINT,
|
|
63
|
+
ADD COLUMN IF NOT EXISTS concurrency_limit BIGINT,
|
|
64
|
+
ADD COLUMN IF NOT EXISTS last_served BIGINT;
|
|
65
|
+
ALTER TABLE bunqueue_group_state
|
|
66
|
+
ALTER COLUMN namespace TYPE TEXT USING namespace::text,
|
|
67
|
+
ALTER COLUMN queue TYPE TEXT USING queue::text,
|
|
68
|
+
ALTER COLUMN group_id TYPE TEXT USING group_id::text,
|
|
69
|
+
ALTER COLUMN rate_limit TYPE BIGINT USING rate_limit::bigint,
|
|
70
|
+
ALTER COLUMN rate_duration_ms TYPE BIGINT USING rate_duration_ms::bigint,
|
|
71
|
+
ALTER COLUMN rate_window_started_at TYPE BIGINT USING rate_window_started_at::bigint,
|
|
72
|
+
ALTER COLUMN rate_count TYPE BIGINT USING rate_count::bigint,
|
|
73
|
+
ALTER COLUMN rate_effective_max TYPE BIGINT USING rate_effective_max::bigint,
|
|
74
|
+
ALTER COLUMN rate_effective_duration_ms TYPE BIGINT USING rate_effective_duration_ms::bigint,
|
|
75
|
+
ALTER COLUMN concurrency_limit TYPE BIGINT USING concurrency_limit::bigint,
|
|
76
|
+
ALTER COLUMN last_served TYPE BIGINT USING last_served::bigint;
|
|
77
|
+
UPDATE bunqueue_group_state SET rate_count = 0 WHERE rate_count IS NULL;
|
|
78
|
+
ALTER TABLE bunqueue_group_state
|
|
79
|
+
ALTER COLUMN namespace DROP DEFAULT,
|
|
80
|
+
ALTER COLUMN namespace SET NOT NULL,
|
|
81
|
+
ALTER COLUMN queue DROP DEFAULT,
|
|
82
|
+
ALTER COLUMN queue SET NOT NULL,
|
|
83
|
+
ALTER COLUMN group_id DROP DEFAULT,
|
|
84
|
+
ALTER COLUMN group_id SET NOT NULL,
|
|
85
|
+
ALTER COLUMN rate_limit DROP DEFAULT,
|
|
86
|
+
ALTER COLUMN rate_limit DROP NOT NULL,
|
|
87
|
+
ALTER COLUMN rate_duration_ms DROP DEFAULT,
|
|
88
|
+
ALTER COLUMN rate_duration_ms DROP NOT NULL,
|
|
89
|
+
ALTER COLUMN rate_window_started_at DROP DEFAULT,
|
|
90
|
+
ALTER COLUMN rate_window_started_at DROP NOT NULL,
|
|
91
|
+
ALTER COLUMN rate_count SET DEFAULT 0,
|
|
92
|
+
ALTER COLUMN rate_count SET NOT NULL,
|
|
93
|
+
ALTER COLUMN rate_effective_max DROP DEFAULT,
|
|
94
|
+
ALTER COLUMN rate_effective_max DROP NOT NULL,
|
|
95
|
+
ALTER COLUMN rate_effective_duration_ms DROP DEFAULT,
|
|
96
|
+
ALTER COLUMN rate_effective_duration_ms DROP NOT NULL,
|
|
97
|
+
ALTER COLUMN concurrency_limit DROP DEFAULT,
|
|
98
|
+
ALTER COLUMN concurrency_limit DROP NOT NULL,
|
|
99
|
+
ALTER COLUMN last_served DROP DEFAULT,
|
|
100
|
+
ALTER COLUMN last_served DROP NOT NULL;
|
|
101
|
+
|
|
102
|
+
DO $group_state_primary_key$
|
|
103
|
+
DECLARE
|
|
104
|
+
constraint_name TEXT;
|
|
105
|
+
BEGIN
|
|
106
|
+
IF NOT EXISTS (
|
|
107
|
+
SELECT 1
|
|
108
|
+
FROM pg_constraint AS constraint_state
|
|
109
|
+
JOIN pg_index AS index_state ON index_state.indexrelid = constraint_state.conindid
|
|
110
|
+
JOIN pg_class AS index_class ON index_class.oid = index_state.indexrelid
|
|
111
|
+
JOIN pg_am AS access_method ON access_method.oid = index_class.relam
|
|
112
|
+
WHERE constraint_state.conrelid = to_regclass('bunqueue_group_state')
|
|
113
|
+
AND constraint_state.contype = 'p'
|
|
114
|
+
AND constraint_state.convalidated
|
|
115
|
+
AND NOT constraint_state.condeferrable
|
|
116
|
+
AND NOT constraint_state.condeferred
|
|
117
|
+
AND index_state.indisprimary AND index_state.indisunique
|
|
118
|
+
AND index_state.indisvalid AND index_state.indisready
|
|
119
|
+
AND access_method.amname = 'btree'
|
|
120
|
+
AND ARRAY(
|
|
121
|
+
SELECT attribute.attname::text
|
|
122
|
+
FROM unnest(constraint_state.conkey) WITH ORDINALITY AS key(attnum, position)
|
|
123
|
+
JOIN pg_attribute AS attribute
|
|
124
|
+
ON attribute.attrelid = constraint_state.conrelid
|
|
125
|
+
AND attribute.attnum = key.attnum
|
|
126
|
+
ORDER BY key.position
|
|
127
|
+
) = ARRAY['namespace', 'queue', 'group_id']
|
|
128
|
+
) THEN
|
|
129
|
+
FOR constraint_name IN
|
|
130
|
+
SELECT conname FROM pg_constraint
|
|
131
|
+
WHERE conrelid = to_regclass('bunqueue_group_state')
|
|
132
|
+
AND (contype = 'p' OR conname = 'bunqueue_group_state_pkey')
|
|
133
|
+
LOOP
|
|
134
|
+
EXECUTE format(
|
|
135
|
+
'ALTER TABLE bunqueue_group_state DROP CONSTRAINT %I',
|
|
136
|
+
constraint_name
|
|
137
|
+
);
|
|
138
|
+
END LOOP;
|
|
139
|
+
IF to_regclass('bunqueue_group_state_pkey') IS NOT NULL THEN
|
|
140
|
+
DROP INDEX bunqueue_group_state_pkey;
|
|
141
|
+
END IF;
|
|
142
|
+
ALTER TABLE bunqueue_group_state
|
|
143
|
+
ADD CONSTRAINT bunqueue_group_state_pkey PRIMARY KEY (namespace, queue, group_id);
|
|
144
|
+
END IF;
|
|
145
|
+
END;
|
|
146
|
+
$group_state_primary_key$;
|
|
147
|
+
|
|
148
|
+
DROP INDEX IF EXISTS bunqueue_jobs_group_ready_idx;
|
|
149
|
+
CREATE INDEX bunqueue_jobs_group_ready_idx
|
|
150
|
+
ON bunqueue_jobs(namespace, queue, group_id, run_at ASC, group_order ASC, id ASC)
|
|
151
|
+
WHERE group_id IS NOT NULL AND state IN ('waiting', 'prioritized', 'delayed');
|
|
152
|
+
DROP INDEX IF EXISTS bunqueue_group_state_rotation_idx;
|
|
153
|
+
CREATE INDEX bunqueue_group_state_rotation_idx
|
|
154
|
+
ON bunqueue_group_state(namespace, queue, last_served, group_id);
|
|
155
|
+
`;
|