pgbus 0.1.9 → 0.2.1
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.
- checksums.yaml +4 -4
- data/lib/pgbus/active_job/executor.rb +1 -1
- data/lib/pgbus/client.rb +125 -36
- data/lib/pgbus/event_bus/publisher.rb +1 -7
- data/lib/pgbus/outbox/poller.rb +58 -20
- data/lib/pgbus/process/consumer.rb +16 -3
- data/lib/pgbus/process/worker.rb +15 -5
- data/lib/pgbus/recurring/scheduler.rb +11 -0
- data/lib/pgbus/version.rb +1 -1
- data/lib/pgbus/web/data_source.rb +11 -9
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 35bd03aa1d30dde2aea609829f027d9a1355282fb049e22d4f3a20119039828a
|
|
4
|
+
data.tar.gz: 341150d6777d9747f19b8446692ffd262403eee0b2e687461176cce820af8177
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: dafeed2ede684fedb7a95b76ac3504c0987624ba49bdd5b3c9ea18c67a51f241ce626069b3d62f0dff617d9582da83490e8b527e600f396a334785d56010c405
|
|
7
|
+
data.tar.gz: 55a9207ec4e8bbe56ce2e8d7322f0bf242daf6b30f89ac87f4cd37dc4dad2565704503076e24b994a3c52aec31e822abfbdafe72ee11ef239227a703d823f875
|
|
@@ -156,7 +156,7 @@ module Pgbus
|
|
|
156
156
|
|
|
157
157
|
def archive_from(queue_name, msg_id, source_queue: nil)
|
|
158
158
|
if source_queue
|
|
159
|
-
client.
|
|
159
|
+
client.archive_message(source_queue, msg_id, prefixed: false)
|
|
160
160
|
else
|
|
161
161
|
client.archive_message(queue_name, msg_id)
|
|
162
162
|
end
|
data/lib/pgbus/client.rb
CHANGED
|
@@ -19,27 +19,34 @@ module Pgbus
|
|
|
19
19
|
require "pgmq"
|
|
20
20
|
end
|
|
21
21
|
@config = config
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
pool_timeout: config.pool_timeout
|
|
32
|
-
|
|
33
|
-
|
|
22
|
+
conn_opts = config.connection_options
|
|
23
|
+
@shared_connection = conn_opts.is_a?(Proc)
|
|
24
|
+
|
|
25
|
+
if @shared_connection
|
|
26
|
+
# When using the Rails lambda path (-> { AR::Base.connection.raw_connection }),
|
|
27
|
+
# the Proc returns the same underlying PG::Connection that ActiveRecord uses.
|
|
28
|
+
# PG::Connection (libpq) is not thread-safe — concurrent access causes
|
|
29
|
+
# segfaults and result corruption. Force pool_size=1 and serialize all
|
|
30
|
+
# operations through a mutex.
|
|
31
|
+
@pgmq = PGMQ::Client.new(conn_opts, pool_size: 1, pool_timeout: config.pool_timeout)
|
|
32
|
+
@pgmq_mutex = Mutex.new
|
|
33
|
+
else
|
|
34
|
+
# With a String URL or Hash params, pgmq-ruby creates its own dedicated
|
|
35
|
+
# PG::Connection per pool slot — no shared state with ActiveRecord.
|
|
36
|
+
# Use the configured pool_size and let pgmq-ruby's connection_pool handle
|
|
37
|
+
# concurrency internally (no mutex needed).
|
|
38
|
+
@pgmq = PGMQ::Client.new(conn_opts, pool_size: config.pool_size, pool_timeout: config.pool_timeout)
|
|
39
|
+
@pgmq_mutex = nil
|
|
40
|
+
end
|
|
41
|
+
|
|
34
42
|
@queues_created = Concurrent::Map.new
|
|
35
43
|
@queue_strategy = QueueFactory.for(config)
|
|
44
|
+
@schema_ensured = false
|
|
36
45
|
end
|
|
37
46
|
|
|
38
47
|
def ensure_queue(name)
|
|
48
|
+
ensure_pgmq_schema
|
|
39
49
|
@queue_strategy.physical_queue_names(name).each { |pq| ensure_single_queue(pq) }
|
|
40
|
-
rescue PGMQ::Errors::ConnectionError => e
|
|
41
|
-
raise Pgbus::SchemaNotReady,
|
|
42
|
-
"PGMQ schema is not available (#{e.message}). Run `rails db:migrate` for the pgbus database."
|
|
43
50
|
end
|
|
44
51
|
|
|
45
52
|
def ensure_all_queues
|
|
@@ -70,7 +77,7 @@ module Pgbus
|
|
|
70
77
|
full_name = config.queue_name(queue_name)
|
|
71
78
|
ensure_queue(queue_name)
|
|
72
79
|
serialized = payloads.map { |p| serialize(p) }
|
|
73
|
-
serialized_headers = headers&.map { |h| serialize(h) }
|
|
80
|
+
serialized_headers = headers&.map { |h| h.nil? ? nil : serialize(h) }
|
|
74
81
|
Instrumentation.instrument("pgbus.client.send_batch", queue: full_name, size: payloads.size) do
|
|
75
82
|
synchronized { @pgmq.produce_batch(full_name, serialized, headers: serialized_headers, delay: delay) }
|
|
76
83
|
end
|
|
@@ -129,31 +136,47 @@ module Pgbus
|
|
|
129
136
|
end
|
|
130
137
|
end
|
|
131
138
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
139
|
+
# Read from multiple queues in a single SQL query (UNION ALL).
|
|
140
|
+
# Each returned message includes a queue_name field identifying its source.
|
|
141
|
+
# queue_names should be logical names (prefix is added automatically).
|
|
142
|
+
def read_multi(queue_names, qty:, vt: nil)
|
|
143
|
+
full_names = queue_names.map { |q| config.queue_name(q) }
|
|
144
|
+
Instrumentation.instrument("pgbus.client.read_multi", queues: full_names, qty: qty) do
|
|
145
|
+
synchronized { @pgmq.read_multi(full_names, vt: vt || config.visibility_timeout, qty: qty) }
|
|
146
|
+
end
|
|
135
147
|
end
|
|
136
148
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
149
|
+
# Delete a message. Pass prefixed: false when queue_name is already
|
|
150
|
+
# the full PGMQ queue name (e.g. from priority sub-queues or dashboard).
|
|
151
|
+
def delete_message(queue_name, msg_id, prefixed: true)
|
|
152
|
+
name = prefixed ? config.queue_name(queue_name) : queue_name
|
|
153
|
+
synchronized { @pgmq.delete(name, msg_id) }
|
|
140
154
|
end
|
|
141
155
|
|
|
142
|
-
|
|
143
|
-
|
|
156
|
+
# Archive a message. Pass prefixed: false when queue_name is already
|
|
157
|
+
# the full PGMQ queue name.
|
|
158
|
+
def archive_message(queue_name, msg_id, prefixed: true)
|
|
159
|
+
name = prefixed ? config.queue_name(queue_name) : queue_name
|
|
160
|
+
synchronized { @pgmq.archive(name, msg_id) }
|
|
144
161
|
end
|
|
145
162
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
163
|
+
# Batch archive — moves multiple messages to the archive table in one call.
|
|
164
|
+
def archive_batch(queue_name, msg_ids, prefixed: true)
|
|
165
|
+
name = prefixed ? config.queue_name(queue_name) : queue_name
|
|
166
|
+
synchronized { @pgmq.archive_batch(name, msg_ids) }
|
|
149
167
|
end
|
|
150
168
|
|
|
151
|
-
|
|
152
|
-
|
|
169
|
+
# Batch delete — permanently removes multiple messages in one call.
|
|
170
|
+
def delete_batch(queue_name, msg_ids, prefixed: true)
|
|
171
|
+
name = prefixed ? config.queue_name(queue_name) : queue_name
|
|
172
|
+
synchronized { @pgmq.delete_batch(name, msg_ids) }
|
|
153
173
|
end
|
|
154
174
|
|
|
155
|
-
|
|
156
|
-
|
|
175
|
+
# Set visibility timeout. Pass prefixed: false when queue_name is already
|
|
176
|
+
# the full PGMQ queue name.
|
|
177
|
+
def set_visibility_timeout(queue_name, msg_id, vt:, prefixed: true)
|
|
178
|
+
name = prefixed ? config.queue_name(queue_name) : queue_name
|
|
179
|
+
synchronized { @pgmq.set_vt(name, msg_id, vt: vt) }
|
|
157
180
|
end
|
|
158
181
|
|
|
159
182
|
def transaction(&block)
|
|
@@ -201,7 +224,9 @@ module Pgbus
|
|
|
201
224
|
|
|
202
225
|
loop do
|
|
203
226
|
deleted = synchronized do
|
|
204
|
-
|
|
227
|
+
with_raw_connection do |conn|
|
|
228
|
+
conn.exec_params(sql, [older_than, batch_size]).cmd_tuples
|
|
229
|
+
end
|
|
205
230
|
end
|
|
206
231
|
total += deleted
|
|
207
232
|
break if deleted < batch_size
|
|
@@ -254,6 +279,66 @@ module Pgbus
|
|
|
254
279
|
queues.to_a
|
|
255
280
|
end
|
|
256
281
|
|
|
282
|
+
def ensure_pgmq_schema
|
|
283
|
+
return if @schema_ensured
|
|
284
|
+
|
|
285
|
+
synchronized do
|
|
286
|
+
return if @schema_ensured
|
|
287
|
+
|
|
288
|
+
with_raw_connection do |raw_conn|
|
|
289
|
+
exists = raw_conn.exec("SELECT 1 FROM pg_tables WHERE schemaname = 'pgmq' AND tablename = 'meta' LIMIT 1")
|
|
290
|
+
install_pgmq_schema(raw_conn) if exists.ntuples.zero?
|
|
291
|
+
end
|
|
292
|
+
@schema_ensured = true
|
|
293
|
+
end
|
|
294
|
+
rescue StandardError => e
|
|
295
|
+
raise Pgbus::SchemaNotReady,
|
|
296
|
+
"PGMQ schema installation failed (#{e.class}: #{e.message}). " \
|
|
297
|
+
"Ensure the pgbus database exists and migrations have been run."
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def install_pgmq_schema(conn)
|
|
301
|
+
mode = config.pgmq_schema_mode
|
|
302
|
+
|
|
303
|
+
case mode
|
|
304
|
+
when :extension
|
|
305
|
+
Pgbus.logger.info { "[Pgbus] PGMQ schema not found — installing via extension" }
|
|
306
|
+
conn.exec("CREATE EXTENSION IF NOT EXISTS pgmq")
|
|
307
|
+
when :embedded
|
|
308
|
+
Pgbus.logger.info { "[Pgbus] PGMQ schema not found — installing embedded SQL" }
|
|
309
|
+
conn.exec(PgmqSchema.install_sql)
|
|
310
|
+
else # :auto
|
|
311
|
+
ext = conn.exec("SELECT 1 FROM pg_available_extensions WHERE name = 'pgmq' LIMIT 1")
|
|
312
|
+
if ext.ntuples.positive?
|
|
313
|
+
Pgbus.logger.info { "[Pgbus] PGMQ schema not found — installing via extension" }
|
|
314
|
+
conn.exec("CREATE EXTENSION IF NOT EXISTS pgmq")
|
|
315
|
+
else
|
|
316
|
+
Pgbus.logger.info { "[Pgbus] PGMQ schema not found — installing embedded SQL" }
|
|
317
|
+
conn.exec(PgmqSchema.install_sql)
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
def with_raw_connection
|
|
323
|
+
opts = config.connection_options
|
|
324
|
+
owned = false
|
|
325
|
+
conn = case opts
|
|
326
|
+
when Proc
|
|
327
|
+
opts.call
|
|
328
|
+
when String
|
|
329
|
+
owned = true
|
|
330
|
+
PG.connect(opts)
|
|
331
|
+
when Hash
|
|
332
|
+
owned = true
|
|
333
|
+
PG.connect(**opts)
|
|
334
|
+
else
|
|
335
|
+
raise ConfigurationError, "Cannot resolve raw PG connection from #{opts.class}"
|
|
336
|
+
end
|
|
337
|
+
yield conn
|
|
338
|
+
ensure
|
|
339
|
+
conn&.close if owned
|
|
340
|
+
end
|
|
341
|
+
|
|
257
342
|
def ensure_single_queue(full_name)
|
|
258
343
|
return if @queues_created[full_name]
|
|
259
344
|
|
|
@@ -266,11 +351,15 @@ module Pgbus
|
|
|
266
351
|
end
|
|
267
352
|
end
|
|
268
353
|
|
|
269
|
-
# Serialize
|
|
270
|
-
#
|
|
271
|
-
#
|
|
354
|
+
# Serialize PGMQ operations through a mutex when sharing a connection
|
|
355
|
+
# with ActiveRecord (Proc path). When pgmq-ruby owns its own connections
|
|
356
|
+
# (String/Hash path), the internal connection_pool handles concurrency.
|
|
272
357
|
def synchronized(&)
|
|
273
|
-
@pgmq_mutex
|
|
358
|
+
if @pgmq_mutex
|
|
359
|
+
@pgmq_mutex.synchronize(&)
|
|
360
|
+
else
|
|
361
|
+
yield
|
|
362
|
+
end
|
|
274
363
|
end
|
|
275
364
|
|
|
276
365
|
def serialize(data)
|
|
@@ -7,13 +7,7 @@ module Pgbus
|
|
|
7
7
|
|
|
8
8
|
def publish(routing_key, payload, headers: nil, delay: 0)
|
|
9
9
|
event_data = build_event_data(payload)
|
|
10
|
-
|
|
11
|
-
if Pgbus.client.pgmq.respond_to?(:produce_topic)
|
|
12
|
-
Pgbus.client.publish_to_topic(routing_key, event_data, headers: headers, delay: delay)
|
|
13
|
-
else
|
|
14
|
-
# Fallback: send directly to queues matching the routing key
|
|
15
|
-
Pgbus.client.send_message(routing_key, event_data, headers: headers, delay: delay)
|
|
16
|
-
end
|
|
10
|
+
Pgbus.client.publish_to_topic(routing_key, event_data, headers: headers, delay: delay)
|
|
17
11
|
end
|
|
18
12
|
|
|
19
13
|
def publish_later(routing_key, payload, delay:, headers: nil)
|
data/lib/pgbus/outbox/poller.rb
CHANGED
|
@@ -54,10 +54,7 @@ module Pgbus
|
|
|
54
54
|
.to_a
|
|
55
55
|
break if entries.empty?
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
succeeded += 1 if publish_entry(entry)
|
|
59
|
-
end
|
|
60
|
-
|
|
57
|
+
succeeded = publish_entries(entries)
|
|
61
58
|
published += succeeded
|
|
62
59
|
break if succeeded.zero? || entries.size < config.outbox_batch_size
|
|
63
60
|
end
|
|
@@ -74,24 +71,65 @@ module Pgbus
|
|
|
74
71
|
|
|
75
72
|
private
|
|
76
73
|
|
|
77
|
-
def
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
74
|
+
def publish_entries(entries)
|
|
75
|
+
# Partition: topic-routed entries must be published individually
|
|
76
|
+
# (different routing keys), direct-queue entries can be batched.
|
|
77
|
+
topic_entries, queue_entries = entries.partition { |e| e.routing_key.present? }
|
|
78
|
+
|
|
79
|
+
succeeded = 0
|
|
80
|
+
topic_entries.each { |e| succeeded += 1 if publish_single(e) }
|
|
81
|
+
succeeded += publish_queue_batch(queue_entries)
|
|
82
|
+
succeeded
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def publish_single(entry)
|
|
86
|
+
Pgbus.client.publish_to_topic(
|
|
87
|
+
entry.routing_key,
|
|
88
|
+
entry.payload,
|
|
89
|
+
headers: entry.headers,
|
|
90
|
+
delay: entry.delay || 0
|
|
91
|
+
)
|
|
92
|
+
entry.update!(published_at: Time.current)
|
|
93
|
+
true
|
|
94
|
+
rescue StandardError => e
|
|
95
|
+
Pgbus.logger.error { "[Pgbus] Failed to publish outbox entry #{entry.id}: #{e.message}" }
|
|
96
|
+
false
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Group direct-queue entries by (queue_name, priority, delay) and
|
|
100
|
+
# use send_batch for each group to reduce round-trips.
|
|
101
|
+
def publish_queue_batch(entries)
|
|
102
|
+
return 0 if entries.empty?
|
|
103
|
+
|
|
104
|
+
succeeded = 0
|
|
105
|
+
entries.group_by { |e| [e.queue_name, e.priority, e.delay || 0] }.each do |(queue, _priority, delay), group|
|
|
106
|
+
payloads = group.map(&:payload)
|
|
107
|
+
headers = group.map(&:headers)
|
|
108
|
+
headers = nil if headers.all?(&:blank?)
|
|
109
|
+
|
|
110
|
+
Pgbus.client.send_batch(queue, payloads, headers: headers, delay: delay)
|
|
111
|
+
now = Time.current
|
|
112
|
+
group.each { |e| e.update!(published_at: now) }
|
|
113
|
+
succeeded += group.size
|
|
114
|
+
rescue StandardError => e
|
|
115
|
+
Pgbus.logger.error { "[Pgbus] Failed to batch-publish #{group.size} outbox entries: #{e.message}" }
|
|
116
|
+
# Fall back to individual publishing for this group
|
|
117
|
+
group.each { |entry| succeeded += 1 if publish_single_queue(entry) }
|
|
93
118
|
end
|
|
119
|
+
succeeded
|
|
120
|
+
end
|
|
94
121
|
|
|
122
|
+
# Fallback for individual publishing when a batch fails.
|
|
123
|
+
# Intentionally omits priority to match send_batch routing behavior
|
|
124
|
+
# (base queue only), ensuring consistent queue placement regardless
|
|
125
|
+
# of whether the batch or fallback path is used.
|
|
126
|
+
def publish_single_queue(entry)
|
|
127
|
+
Pgbus.client.send_message(
|
|
128
|
+
entry.queue_name,
|
|
129
|
+
entry.payload,
|
|
130
|
+
headers: entry.headers,
|
|
131
|
+
delay: entry.delay || 0
|
|
132
|
+
)
|
|
95
133
|
entry.update!(published_at: Time.current)
|
|
96
134
|
true
|
|
97
135
|
rescue StandardError => e
|
|
@@ -56,9 +56,12 @@ module Pgbus
|
|
|
56
56
|
idle = @pool.max_length - @pool.queue_length
|
|
57
57
|
return interruptible_sleep(config.polling_interval) if idle <= 0
|
|
58
58
|
|
|
59
|
-
tagged_messages = @queue_names.
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
tagged_messages = if @queue_names.size == 1
|
|
60
|
+
queue = @queue_names.first
|
|
61
|
+
(Pgbus.client.read_batch(queue, qty: idle) || []).map { |m| [queue, m] }
|
|
62
|
+
else
|
|
63
|
+
fetch_multi_consumer(idle)
|
|
64
|
+
end
|
|
62
65
|
|
|
63
66
|
if tagged_messages.empty?
|
|
64
67
|
interruptible_sleep(config.polling_interval)
|
|
@@ -94,6 +97,16 @@ module Pgbus
|
|
|
94
97
|
# the next read will route to DLQ above.
|
|
95
98
|
end
|
|
96
99
|
|
|
100
|
+
def fetch_multi_consumer(qty)
|
|
101
|
+
messages = Pgbus.client.read_multi(@queue_names, qty: qty) || []
|
|
102
|
+
prefix = "#{config.queue_prefix}_"
|
|
103
|
+
|
|
104
|
+
messages.map do |m|
|
|
105
|
+
logical = m.queue_name&.delete_prefix(prefix) || @queue_names.first
|
|
106
|
+
[logical, m]
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
97
110
|
def pattern_overlaps?(topic_filter, subscription_pattern)
|
|
98
111
|
# Simple check: if either is a subset of the other
|
|
99
112
|
topic_filter == subscription_pattern ||
|
data/lib/pgbus/process/worker.rb
CHANGED
|
@@ -107,7 +107,7 @@ module Pgbus
|
|
|
107
107
|
end
|
|
108
108
|
|
|
109
109
|
# Returns an array of [queue_name, message] pairs so we always know
|
|
110
|
-
# which queue each message came from
|
|
110
|
+
# which queue each message came from.
|
|
111
111
|
def fetch_messages(qty)
|
|
112
112
|
active_queues = queues.reject { |q| @circuit_breaker.paused?(q) }
|
|
113
113
|
active_queues = active_queues.select { |q| @queue_lock.try_lock(q) } if @single_active_consumer
|
|
@@ -120,10 +120,7 @@ module Pgbus
|
|
|
120
120
|
messages = Pgbus.client.read_batch(queue, qty: qty) || []
|
|
121
121
|
messages.map { |m| [queue, m] }
|
|
122
122
|
else
|
|
123
|
-
|
|
124
|
-
active_queues.flat_map do |q|
|
|
125
|
-
(Pgbus.client.read_batch(q, qty: per_queue) || []).map { |m| [q, m] }
|
|
126
|
-
end.first(qty)
|
|
123
|
+
fetch_multi(active_queues, qty)
|
|
127
124
|
end
|
|
128
125
|
rescue StandardError => e
|
|
129
126
|
Pgbus.logger.error { "[Pgbus] Error fetching messages: #{e.message}" }
|
|
@@ -147,6 +144,19 @@ module Pgbus
|
|
|
147
144
|
results
|
|
148
145
|
end
|
|
149
146
|
|
|
147
|
+
# Use pgmq-ruby's read_multi to read from all queues in a single
|
|
148
|
+
# SQL query (UNION ALL). Each returned message carries a queue_name
|
|
149
|
+
# field so we can map it back to the logical queue.
|
|
150
|
+
def fetch_multi(active_queues, qty)
|
|
151
|
+
messages = Pgbus.client.read_multi(active_queues, qty: qty) || []
|
|
152
|
+
prefix = "#{config.queue_prefix}_"
|
|
153
|
+
|
|
154
|
+
messages.map do |m|
|
|
155
|
+
logical = m.queue_name&.delete_prefix(prefix) || active_queues.first
|
|
156
|
+
[logical, m]
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
150
160
|
def priority_enabled?
|
|
151
161
|
config.priority_levels && config.priority_levels > 1
|
|
152
162
|
end
|
|
@@ -17,6 +17,7 @@ module Pgbus
|
|
|
17
17
|
def run
|
|
18
18
|
setup_signals
|
|
19
19
|
start_heartbeat
|
|
20
|
+
sync_recurring_tasks
|
|
20
21
|
|
|
21
22
|
Pgbus.logger.info do
|
|
22
23
|
"[Pgbus] Scheduler started: #{schedule.tasks.size} recurring tasks, " \
|
|
@@ -84,6 +85,16 @@ module Pgbus
|
|
|
84
85
|
|
|
85
86
|
private
|
|
86
87
|
|
|
88
|
+
def sync_recurring_tasks
|
|
89
|
+
tasks = config.recurring_tasks
|
|
90
|
+
return if tasks.nil?
|
|
91
|
+
|
|
92
|
+
RecurringTask.sync_from_config!(tasks)
|
|
93
|
+
Pgbus.logger.info { "[Pgbus] Synced #{tasks.size} recurring task(s) from configuration" }
|
|
94
|
+
rescue StandardError => e
|
|
95
|
+
Pgbus.logger.error { "[Pgbus] Failed to sync recurring tasks: #{e.class}: #{e.message}" }
|
|
96
|
+
end
|
|
97
|
+
|
|
87
98
|
def start_heartbeat
|
|
88
99
|
@heartbeat = Process::Heartbeat.new(
|
|
89
100
|
kind: "scheduler",
|
data/lib/pgbus/version.rb
CHANGED
|
@@ -105,11 +105,11 @@ module Pgbus
|
|
|
105
105
|
end
|
|
106
106
|
|
|
107
107
|
def retry_job(queue_name, msg_id)
|
|
108
|
-
@client.set_visibility_timeout(queue_name, msg_id.to_i, vt: 0)
|
|
108
|
+
@client.set_visibility_timeout(queue_name, msg_id.to_i, vt: 0, prefixed: false)
|
|
109
109
|
end
|
|
110
110
|
|
|
111
111
|
def discard_job(queue_name, msg_id)
|
|
112
|
-
@client.archive_message(queue_name, msg_id.to_i)
|
|
112
|
+
@client.archive_message(queue_name, msg_id.to_i, prefixed: false)
|
|
113
113
|
end
|
|
114
114
|
|
|
115
115
|
# Failed events
|
|
@@ -269,7 +269,7 @@ module Pgbus
|
|
|
269
269
|
|
|
270
270
|
def discard_dlq_message(queue_name, msg_id)
|
|
271
271
|
# queue_name here is the full DLQ name (already prefixed)
|
|
272
|
-
@client.
|
|
272
|
+
@client.delete_message(queue_name, msg_id.to_i, prefixed: false)
|
|
273
273
|
true
|
|
274
274
|
rescue StandardError => e
|
|
275
275
|
Pgbus.logger.debug { "[Pgbus::Web] Error discarding DLQ message #{msg_id}: #{e.message}" }
|
|
@@ -290,14 +290,16 @@ module Pgbus
|
|
|
290
290
|
|
|
291
291
|
def discard_all_dlq
|
|
292
292
|
messages = dlq_messages(page: 1, per_page: 1000)
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
293
|
+
return 0 if messages.empty?
|
|
294
|
+
|
|
295
|
+
# Group by queue for batch delete — one call per DLQ instead of N calls
|
|
296
|
+
messages.group_by { |m| m[:queue_name] }.sum do |queue_name, msgs|
|
|
297
|
+
ids = msgs.map { |m| m[:msg_id].to_i }
|
|
298
|
+
@client.delete_batch(queue_name, ids, prefixed: false).size
|
|
296
299
|
rescue StandardError => e
|
|
297
|
-
Pgbus.logger.debug { "[Pgbus::Web] Error discarding DLQ
|
|
298
|
-
|
|
300
|
+
Pgbus.logger.debug { "[Pgbus::Web] Error batch-discarding DLQ messages from #{queue_name}: #{e.message}" }
|
|
301
|
+
0
|
|
299
302
|
end
|
|
300
|
-
count
|
|
301
303
|
end
|
|
302
304
|
|
|
303
305
|
# Processes
|