pgbus 0.1.3 → 0.1.5
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/app/controllers/pgbus/outbox_controller.rb +10 -0
- data/app/controllers/pgbus/queues_controller.rb +10 -0
- data/app/helpers/pgbus/application_helper.rb +6 -0
- data/app/models/pgbus/outbox_entry.rb +10 -0
- data/app/models/pgbus/queue_state.rb +33 -0
- data/app/views/layouts/pgbus/application.html.erb +1 -0
- data/app/views/pgbus/dashboard/_stats_cards.html.erb +7 -1
- data/app/views/pgbus/outbox/index.html.erb +55 -0
- data/app/views/pgbus/queues/_queues_list.html.erb +15 -1
- data/config/routes.rb +4 -0
- data/lib/generators/pgbus/add_outbox_generator.rb +52 -0
- data/lib/generators/pgbus/add_queue_states_generator.rb +51 -0
- data/lib/generators/pgbus/templates/add_outbox.rb.erb +25 -0
- data/lib/generators/pgbus/templates/add_queue_states.rb.erb +16 -0
- data/lib/pgbus/active_job/adapter.rb +6 -5
- data/lib/pgbus/active_job/executor.rb +22 -5
- data/lib/pgbus/circuit_breaker.rb +112 -0
- data/lib/pgbus/client.rb +140 -49
- data/lib/pgbus/configuration.rb +54 -2
- data/lib/pgbus/dedup_cache.rb +76 -0
- data/lib/pgbus/engine.rb +6 -0
- data/lib/pgbus/event_bus/handler.rb +13 -2
- data/lib/pgbus/outbox/poller.rb +117 -0
- data/lib/pgbus/outbox.rb +30 -0
- data/lib/pgbus/process/dispatcher.rb +46 -0
- data/lib/pgbus/process/heartbeat.rb +3 -1
- data/lib/pgbus/process/lifecycle.rb +111 -0
- data/lib/pgbus/process/supervisor.rb +40 -5
- data/lib/pgbus/process/worker.rb +86 -19
- data/lib/pgbus/rate_counter.rb +81 -0
- data/lib/pgbus/recurring/schedule.rb +1 -1
- data/lib/pgbus/version.rb +1 -1
- data/lib/pgbus/web/data_source.rb +87 -2
- data/lib/pgbus.rb +35 -6
- data/lib/tasks/pgbus_pgmq.rake +5 -3
- metadata +15 -1
|
@@ -7,6 +7,8 @@ module Pgbus
|
|
|
7
7
|
class DataSource
|
|
8
8
|
def initialize(client: Pgbus.client)
|
|
9
9
|
@client = client
|
|
10
|
+
@last_throughput_snapshot = nil
|
|
11
|
+
@last_throughput_at = nil
|
|
10
12
|
end
|
|
11
13
|
|
|
12
14
|
# Dashboard summary
|
|
@@ -17,6 +19,8 @@ module Pgbus
|
|
|
17
19
|
dlq_suffix = Pgbus.configuration.dead_letter_queue_suffix
|
|
18
20
|
dlq_depth = queues.select { |q| q[:name].end_with?(dlq_suffix) }.sum { |q| q[:queue_length] }
|
|
19
21
|
|
|
22
|
+
throughput = compute_throughput(queues)
|
|
23
|
+
|
|
20
24
|
{
|
|
21
25
|
total_queues: queues.size,
|
|
22
26
|
total_depth: total_depth,
|
|
@@ -24,7 +28,8 @@ module Pgbus
|
|
|
24
28
|
active_processes: processes.count,
|
|
25
29
|
failed_count: failed_events_count,
|
|
26
30
|
dlq_depth: dlq_depth,
|
|
27
|
-
recurring_count: recurring_tasks_count
|
|
31
|
+
recurring_count: recurring_tasks_count,
|
|
32
|
+
throughput_rate: throughput
|
|
28
33
|
}
|
|
29
34
|
end
|
|
30
35
|
|
|
@@ -33,7 +38,10 @@ module Pgbus
|
|
|
33
38
|
# different connection lifecycle than the worker processes).
|
|
34
39
|
def queues_with_metrics
|
|
35
40
|
queue_names = connection.select_values("SELECT queue_name FROM pgmq.meta ORDER BY queue_name")
|
|
36
|
-
|
|
41
|
+
paused_queues = paused_queue_names
|
|
42
|
+
queue_names.map { |name| queue_metrics_via_sql(name) }.compact.map do |q|
|
|
43
|
+
q.merge(paused: paused_queues.include?(logical_queue_name(q[:name])))
|
|
44
|
+
end
|
|
37
45
|
rescue StandardError => e
|
|
38
46
|
Pgbus.logger.error { "[Pgbus::Web] Error fetching queue metrics: #{e.class}: #{e.message}" }
|
|
39
47
|
[]
|
|
@@ -52,6 +60,24 @@ module Pgbus
|
|
|
52
60
|
@client.purge_queue(name)
|
|
53
61
|
end
|
|
54
62
|
|
|
63
|
+
def pause_queue(name, reason: nil)
|
|
64
|
+
QueueState.pause!(logical_queue_name(name), reason: reason)
|
|
65
|
+
rescue StandardError => e
|
|
66
|
+
Pgbus.logger.error { "[Pgbus::Web] Error pausing queue #{name}: #{e.message}" }
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def resume_queue(name)
|
|
70
|
+
QueueState.resume!(logical_queue_name(name))
|
|
71
|
+
rescue StandardError => e
|
|
72
|
+
Pgbus.logger.error { "[Pgbus::Web] Error resuming queue #{name}: #{e.message}" }
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def queue_paused?(name)
|
|
76
|
+
QueueState.paused?(logical_queue_name(name))
|
|
77
|
+
rescue StandardError
|
|
78
|
+
false
|
|
79
|
+
end
|
|
80
|
+
|
|
55
81
|
# Jobs (messages in queue tables)
|
|
56
82
|
def jobs(queue_name: nil, page: 1, per_page: 25)
|
|
57
83
|
offset = (page - 1) * per_page
|
|
@@ -451,6 +477,26 @@ module Pgbus
|
|
|
451
477
|
0
|
|
452
478
|
end
|
|
453
479
|
|
|
480
|
+
# Outbox
|
|
481
|
+
def outbox_stats
|
|
482
|
+
{
|
|
483
|
+
unpublished: OutboxEntry.unpublished.count,
|
|
484
|
+
total: OutboxEntry.count,
|
|
485
|
+
oldest_unpublished_age: oldest_unpublished_age
|
|
486
|
+
}
|
|
487
|
+
rescue StandardError => e
|
|
488
|
+
Pgbus.logger.debug { "[Pgbus::Web] Error fetching outbox stats: #{e.message}" }
|
|
489
|
+
{ unpublished: 0, total: 0, oldest_unpublished_age: nil }
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
def outbox_entries(page: 1, per_page: 25)
|
|
493
|
+
offset = (page - 1) * per_page
|
|
494
|
+
OutboxEntry.order(id: :desc).limit(per_page).offset(offset).to_a
|
|
495
|
+
rescue StandardError => e
|
|
496
|
+
Pgbus.logger.debug { "[Pgbus::Web] Error fetching outbox entries: #{e.message}" }
|
|
497
|
+
[]
|
|
498
|
+
end
|
|
499
|
+
|
|
454
500
|
# Subscriber registry
|
|
455
501
|
def registered_subscribers
|
|
456
502
|
EventBus::Registry.instance.subscribers.map do |s|
|
|
@@ -571,6 +617,45 @@ module Pgbus
|
|
|
571
617
|
sanitized
|
|
572
618
|
end
|
|
573
619
|
|
|
620
|
+
def compute_throughput(queues)
|
|
621
|
+
current_totals = queues.sum { |q| q[:total_messages] }
|
|
622
|
+
now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
623
|
+
|
|
624
|
+
if @last_throughput_snapshot && @last_throughput_at
|
|
625
|
+
elapsed = now - @last_throughput_at
|
|
626
|
+
delta = current_totals - @last_throughput_snapshot
|
|
627
|
+
rate = elapsed.positive? ? (delta / elapsed).round(1) : 0.0
|
|
628
|
+
end
|
|
629
|
+
|
|
630
|
+
@last_throughput_snapshot = current_totals
|
|
631
|
+
@last_throughput_at = now
|
|
632
|
+
|
|
633
|
+
rate || 0.0
|
|
634
|
+
rescue StandardError
|
|
635
|
+
0.0
|
|
636
|
+
end
|
|
637
|
+
|
|
638
|
+
def logical_queue_name(name)
|
|
639
|
+
name
|
|
640
|
+
.delete_prefix("#{Pgbus.configuration.queue_prefix}_")
|
|
641
|
+
.sub(/_p\d+\z/, "")
|
|
642
|
+
end
|
|
643
|
+
|
|
644
|
+
def paused_queue_names
|
|
645
|
+
QueueState.paused.pluck(:queue_name)
|
|
646
|
+
rescue StandardError
|
|
647
|
+
[]
|
|
648
|
+
end
|
|
649
|
+
|
|
650
|
+
def oldest_unpublished_age
|
|
651
|
+
oldest = OutboxEntry.unpublished.order(:id).pick(:created_at)
|
|
652
|
+
return nil unless oldest
|
|
653
|
+
|
|
654
|
+
(Time.now - oldest).to_i
|
|
655
|
+
rescue StandardError
|
|
656
|
+
nil
|
|
657
|
+
end
|
|
658
|
+
|
|
574
659
|
def parse_arguments(args)
|
|
575
660
|
case args
|
|
576
661
|
when Array then args
|
data/lib/pgbus.rb
CHANGED
|
@@ -18,16 +18,33 @@ module Pgbus
|
|
|
18
18
|
loader.inflector.inflect("pgbus" => "Pgbus", "cli" => "CLI", "dsl" => "DSL")
|
|
19
19
|
loader.ignore("#{__dir__}/generators")
|
|
20
20
|
loader.ignore("#{__dir__}/active_job")
|
|
21
|
-
# Register app/models for non-Rails usage (specs, standalone).
|
|
22
|
-
# When Rails is running, the Engine handles autoloading app/models.
|
|
23
|
-
unless defined?(Rails::Engine)
|
|
24
|
-
models_dir = File.expand_path("../app/models", __dir__)
|
|
25
|
-
loader.push_dir(models_dir) if File.directory?(models_dir)
|
|
26
|
-
end
|
|
27
21
|
loader
|
|
28
22
|
end
|
|
29
23
|
end
|
|
30
24
|
|
|
25
|
+
# Separate loader for app/models used only in non-Rails contexts (specs,
|
|
26
|
+
# standalone scripts). When the Engine boots, Rails' autoloader takes over
|
|
27
|
+
# app/models and this loader is torn down to avoid conflicts.
|
|
28
|
+
def models_loader
|
|
29
|
+
models_dir = File.expand_path("../app/models", __dir__)
|
|
30
|
+
return nil unless File.directory?(models_dir)
|
|
31
|
+
|
|
32
|
+
@models_loader ||= begin
|
|
33
|
+
loader = Zeitwerk::Loader.new
|
|
34
|
+
loader.tag = "pgbus-models"
|
|
35
|
+
loader.push_dir(models_dir)
|
|
36
|
+
loader.setup
|
|
37
|
+
loader
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def teardown_models_loader!
|
|
42
|
+
return unless @models_loader
|
|
43
|
+
|
|
44
|
+
@models_loader.unregister
|
|
45
|
+
@models_loader = nil
|
|
46
|
+
end
|
|
47
|
+
|
|
31
48
|
def configuration
|
|
32
49
|
@configuration ||= Configuration.new
|
|
33
50
|
end
|
|
@@ -46,12 +63,24 @@ module Pgbus
|
|
|
46
63
|
@configuration = nil
|
|
47
64
|
end
|
|
48
65
|
|
|
66
|
+
# Discard the inherited PGMQ client after fork.
|
|
67
|
+
# Do NOT call close — the parent's @pgmq_mutex is in undefined
|
|
68
|
+
# state post-fork and attempting to acquire it can deadlock.
|
|
69
|
+
# The next call to Pgbus.client will lazily create a fresh one.
|
|
70
|
+
def reset_client!
|
|
71
|
+
@client = nil
|
|
72
|
+
end
|
|
73
|
+
|
|
49
74
|
def logger
|
|
50
75
|
configuration.logger
|
|
51
76
|
end
|
|
52
77
|
end
|
|
53
78
|
|
|
54
79
|
loader.setup
|
|
80
|
+
|
|
81
|
+
# In non-Rails contexts, set up model autoloading via a separate loader.
|
|
82
|
+
# This is torn down by the Engine initializer when Rails boots.
|
|
83
|
+
models_loader unless defined?(Rails::Engine)
|
|
55
84
|
end
|
|
56
85
|
|
|
57
86
|
require "active_job/queue_adapters/pgbus_adapter" if defined?(ActiveJob)
|
data/lib/tasks/pgbus_pgmq.rake
CHANGED
|
@@ -12,8 +12,10 @@ namespace :pgbus do
|
|
|
12
12
|
latest = Pgbus::PgmqSchema.latest_version
|
|
13
13
|
puts "Vendored version: #{latest}"
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
conn = Pgbus.configuration.connects_to ? Pgbus::ApplicationRecord.connection : ActiveRecord::Base.connection
|
|
16
|
+
|
|
17
|
+
if conn.table_exists?("pgbus_pgmq_schema_versions")
|
|
18
|
+
row = conn.select_one(
|
|
17
19
|
"SELECT version, install_method, installed_at FROM pgbus_pgmq_schema_versions ORDER BY installed_at DESC LIMIT 1"
|
|
18
20
|
)
|
|
19
21
|
if row
|
|
@@ -32,7 +34,7 @@ namespace :pgbus do
|
|
|
32
34
|
end
|
|
33
35
|
else
|
|
34
36
|
# Check if pgmq schema exists at all
|
|
35
|
-
schema_exists =
|
|
37
|
+
schema_exists = conn.select_value(
|
|
36
38
|
"SELECT 1 FROM information_schema.schemata WHERE schema_name = 'pgmq'"
|
|
37
39
|
)
|
|
38
40
|
if schema_exists
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pgbus
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mikael Henriksson
|
|
@@ -113,6 +113,7 @@ files:
|
|
|
113
113
|
- app/controllers/pgbus/dead_letter_controller.rb
|
|
114
114
|
- app/controllers/pgbus/events_controller.rb
|
|
115
115
|
- app/controllers/pgbus/jobs_controller.rb
|
|
116
|
+
- app/controllers/pgbus/outbox_controller.rb
|
|
116
117
|
- app/controllers/pgbus/processes_controller.rb
|
|
117
118
|
- app/controllers/pgbus/queues_controller.rb
|
|
118
119
|
- app/controllers/pgbus/recurring_tasks_controller.rb
|
|
@@ -120,8 +121,10 @@ files:
|
|
|
120
121
|
- app/models/pgbus/application_record.rb
|
|
121
122
|
- app/models/pgbus/batch_entry.rb
|
|
122
123
|
- app/models/pgbus/blocked_execution.rb
|
|
124
|
+
- app/models/pgbus/outbox_entry.rb
|
|
123
125
|
- app/models/pgbus/process_entry.rb
|
|
124
126
|
- app/models/pgbus/processed_event.rb
|
|
127
|
+
- app/models/pgbus/queue_state.rb
|
|
125
128
|
- app/models/pgbus/recurring_execution.rb
|
|
126
129
|
- app/models/pgbus/recurring_task.rb
|
|
127
130
|
- app/models/pgbus/semaphore.rb
|
|
@@ -140,6 +143,7 @@ files:
|
|
|
140
143
|
- app/views/pgbus/jobs/_failed_table.html.erb
|
|
141
144
|
- app/views/pgbus/jobs/index.html.erb
|
|
142
145
|
- app/views/pgbus/jobs/show.html.erb
|
|
146
|
+
- app/views/pgbus/outbox/index.html.erb
|
|
143
147
|
- app/views/pgbus/processes/_processes_table.html.erb
|
|
144
148
|
- app/views/pgbus/processes/index.html.erb
|
|
145
149
|
- app/views/pgbus/queues/_queues_list.html.erb
|
|
@@ -151,8 +155,12 @@ files:
|
|
|
151
155
|
- config/routes.rb
|
|
152
156
|
- exe/pgbus
|
|
153
157
|
- lib/active_job/queue_adapters/pgbus_adapter.rb
|
|
158
|
+
- lib/generators/pgbus/add_outbox_generator.rb
|
|
159
|
+
- lib/generators/pgbus/add_queue_states_generator.rb
|
|
154
160
|
- lib/generators/pgbus/add_recurring_generator.rb
|
|
155
161
|
- lib/generators/pgbus/install_generator.rb
|
|
162
|
+
- lib/generators/pgbus/templates/add_outbox.rb.erb
|
|
163
|
+
- lib/generators/pgbus/templates/add_queue_states.rb.erb
|
|
156
164
|
- lib/generators/pgbus/templates/add_recurring_tables.rb.erb
|
|
157
165
|
- lib/generators/pgbus/templates/migration.rb.erb
|
|
158
166
|
- lib/generators/pgbus/templates/pgbus.yml.erb
|
|
@@ -164,6 +172,7 @@ files:
|
|
|
164
172
|
- lib/pgbus/active_job/adapter.rb
|
|
165
173
|
- lib/pgbus/active_job/executor.rb
|
|
166
174
|
- lib/pgbus/batch.rb
|
|
175
|
+
- lib/pgbus/circuit_breaker.rb
|
|
167
176
|
- lib/pgbus/cli.rb
|
|
168
177
|
- lib/pgbus/client.rb
|
|
169
178
|
- lib/pgbus/concurrency.rb
|
|
@@ -171,6 +180,7 @@ files:
|
|
|
171
180
|
- lib/pgbus/concurrency/semaphore.rb
|
|
172
181
|
- lib/pgbus/config_loader.rb
|
|
173
182
|
- lib/pgbus/configuration.rb
|
|
183
|
+
- lib/pgbus/dedup_cache.rb
|
|
174
184
|
- lib/pgbus/engine.rb
|
|
175
185
|
- lib/pgbus/event.rb
|
|
176
186
|
- lib/pgbus/event_bus/handler.rb
|
|
@@ -178,14 +188,18 @@ files:
|
|
|
178
188
|
- lib/pgbus/event_bus/registry.rb
|
|
179
189
|
- lib/pgbus/event_bus/subscriber.rb
|
|
180
190
|
- lib/pgbus/instrumentation.rb
|
|
191
|
+
- lib/pgbus/outbox.rb
|
|
192
|
+
- lib/pgbus/outbox/poller.rb
|
|
181
193
|
- lib/pgbus/pgmq_schema.rb
|
|
182
194
|
- lib/pgbus/pgmq_schema/pgmq_v1.11.0.sql
|
|
183
195
|
- lib/pgbus/process/consumer.rb
|
|
184
196
|
- lib/pgbus/process/dispatcher.rb
|
|
185
197
|
- lib/pgbus/process/heartbeat.rb
|
|
198
|
+
- lib/pgbus/process/lifecycle.rb
|
|
186
199
|
- lib/pgbus/process/signal_handler.rb
|
|
187
200
|
- lib/pgbus/process/supervisor.rb
|
|
188
201
|
- lib/pgbus/process/worker.rb
|
|
202
|
+
- lib/pgbus/rate_counter.rb
|
|
189
203
|
- lib/pgbus/recurring/already_recorded.rb
|
|
190
204
|
- lib/pgbus/recurring/command_job.rb
|
|
191
205
|
- lib/pgbus/recurring/config_loader.rb
|