bullmq 6.3.11 → 6.3.13

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.
Files changed (204) hide show
  1. package/dist/cjs/classes/queue-getters.js +3 -1
  2. package/dist/cjs/classes/queue.js +2 -1
  3. package/dist/cjs/classes/redis-connection.js +1 -1
  4. package/dist/cjs/classes/redis-queue-backend.js +1 -1
  5. package/dist/cjs/commands/includes/moveChildBackToParentDependencies.lua +20 -0
  6. package/dist/cjs/commands/moveJobsToWait-8.lua +3 -0
  7. package/dist/cjs/commands/reprocessJob-7.lua +2 -14
  8. package/dist/cjs/postgres/sql-loader.js +27 -41
  9. package/dist/cjs/postgres/sql-scripts.js +110 -0
  10. package/dist/cjs/scripts/moveJobsToWait-8.js +21 -0
  11. package/dist/cjs/scripts/reprocessJob-7.js +20 -13
  12. package/dist/cjs/tsconfig-cjs.tsbuildinfo +1 -1
  13. package/dist/cjs/version.js +1 -1
  14. package/dist/esm/classes/queue-getters.d.ts +3 -1
  15. package/dist/esm/classes/queue-getters.js +3 -1
  16. package/dist/esm/classes/queue.d.ts +2 -1
  17. package/dist/esm/classes/queue.js +2 -1
  18. package/dist/esm/classes/redis-connection.d.ts +1 -1
  19. package/dist/esm/classes/redis-connection.js +1 -1
  20. package/dist/esm/classes/redis-queue-backend.d.ts +1 -1
  21. package/dist/esm/classes/redis-queue-backend.js +1 -1
  22. package/dist/esm/commands/includes/moveChildBackToParentDependencies.lua +20 -0
  23. package/dist/esm/commands/moveJobsToWait-8.lua +3 -0
  24. package/dist/esm/commands/reprocessJob-7.lua +2 -14
  25. package/dist/esm/postgres/sql-loader.d.ts +6 -1
  26. package/dist/esm/postgres/sql-loader.js +27 -41
  27. package/dist/esm/postgres/sql-scripts.d.ts +10 -0
  28. package/dist/esm/postgres/sql-scripts.js +107 -0
  29. package/dist/esm/scripts/moveJobsToWait-8.js +21 -0
  30. package/dist/esm/scripts/reprocessJob-7.js +20 -13
  31. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  32. package/dist/esm/version.d.ts +1 -1
  33. package/dist/esm/version.js +1 -1
  34. package/package.json +10 -9
  35. package/dist/cjs/postgres/commands/add_flow.sql +0 -3
  36. package/dist/cjs/postgres/commands/add_job.sql +0 -5
  37. package/dist/cjs/postgres/commands/add_job_scheduler.sql +0 -6
  38. package/dist/cjs/postgres/commands/add_jobs_bulk.sql +0 -3
  39. package/dist/cjs/postgres/commands/add_log.sql +0 -14
  40. package/dist/cjs/postgres/commands/change_delay.sql +0 -3
  41. package/dist/cjs/postgres/commands/change_priority.sql +0 -3
  42. package/dist/cjs/postgres/commands/clean.sql +0 -3
  43. package/dist/cjs/postgres/commands/clear_logs.sql +0 -9
  44. package/dist/cjs/postgres/commands/collect_metrics.sql +0 -4
  45. package/dist/cjs/postgres/commands/delete_deduplication_key.sql +0 -6
  46. package/dist/cjs/postgres/commands/drain.sql +0 -2
  47. package/dist/cjs/postgres/commands/extend_lock.sql +0 -3
  48. package/dist/cjs/postgres/commands/extend_locks.sql +0 -25
  49. package/dist/cjs/postgres/commands/get_client_list.sql +0 -11
  50. package/dist/cjs/postgres/commands/get_counts.sql +0 -13
  51. package/dist/cjs/postgres/commands/get_counts_per_priority.sql +0 -14
  52. package/dist/cjs/postgres/commands/get_deduplication_job_id.sql +0 -7
  53. package/dist/cjs/postgres/commands/get_dependencies.sql +0 -8
  54. package/dist/cjs/postgres/commands/get_dependencies_page.sql +0 -7
  55. package/dist/cjs/postgres/commands/get_dependency_counts.sql +0 -9
  56. package/dist/cjs/postgres/commands/get_ignored_children_failures.sql +0 -7
  57. package/dist/cjs/postgres/commands/get_job_data.sql +0 -2
  58. package/dist/cjs/postgres/commands/get_job_logs_asc.sql +0 -6
  59. package/dist/cjs/postgres/commands/get_job_logs_count.sql +0 -2
  60. package/dist/cjs/postgres/commands/get_job_logs_desc.sql +0 -6
  61. package/dist/cjs/postgres/commands/get_job_scheduler.sql +0 -6
  62. package/dist/cjs/postgres/commands/get_job_schedulers_count.sql +0 -2
  63. package/dist/cjs/postgres/commands/get_job_schedulers_range.sql +0 -10
  64. package/dist/cjs/postgres/commands/get_metrics.sql +0 -14
  65. package/dist/cjs/postgres/commands/get_processed_children_values.sql +0 -8
  66. package/dist/cjs/postgres/commands/get_queue_meta.sql +0 -2
  67. package/dist/cjs/postgres/commands/get_queue_meta_field.sql +0 -2
  68. package/dist/cjs/postgres/commands/get_queue_meta_fields.sql +0 -3
  69. package/dist/cjs/postgres/commands/get_range.sql +0 -3
  70. package/dist/cjs/postgres/commands/get_rate_limit_ttl.sql +0 -3
  71. package/dist/cjs/postgres/commands/get_state.sql +0 -2
  72. package/dist/cjs/postgres/commands/has_queue_meta_field.sql +0 -4
  73. package/dist/cjs/postgres/commands/has_waiting_job.sql +0 -11
  74. package/dist/cjs/postgres/commands/is_finished.sql +0 -4
  75. package/dist/cjs/postgres/commands/is_job_in_state.sql +0 -6
  76. package/dist/cjs/postgres/commands/is_job_in_wait.sql +0 -15
  77. package/dist/cjs/postgres/commands/is_job_prioritized.sql +0 -6
  78. package/dist/cjs/postgres/commands/is_job_scheduler.sql +0 -4
  79. package/dist/cjs/postgres/commands/is_maxed.sql +0 -10
  80. package/dist/cjs/postgres/commands/listen_events.sql +0 -3
  81. package/dist/cjs/postgres/commands/listen_jobs.sql +0 -4
  82. package/dist/cjs/postgres/commands/move_active_to_wait.sql +0 -4
  83. package/dist/cjs/postgres/commands/move_stalled_jobs_to_wait.sql +0 -4
  84. package/dist/cjs/postgres/commands/move_to_active.sql +0 -4
  85. package/dist/cjs/postgres/commands/move_to_completed.sql +0 -4
  86. package/dist/cjs/postgres/commands/move_to_completed_fetch.sql +0 -9
  87. package/dist/cjs/postgres/commands/move_to_delayed.sql +0 -4
  88. package/dist/cjs/postgres/commands/move_to_failed.sql +0 -4
  89. package/dist/cjs/postgres/commands/move_to_failed_fetch.sql +0 -9
  90. package/dist/cjs/postgres/commands/move_to_waiting_children.sql +0 -3
  91. package/dist/cjs/postgres/commands/next_delay.sql +0 -2
  92. package/dist/cjs/postgres/commands/next_signal.sql +0 -3
  93. package/dist/cjs/postgres/commands/obliterate.sql +0 -4
  94. package/dist/cjs/postgres/commands/paginate_dependencies.sql +0 -19
  95. package/dist/cjs/postgres/commands/pause.sql +0 -2
  96. package/dist/cjs/postgres/commands/promote.sql +0 -3
  97. package/dist/cjs/postgres/commands/promote_jobs.sql +0 -3
  98. package/dist/cjs/postgres/commands/publish_event.sql +0 -3
  99. package/dist/cjs/postgres/commands/read_events.sql +0 -6
  100. package/dist/cjs/postgres/commands/read_events_max.sql +0 -3
  101. package/dist/cjs/postgres/commands/remove.sql +0 -3
  102. package/dist/cjs/postgres/commands/remove_child_dependency.sql +0 -4
  103. package/dist/cjs/postgres/commands/remove_deduplication_key.sql +0 -9
  104. package/dist/cjs/postgres/commands/remove_job_scheduler.sql +0 -3
  105. package/dist/cjs/postgres/commands/remove_queue_meta_fields.sql +0 -2
  106. package/dist/cjs/postgres/commands/remove_rate_limit.sql +0 -6
  107. package/dist/cjs/postgres/commands/remove_unprocessed_children.sql +0 -3
  108. package/dist/cjs/postgres/commands/reprocess_job.sql +0 -4
  109. package/dist/cjs/postgres/commands/retry_job.sql +0 -3
  110. package/dist/cjs/postgres/commands/retry_jobs.sql +0 -3
  111. package/dist/cjs/postgres/commands/set_queue_meta.sql +0 -4
  112. package/dist/cjs/postgres/commands/set_rate_limit.sql +0 -3
  113. package/dist/cjs/postgres/commands/trim_logs.sql +0 -3
  114. package/dist/cjs/postgres/commands/update_data.sql +0 -3
  115. package/dist/cjs/postgres/commands/update_job_scheduler.sql +0 -5
  116. package/dist/cjs/postgres/commands/update_progress.sql +0 -3
  117. package/dist/cjs/postgres/migrations/0001_schema.sql +0 -348
  118. package/dist/cjs/postgres/migrations/0002_functions.sql +0 -3473
  119. package/dist/cjs/postgres/migrations/0003_dedup_stale_key.sql +0 -147
  120. package/dist/esm/postgres/commands/add_flow.sql +0 -3
  121. package/dist/esm/postgres/commands/add_job.sql +0 -5
  122. package/dist/esm/postgres/commands/add_job_scheduler.sql +0 -6
  123. package/dist/esm/postgres/commands/add_jobs_bulk.sql +0 -3
  124. package/dist/esm/postgres/commands/add_log.sql +0 -14
  125. package/dist/esm/postgres/commands/change_delay.sql +0 -3
  126. package/dist/esm/postgres/commands/change_priority.sql +0 -3
  127. package/dist/esm/postgres/commands/clean.sql +0 -3
  128. package/dist/esm/postgres/commands/clear_logs.sql +0 -9
  129. package/dist/esm/postgres/commands/collect_metrics.sql +0 -4
  130. package/dist/esm/postgres/commands/delete_deduplication_key.sql +0 -6
  131. package/dist/esm/postgres/commands/drain.sql +0 -2
  132. package/dist/esm/postgres/commands/extend_lock.sql +0 -3
  133. package/dist/esm/postgres/commands/extend_locks.sql +0 -25
  134. package/dist/esm/postgres/commands/get_client_list.sql +0 -11
  135. package/dist/esm/postgres/commands/get_counts.sql +0 -13
  136. package/dist/esm/postgres/commands/get_counts_per_priority.sql +0 -14
  137. package/dist/esm/postgres/commands/get_deduplication_job_id.sql +0 -7
  138. package/dist/esm/postgres/commands/get_dependencies.sql +0 -8
  139. package/dist/esm/postgres/commands/get_dependencies_page.sql +0 -7
  140. package/dist/esm/postgres/commands/get_dependency_counts.sql +0 -9
  141. package/dist/esm/postgres/commands/get_ignored_children_failures.sql +0 -7
  142. package/dist/esm/postgres/commands/get_job_data.sql +0 -2
  143. package/dist/esm/postgres/commands/get_job_logs_asc.sql +0 -6
  144. package/dist/esm/postgres/commands/get_job_logs_count.sql +0 -2
  145. package/dist/esm/postgres/commands/get_job_logs_desc.sql +0 -6
  146. package/dist/esm/postgres/commands/get_job_scheduler.sql +0 -6
  147. package/dist/esm/postgres/commands/get_job_schedulers_count.sql +0 -2
  148. package/dist/esm/postgres/commands/get_job_schedulers_range.sql +0 -10
  149. package/dist/esm/postgres/commands/get_metrics.sql +0 -14
  150. package/dist/esm/postgres/commands/get_processed_children_values.sql +0 -8
  151. package/dist/esm/postgres/commands/get_queue_meta.sql +0 -2
  152. package/dist/esm/postgres/commands/get_queue_meta_field.sql +0 -2
  153. package/dist/esm/postgres/commands/get_queue_meta_fields.sql +0 -3
  154. package/dist/esm/postgres/commands/get_range.sql +0 -3
  155. package/dist/esm/postgres/commands/get_rate_limit_ttl.sql +0 -3
  156. package/dist/esm/postgres/commands/get_state.sql +0 -2
  157. package/dist/esm/postgres/commands/has_queue_meta_field.sql +0 -4
  158. package/dist/esm/postgres/commands/has_waiting_job.sql +0 -11
  159. package/dist/esm/postgres/commands/is_finished.sql +0 -4
  160. package/dist/esm/postgres/commands/is_job_in_state.sql +0 -6
  161. package/dist/esm/postgres/commands/is_job_in_wait.sql +0 -15
  162. package/dist/esm/postgres/commands/is_job_prioritized.sql +0 -6
  163. package/dist/esm/postgres/commands/is_job_scheduler.sql +0 -4
  164. package/dist/esm/postgres/commands/is_maxed.sql +0 -10
  165. package/dist/esm/postgres/commands/listen_events.sql +0 -3
  166. package/dist/esm/postgres/commands/listen_jobs.sql +0 -4
  167. package/dist/esm/postgres/commands/move_active_to_wait.sql +0 -4
  168. package/dist/esm/postgres/commands/move_stalled_jobs_to_wait.sql +0 -4
  169. package/dist/esm/postgres/commands/move_to_active.sql +0 -4
  170. package/dist/esm/postgres/commands/move_to_completed.sql +0 -4
  171. package/dist/esm/postgres/commands/move_to_completed_fetch.sql +0 -9
  172. package/dist/esm/postgres/commands/move_to_delayed.sql +0 -4
  173. package/dist/esm/postgres/commands/move_to_failed.sql +0 -4
  174. package/dist/esm/postgres/commands/move_to_failed_fetch.sql +0 -9
  175. package/dist/esm/postgres/commands/move_to_waiting_children.sql +0 -3
  176. package/dist/esm/postgres/commands/next_delay.sql +0 -2
  177. package/dist/esm/postgres/commands/next_signal.sql +0 -3
  178. package/dist/esm/postgres/commands/obliterate.sql +0 -4
  179. package/dist/esm/postgres/commands/paginate_dependencies.sql +0 -19
  180. package/dist/esm/postgres/commands/pause.sql +0 -2
  181. package/dist/esm/postgres/commands/promote.sql +0 -3
  182. package/dist/esm/postgres/commands/promote_jobs.sql +0 -3
  183. package/dist/esm/postgres/commands/publish_event.sql +0 -3
  184. package/dist/esm/postgres/commands/read_events.sql +0 -6
  185. package/dist/esm/postgres/commands/read_events_max.sql +0 -3
  186. package/dist/esm/postgres/commands/remove.sql +0 -3
  187. package/dist/esm/postgres/commands/remove_child_dependency.sql +0 -4
  188. package/dist/esm/postgres/commands/remove_deduplication_key.sql +0 -9
  189. package/dist/esm/postgres/commands/remove_job_scheduler.sql +0 -3
  190. package/dist/esm/postgres/commands/remove_queue_meta_fields.sql +0 -2
  191. package/dist/esm/postgres/commands/remove_rate_limit.sql +0 -6
  192. package/dist/esm/postgres/commands/remove_unprocessed_children.sql +0 -3
  193. package/dist/esm/postgres/commands/reprocess_job.sql +0 -4
  194. package/dist/esm/postgres/commands/retry_job.sql +0 -3
  195. package/dist/esm/postgres/commands/retry_jobs.sql +0 -3
  196. package/dist/esm/postgres/commands/set_queue_meta.sql +0 -4
  197. package/dist/esm/postgres/commands/set_rate_limit.sql +0 -3
  198. package/dist/esm/postgres/commands/trim_logs.sql +0 -3
  199. package/dist/esm/postgres/commands/update_data.sql +0 -3
  200. package/dist/esm/postgres/commands/update_job_scheduler.sql +0 -5
  201. package/dist/esm/postgres/commands/update_progress.sql +0 -3
  202. package/dist/esm/postgres/migrations/0001_schema.sql +0 -348
  203. package/dist/esm/postgres/migrations/0002_functions.sql +0 -3473
  204. package/dist/esm/postgres/migrations/0003_dedup_stale_key.sql +0 -147
@@ -1,9 +0,0 @@
1
- -- Conditionally remove a deduplication key, only when the given job is still
2
- -- its (live) winner (mirrors removeDeduplicationKeyIfNeededOnRemoval and the
3
- -- job-instance Job.removeDeduplicationKey, where Redis GET returns nil for an
4
- -- expired key). Params: $1 queue, $2 dedup_id, $3 job_id, $4 now (ms). Returns
5
- -- the removed key (0 or 1 row).
6
- DELETE FROM dedup
7
- WHERE queue = $1 AND dedup_id = $2 AND job_id = $3
8
- AND (expire_at_ms IS NULL OR expire_at_ms > $4)
9
- RETURNING dedup_id;
@@ -1,3 +0,0 @@
1
- -- Remove a scheduler and its still-pending job (emitting `removed` events);
2
- -- returns 1 if the scheduler existed, 0 otherwise. Params: $1 queue, $2 id.
3
- SELECT remove_job_scheduler($1, $2) AS removed;
@@ -1,2 +0,0 @@
1
- -- Remove queue metadata fields. Params: $1 queue, $2 fields (text[]).
2
- DELETE FROM meta WHERE queue = $1 AND field = ANY($2::text[]);
@@ -1,6 +0,0 @@
1
- -- Clear the limiter window; returns the number of rows removed (0 or 1).
2
- -- Param: $1 queue.
3
- WITH d AS (
4
- DELETE FROM rate_limit WHERE queue = $1 RETURNING 1
5
- )
6
- SELECT count(*)::int AS n FROM d;
@@ -1,3 +0,0 @@
1
- -- Recursively remove a parent's still-pending children. Params: $1 queue,
2
- -- $2 job_id.
3
- SELECT remove_unprocessed_children($1, $2);
@@ -1,4 +0,0 @@
1
- -- Re-queue a finished job (failed/completed) back to wait. Params: $1 queue,
2
- -- $2 id, $3 state, $4 lifo, $5 reset_attempts_made, $6 reset_attempts_started.
3
- -- Returns 1 ok, -1 missing, -3 not in the expected state.
4
- SELECT reprocess_job($1, $2, $3, $4, $5, $6) AS code;
@@ -1,3 +0,0 @@
1
- -- Re-queue an active job back to waiting immediately (retry now).
2
- -- Params: $1 queue, $2 id, $3 token, $4 lifo, $5 failed_reason, $6 stacktrace (jsonb).
3
- SELECT retry_job($1, $2, $3, $4, $5, $6::jsonb) AS n;
@@ -1,3 +0,0 @@
1
- -- Move up to `count` finished jobs back to waiting; returns the number moved.
2
- -- Params: $1 queue, $2 state, $3 count, $4 timestamp.
3
- SELECT retry_jobs($1, $2, $3, $4) AS n;
@@ -1,4 +0,0 @@
1
- -- Upsert queue metadata fields. Params: $1 queue, $2 fields (text[]), $3 values (text[]).
2
- INSERT INTO meta (queue, field, value)
3
- SELECT $1, f, v FROM unnest($2::text[], $3::text[]) AS t(f, v)
4
- ON CONFLICT (queue, field) DO UPDATE SET value = EXCLUDED.value;
@@ -1,3 +0,0 @@
1
- -- Force the limiter window (dynamic / manual rate limit).
2
- -- Params: $1 queue, $2 expire ms, $3 now_ms.
3
- SELECT set_rate_limit($1, $2, $3);
@@ -1,3 +0,0 @@
1
- -- Trim a job's oldest logs. Params: $1 queue, $2 job_id, $3 min_idx_to_keep.
2
- DELETE FROM job_log
3
- WHERE queue = $1 AND job_id = $2 AND idx < $3;
@@ -1,3 +0,0 @@
1
- -- Replace a job's data payload. Params: $1 queue, $2 id, $3 data (jsonb).
2
- UPDATE job SET data = $3::jsonb WHERE queue = $1 AND id = $2
3
- RETURNING id;
@@ -1,5 +0,0 @@
1
- -- Advance an existing scheduler to its next iteration (no template change);
2
- -- returns the new delayed job id, or NULL if the scheduler is gone.
3
- -- Params: $1 queue, $2 scheduler_id, $3 next_millis, $4 template_data (jsonb),
4
- -- $5 delayed_opts (jsonb), $6 now, $7 producer_id.
5
- SELECT update_job_scheduler_next_millis($1, $2, $3, $4, $5, $6, $7) AS job_id;
@@ -1,3 +0,0 @@
1
- -- Update a job's progress and emit a 'progress' event. Returns the number of
2
- -- rows updated (0 = missing job). Params: $1 queue, $2 id, $3 progress (jsonb).
3
- SELECT update_progress($1, $2, $3::jsonb) AS updated;
@@ -1,348 +0,0 @@
1
- -- BullMQ PostgreSQL backend — schema (types, sequences, tables, indexes).
2
- --
3
- -- Consolidated initial schema. All BullMQ objects live in a connection-level
4
- -- schema (namespace); the operation functions live in 0002_functions.sql.
5
-
6
- -- BullMQ PostgreSQL backend — initial schema (schema version 1).
7
- --
8
- -- This file is the *portable source of truth* for the schema. It uses only
9
- -- standard SQL / PL-pgSQL so it can be shared verbatim with the future Elixir
10
- -- and Python ports (which call the very same tables and functions).
11
- --
12
- -- All objects are created inside the backend's configured *schema* (the
13
- -- connection-level namespace, default `bullmq`). The migration runner has
14
- -- already created the schema and set `search_path` to it, so the unqualified
15
- -- names below resolve into that schema. Unlike Redis — where a per-queue key
16
- -- `prefix` namespaces every key — SQL uses the schema as the single namespace
17
- -- for the whole connection, so there is no per-row/per-queue prefix.
18
- --
19
- -- The migration ledger table (`migration`) is bootstrapped by the
20
- -- migration runner itself, so it is intentionally not created here.
21
- --
22
- -- v1 only establishes the foundation that is independent of the job-storage
23
- -- model: per-queue metadata. Subsequent migrations add the job tables,
24
- -- indexes and the atomic operation functions.
25
-
26
- -- Per-queue metadata. Mirrors the Redis `<prefix>:<queue>:meta` hash: a small
27
- -- key/value store keyed by (queue, field). Used for the queue version, global
28
- -- concurrency, global rate limit, paused flag, etc.
29
- CREATE TABLE IF NOT EXISTS meta (
30
- queue text NOT NULL,
31
- field text NOT NULL,
32
- value text,
33
- PRIMARY KEY (queue, field)
34
- );
35
-
36
- -- BullMQ PostgreSQL backend — core job schema (schema version 2).
37
- --
38
- -- Portable source of truth (standard SQL / PL-pgSQL) shared verbatim with the
39
- -- future Elixir and Python ports. This migration establishes the *storage
40
- -- model* (tables + indexes) for every BullMQ feature: FIFO/LIFO, priority,
41
- -- delayed jobs, concurrency, locks/stalled detection, flows (parent/child
42
- -- dependencies), deduplication, rate limiting, repeatable job schedulers, the
43
- -- event stream and metrics. The atomic *operation* functions (addJob,
44
- -- moveToActive, …) are layered on top in subsequent migrations.
45
- --
46
- -- ──────────────────────────────────────────────────────────────────────────
47
- -- Design overview
48
- -- ──────────────────────────────────────────────────────────────────────────
49
- -- * Namespace = the connection's PostgreSQL *schema* (default `bullmq`), not a
50
- -- per-queue key prefix. In Redis a `prefix` namespaces every key because the
51
- -- keyspace is shared; in SQL the schema already isolates BullMQ from the
52
- -- user's other tables, and a different schema (or database) gives a fully
53
- -- independent BullMQ namespace. So there is no per-row/per-queue prefix — the
54
- -- `queue` column alone discriminates within the schema.
55
- --
56
- -- * Single `job` table keyed by (queue, id). The Redis adapter spreads a
57
- -- job's lifecycle across many keys (wait list, prioritized zset, delayed zset,
58
- -- active list, completed/failed zsets, locks, …); here a single `state`
59
- -- column plus a handful of promoted, indexable columns replaces all of them.
60
- -- State transitions are plain `UPDATE`s; claiming uses `FOR UPDATE SKIP
61
- -- LOCKED` so concurrent workers never block each other.
62
- --
63
- -- * Time is stored as `bigint` epoch-milliseconds (suffix `_ms`) to match
64
- -- BullMQ's millisecond API exactly and avoid timezone math on hot paths.
65
- --
66
- -- * Partial indexes are state-scoped: each hot path (claim next ready job,
67
- -- promote due delayed jobs, find stalled active jobs, clean finished jobs)
68
- -- has a small index that only contains the rows in the relevant state.
69
- --
70
- -- * "paused" and "prioritized" are NOT physical states. Pausing is an O(1)
71
- -- queue-level flag in `meta`; a prioritized job is simply a waiting job
72
- -- with `priority > 0`. This avoids the bulk row rewrites Redis performs on
73
- -- pause and removes the need for a separate prioritized structure — full
74
- -- `ORDER BY (priority, seq)` does the job.
75
- -- ──────────────────────────────────────────────────────────────────────────
76
-
77
- -- Physical job states. (Pause = meta flag; prioritized = waiting + priority>0.)
78
- CREATE TYPE job_state AS ENUM (
79
- 'waiting',
80
- 'active',
81
- 'completed',
82
- 'failed',
83
- 'delayed',
84
- 'waiting-children'
85
- );
86
-
87
- -- Status of a parent→child dependency (flows).
88
- CREATE TYPE dep_status AS ENUM (
89
- 'pending', -- child not finished yet
90
- 'processed', -- child completed; `value` holds its return value
91
- 'ignored', -- child failed but parent was told to ignore it; `value` = reason
92
- 'failed' -- child failed and the failure is recorded against the parent
93
- );
94
-
95
- -- Global monotonic ordering for FIFO. A single sequence gives a total order
96
- -- across all queues; FIFO within a queue is preserved because `seq` increases
97
- -- with insertion. LIFO jobs are stored with a *negative* `seq` (from the same
98
- -- sequence, negated) so they sort ahead of all FIFO jobs and, among themselves,
99
- -- most-recently-added first — reproducing "last in, first out" with one column.
100
- CREATE SEQUENCE job_seq;
101
-
102
- -- Global monotonic id for the event stream (the Postgres analogue of a Redis
103
- -- stream entry id). Consumers page forward with `id > cursor`.
104
- CREATE SEQUENCE event_seq;
105
-
106
- -- ──────────────────────────────────────────────────────────────────────────
107
- -- Jobs
108
- -- ──────────────────────────────────────────────────────────────────────────
109
- CREATE TABLE job (
110
- queue text NOT NULL,
111
- id text NOT NULL, -- custom id, or numeric counter as text
112
- seq bigint NOT NULL, -- FIFO order (negative = LIFO); see sequence above
113
- name text NOT NULL,
114
- state job_state NOT NULL,
115
-
116
- -- Payload / options. BullMQ serializes these as JSON strings; stored as jsonb
117
- -- (always valid JSON) so they are queryable and compact.
118
- data jsonb NOT NULL DEFAULT '{}'::jsonb,
119
- opts jsonb NOT NULL DEFAULT '{}'::jsonb,
120
-
121
- -- Promoted, frequently-read scalars (also live implicitly in `opts`, but are
122
- -- broken out so they can be indexed / updated cheaply).
123
- priority integer NOT NULL DEFAULT 0, -- 0 = no priority (FIFO); >0 = prioritized
124
- delay_ms bigint NOT NULL DEFAULT 0,
125
- max_attempts integer NOT NULL DEFAULT 1,
126
- attempts_made integer NOT NULL DEFAULT 0,
127
- attempts_started integer NOT NULL DEFAULT 0,
128
-
129
- -- Progress / results.
130
- progress jsonb, -- number or object
131
- return_value jsonb, -- BullMQ `returnvalue`
132
- failed_reason text,
133
- stacktrace jsonb, -- array of frames
134
- deferred_failure text,
135
- processed_by text, -- worker id that processed it
136
-
137
- -- Timestamps (epoch ms).
138
- added_at_ms bigint NOT NULL, -- BullMQ `timestamp`
139
- process_at_ms bigint, -- when a delayed job becomes ready
140
- processed_at_ms bigint, -- BullMQ `processedOn`
141
- finished_at_ms bigint, -- BullMQ `finishedOn`
142
-
143
- -- Locking / stalled detection. An active job is "locked" until
144
- -- `locked_until_ms`; once it elapses the job is considered stalled.
145
- lock_token text,
146
- locked_until_ms bigint,
147
- stalled_count integer NOT NULL DEFAULT 0,
148
-
149
- -- Deduplication / scheduler linkage.
150
- dedup_id text, -- BullMQ deduplicationId / debounceId
151
- scheduler_id text, -- id of the scheduler that produced this job (BullMQ repeatJobKey)
152
-
153
- -- Flow (parent/child) linkage. A child stores its parent's coordinates; a
154
- -- parent tracks how many children are still unfinished.
155
- parent_queue text,
156
- parent_id text,
157
- parent_key text, -- denormalized "<queue>:<id>" (Redis-compatible "<prefix>:<queue>:<id>")
158
- pending_deps integer NOT NULL DEFAULT 0,
159
- -- Two-phase stalled mark/sweep: set on the first stalled pass, reclaimed on
160
- -- the next (see move_stalled_jobs_to_wait in 0002_functions.sql).
161
- stalled_marked boolean NOT NULL DEFAULT false,
162
-
163
- PRIMARY KEY (queue, id)
164
- );
165
-
166
- -- Claim next ready job: waiting jobs ordered by (priority, seq). Covers both the
167
- -- "wait" (priority = 0) and "prioritized" (priority > 0) views, and the FIFO/LIFO
168
- -- ordering via the signed `seq`. Used with FOR UPDATE SKIP LOCKED.
169
- CREATE INDEX job_ready_idx
170
- ON job (queue, priority, seq)
171
- WHERE state = 'waiting';
172
-
173
- -- Promote due delayed jobs (process_at_ms <= now) and find the next wake-up.
174
- CREATE INDEX job_delayed_idx
175
- ON job (queue, process_at_ms)
176
- WHERE state = 'delayed';
177
-
178
- -- Active jobs: stalled scan by lock expiry, and O(1)-ish concurrency counting
179
- -- (COUNT over the partial index for a queue).
180
- CREATE INDEX job_active_idx
181
- ON job (queue, locked_until_ms)
182
- WHERE state = 'active';
183
-
184
- -- Finished jobs: range listing and age/count-based retention + cleaning.
185
- CREATE INDEX job_finished_idx
186
- ON job (queue, state, finished_at_ms)
187
- WHERE state IN ('completed', 'failed');
188
-
189
- -- Parents blocked on children.
190
- CREATE INDEX job_waiting_children_idx
191
- ON job (queue, seq)
192
- WHERE state = 'waiting-children';
193
-
194
- -- Reverse lookup from a child to its parent (e.g. orphan/repair scans).
195
- CREATE INDEX job_parent_idx
196
- ON job (parent_queue, parent_id)
197
- WHERE parent_id IS NOT NULL;
198
-
199
- -- Locate the job(s) produced by a given scheduler.
200
- CREATE INDEX job_scheduler_idx
201
- ON job (queue, scheduler_id)
202
- WHERE scheduler_id IS NOT NULL;
203
-
204
- -- ──────────────────────────────────────────────────────────────────────────
205
- -- Job logs (append-only, per-job ordered lines)
206
- -- ──────────────────────────────────────────────────────────────────────────
207
- CREATE TABLE job_log (
208
- queue text NOT NULL,
209
- job_id text NOT NULL,
210
- idx bigint NOT NULL, -- per-job ordinal (0-based); range reads + trimming
211
- row text NOT NULL,
212
- PRIMARY KEY (queue, job_id, idx),
213
- FOREIGN KEY (queue, job_id)
214
- REFERENCES job (queue, id) ON DELETE CASCADE
215
- );
216
-
217
- -- ──────────────────────────────────────────────────────────────────────────
218
- -- Flow dependencies (parent → child)
219
- -- ──────────────────────────────────────────────────────────────────────────
220
- -- Owned by the parent. Reproduces BullMQ's processed/ignored/failed/unprocessed
221
- -- child sets with a single `status` column, and stores each processed child's
222
- -- return value (or failure reason) in `value`. `pending_deps` on the parent job
223
- -- is the count of rows here still in 'pending'.
224
- CREATE TABLE job_dependency (
225
- parent_queue text NOT NULL,
226
- parent_id text NOT NULL,
227
- child_queue text NOT NULL,
228
- child_id text NOT NULL,
229
- child_key text NOT NULL, -- denormalized "<queue>:<id>" (Redis-compatible "<prefix>:<queue>:<id>")
230
- status dep_status NOT NULL DEFAULT 'pending',
231
- value jsonb,
232
- PRIMARY KEY (parent_queue, parent_id, child_key),
233
- FOREIGN KEY (parent_queue, parent_id)
234
- REFERENCES job (queue, id) ON DELETE CASCADE
235
- );
236
-
237
- -- Paginate a parent's children by category (processed / pending / …).
238
- CREATE INDEX dep_parent_status_idx
239
- ON job_dependency (parent_queue, parent_id, status);
240
-
241
- -- Resolve dependencies from the child side (when a child finishes, or when a
242
- -- child dependency is explicitly removed).
243
- CREATE INDEX dep_child_idx
244
- ON job_dependency (child_queue, child_id);
245
-
246
- -- ──────────────────────────────────────────────────────────────────────────
247
- -- Event stream (QueueEvents)
248
- -- ──────────────────────────────────────────────────────────────────────────
249
- -- The Postgres analogue of the Redis events stream. `id` is globally monotonic;
250
- -- consumers page forward with `id > cursor`. Blocking reads are delivered via
251
- -- LISTEN/NOTIFY (the operation layer NOTIFYs on insert); trimming deletes the
252
- -- lowest ids beyond the configured max length.
253
- CREATE TABLE event (
254
- queue text NOT NULL,
255
- id bigint NOT NULL DEFAULT nextval('event_seq'),
256
- event text NOT NULL,
257
- data jsonb NOT NULL DEFAULT '{}'::jsonb,
258
- created_at_ms bigint NOT NULL,
259
- PRIMARY KEY (queue, id)
260
- );
261
-
262
- CREATE TABLE metrics (
263
- queue text NOT NULL,
264
- kind text NOT NULL, -- 'completed' | 'failed'
265
- count bigint NOT NULL DEFAULT 0, -- cumulative finished jobs
266
- prev_ts bigint, -- ts of the last data point
267
- prev_count bigint NOT NULL DEFAULT 0, -- count at the last data point
268
- data bigint[] NOT NULL DEFAULT '{}',-- per-minute deltas, newest first
269
- PRIMARY KEY (queue, kind)
270
- );
271
-
272
- -- ──────────────────────────────────────────────────────────────────────────
273
- -- Rate limiting (token window per queue)
274
- -- ──────────────────────────────────────────────────────────────────────────
275
- -- One row per queue holding the current limiter window: `points` counts the
276
- -- jobs consumed in the window, which ends at `expire_at_ms`. Mirrors the single
277
- -- Redis `<prefix>:<queue>:limiter` counter key (with its PTTL). The global
278
- -- limiter is the only mode the open-source backend supports (per-group rate
279
- -- limiting was removed in BullMQ 3.0).
280
- CREATE TABLE rate_limit (
281
- queue text NOT NULL,
282
- points bigint NOT NULL DEFAULT 0,
283
- expire_at_ms bigint NOT NULL,
284
- PRIMARY KEY (queue)
285
- );
286
-
287
- -- ──────────────────────────────────────────────────────────────────────────
288
- -- Deduplication keys
289
- -- ──────────────────────────────────────────────────────────────────────────
290
- CREATE TABLE dedup (
291
- queue text NOT NULL,
292
- dedup_id text NOT NULL,
293
- job_id text NOT NULL,
294
- expire_at_ms bigint, -- NULL = no expiry; otherwise lazily reclaimed
295
- PRIMARY KEY (queue, dedup_id)
296
- );
297
-
298
- -- Expired dedup keys are reclaimed lazily by operation functions in
299
- -- 0002_functions.sql (for example deduplicate_job and
300
- -- dedup_finalize). This index keeps those lookups/deletes efficient.
301
- CREATE INDEX dedup_expire_idx
302
- ON dedup (queue, expire_at_ms)
303
- WHERE expire_at_ms IS NOT NULL;
304
-
305
- -- ──────────────────────────────────────────────────────────────────────────
306
- -- Job schedulers (repeatable jobs)
307
- --
308
- -- A scheduler is a job factory: it stores a template (data/opts) plus a repeat
309
- -- spec (cron `pattern` or fixed `every` ms) and, on each upsert, produces the
310
- -- next delayed job `repeat:<schedulerId>:<nextMillis>`. For cron the caller
311
- -- computes nextMillis (JS cron-parser); for `every` the backend computes it,
312
- -- honouring `offset_ms` (the phase offset from `start_date`).
313
- -- ──────────────────────────────────────────────────────────────────────────
314
- CREATE TABLE scheduler (
315
- queue text NOT NULL,
316
- scheduler_id text NOT NULL,
317
- name text,
318
- next_run_ms bigint, -- next iteration's due time
319
- pattern text, -- cron expression (mutually exclusive with every_ms)
320
- every_ms bigint, -- fixed interval in ms
321
- tz text, -- timezone for cron evaluation
322
- start_date_ms bigint,
323
- end_date_ms bigint,
324
- limit_count integer, -- max number of iterations (NULL = unlimited)
325
- iteration_count integer NOT NULL DEFAULT 0,
326
- template_data jsonb, -- payload template reused for every iteration
327
- template_opts jsonb, -- options template reused for every iteration
328
- producer_id text,
329
- offset_ms bigint, -- 'every' phase offset from start_date
330
- PRIMARY KEY (queue, scheduler_id)
331
- );
332
-
333
- -- Find schedulers whose next iteration is due, and order schedulers by next run.
334
- CREATE INDEX scheduler_next_idx
335
- ON scheduler (queue, next_run_ms);
336
-
337
- -- ── keepLastIfActive proto-next storage ──────────────────────────────────
338
- -- When a job is deduplicated while its winner is *active* and keepLastIfActive
339
- -- is set, the new job's payload is stashed here (Redis `dn:<id>` hash) and the
340
- -- dedup key is persisted (no expiry). When the active winner finishes, the
341
- -- stashed payload is turned into a real job (the new winner). At most one
342
- -- proto-next exists per id; a later add while active overwrites it.
343
- CREATE TABLE dedup_next (
344
- queue text NOT NULL,
345
- dedup_id text NOT NULL,
346
- payload jsonb NOT NULL, -- { name, data, opts, jobId }
347
- PRIMARY KEY (queue, dedup_id)
348
- );