pgbus 0.1.4 → 0.1.6

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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +326 -11
  3. data/app/controllers/pgbus/api/insights_controller.rb +16 -0
  4. data/app/controllers/pgbus/insights_controller.rb +10 -0
  5. data/app/controllers/pgbus/locks_controller.rb +9 -0
  6. data/app/controllers/pgbus/outbox_controller.rb +10 -0
  7. data/app/controllers/pgbus/queues_controller.rb +10 -0
  8. data/app/helpers/pgbus/application_helper.rb +34 -0
  9. data/app/models/pgbus/job_lock.rb +82 -0
  10. data/app/models/pgbus/job_stat.rb +94 -0
  11. data/app/models/pgbus/outbox_entry.rb +10 -0
  12. data/app/models/pgbus/queue_state.rb +33 -0
  13. data/app/views/layouts/pgbus/application.html.erb +33 -8
  14. data/app/views/pgbus/dashboard/_stats_cards.html.erb +24 -18
  15. data/app/views/pgbus/insights/show.html.erb +161 -0
  16. data/app/views/pgbus/locks/index.html.erb +53 -0
  17. data/app/views/pgbus/outbox/index.html.erb +55 -0
  18. data/app/views/pgbus/queues/_queues_list.html.erb +15 -1
  19. data/config/routes.rb +7 -0
  20. data/lib/generators/pgbus/add_job_locks_generator.rb +52 -0
  21. data/lib/generators/pgbus/add_job_stats_generator.rb +52 -0
  22. data/lib/generators/pgbus/add_outbox_generator.rb +52 -0
  23. data/lib/generators/pgbus/add_queue_states_generator.rb +51 -0
  24. data/lib/generators/pgbus/add_recurring_generator.rb +1 -1
  25. data/lib/generators/pgbus/install_generator.rb +1 -1
  26. data/lib/generators/pgbus/templates/add_job_locks.rb.erb +21 -0
  27. data/lib/generators/pgbus/templates/add_job_stats.rb.erb +18 -0
  28. data/lib/generators/pgbus/templates/add_outbox.rb.erb +25 -0
  29. data/lib/generators/pgbus/templates/add_queue_states.rb.erb +16 -0
  30. data/lib/generators/pgbus/upgrade_pgmq_generator.rb +1 -1
  31. data/lib/pgbus/active_job/adapter.rb +64 -9
  32. data/lib/pgbus/active_job/executor.rb +67 -5
  33. data/lib/pgbus/circuit_breaker.rb +112 -0
  34. data/lib/pgbus/client.rb +127 -50
  35. data/lib/pgbus/configuration.rb +55 -1
  36. data/lib/pgbus/dedup_cache.rb +76 -0
  37. data/lib/pgbus/engine.rb +1 -0
  38. data/lib/pgbus/event_bus/handler.rb +13 -2
  39. data/lib/pgbus/outbox/poller.rb +117 -0
  40. data/lib/pgbus/outbox.rb +30 -0
  41. data/lib/pgbus/process/consumer_priority.rb +64 -0
  42. data/lib/pgbus/process/dispatcher.rb +75 -0
  43. data/lib/pgbus/process/heartbeat.rb +3 -1
  44. data/lib/pgbus/process/lifecycle.rb +111 -0
  45. data/lib/pgbus/process/queue_lock.rb +87 -0
  46. data/lib/pgbus/process/supervisor.rb +46 -6
  47. data/lib/pgbus/process/wake_signal.rb +53 -0
  48. data/lib/pgbus/process/worker.rb +117 -21
  49. data/lib/pgbus/queue_factory.rb +62 -0
  50. data/lib/pgbus/rate_counter.rb +81 -0
  51. data/lib/pgbus/recurring/schedule.rb +1 -1
  52. data/lib/pgbus/uniqueness.rb +169 -0
  53. data/lib/pgbus/version.rb +1 -1
  54. data/lib/pgbus/web/data_source.rb +136 -2
  55. data/lib/pgbus.rb +9 -0
  56. metadata +31 -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
- queue_names.map { |name| queue_metrics_via_sql(name) }.compact
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,75 @@ 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
+
500
+ # Job locks
501
+ def job_locks
502
+ JobLock.order(locked_at: :desc).limit(100).map do |lock|
503
+ {
504
+ lock_key: lock.lock_key,
505
+ job_class: lock.job_class,
506
+ job_id: lock.job_id,
507
+ state: lock.state,
508
+ owner_pid: lock.owner_pid,
509
+ owner_hostname: lock.owner_hostname,
510
+ locked_at: lock.locked_at,
511
+ expires_at: lock.expires_at,
512
+ age_seconds: lock.locked_at ? (Time.current - lock.locked_at).to_i : nil
513
+ }
514
+ end
515
+ rescue StandardError => e
516
+ Pgbus.logger.debug { "[Pgbus::Web] Error fetching job locks: #{e.message}" }
517
+ []
518
+ end
519
+
520
+ # Job stats
521
+ def job_stats_summary(minutes: 60)
522
+ JobStat.summary(minutes: minutes)
523
+ rescue StandardError => e
524
+ Pgbus.logger.debug { "[Pgbus::Web] Error fetching job stats summary: #{e.message}" }
525
+ { total: 0, success: 0, failed: 0, dead_lettered: 0, avg_duration_ms: 0, max_duration_ms: 0 }
526
+ end
527
+
528
+ def job_throughput(minutes: 60)
529
+ JobStat.throughput(minutes: minutes).map { |time, count| { time: time, count: count } }
530
+ rescue StandardError => e
531
+ Pgbus.logger.debug { "[Pgbus::Web] Error fetching throughput: #{e.message}" }
532
+ []
533
+ end
534
+
535
+ def job_status_counts(minutes: 60)
536
+ JobStat.status_counts(minutes: minutes)
537
+ rescue StandardError => e
538
+ Pgbus.logger.debug { "[Pgbus::Web] Error fetching status counts: #{e.message}" }
539
+ {}
540
+ end
541
+
542
+ def slowest_job_classes(limit: 10, minutes: 60)
543
+ JobStat.slowest_classes(limit: limit, minutes: minutes)
544
+ rescue StandardError => e
545
+ Pgbus.logger.debug { "[Pgbus::Web] Error fetching slowest classes: #{e.message}" }
546
+ []
547
+ end
548
+
454
549
  # Subscriber registry
455
550
  def registered_subscribers
456
551
  EventBus::Registry.instance.subscribers.map do |s|
@@ -571,6 +666,45 @@ module Pgbus
571
666
  sanitized
572
667
  end
573
668
 
669
+ def compute_throughput(queues)
670
+ current_totals = queues.sum { |q| q[:total_messages] }
671
+ now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
672
+
673
+ if @last_throughput_snapshot && @last_throughput_at
674
+ elapsed = now - @last_throughput_at
675
+ delta = current_totals - @last_throughput_snapshot
676
+ rate = elapsed.positive? ? (delta / elapsed).round(1) : 0.0
677
+ end
678
+
679
+ @last_throughput_snapshot = current_totals
680
+ @last_throughput_at = now
681
+
682
+ rate || 0.0
683
+ rescue StandardError
684
+ 0.0
685
+ end
686
+
687
+ def logical_queue_name(name)
688
+ name
689
+ .delete_prefix("#{Pgbus.configuration.queue_prefix}_")
690
+ .sub(/_p\d+\z/, "")
691
+ end
692
+
693
+ def paused_queue_names
694
+ QueueState.paused.pluck(:queue_name)
695
+ rescue StandardError
696
+ []
697
+ end
698
+
699
+ def oldest_unpublished_age
700
+ oldest = OutboxEntry.unpublished.order(:id).pick(:created_at)
701
+ return nil unless oldest
702
+
703
+ (Time.now - oldest).to_i
704
+ rescue StandardError
705
+ nil
706
+ end
707
+
574
708
  def parse_arguments(args)
575
709
  case args
576
710
  when Array then args
data/lib/pgbus.rb CHANGED
@@ -9,6 +9,7 @@ module Pgbus
9
9
  class QueueNotFoundError < Error; end
10
10
  class DeadLetterError < Error; end
11
11
  class ConcurrencyLimitExceeded < Error; end
12
+ class JobNotUnique < Error; end
12
13
  class SchemaNotReady < Error; end
13
14
 
14
15
  class << self
@@ -63,6 +64,14 @@ module Pgbus
63
64
  @configuration = nil
64
65
  end
65
66
 
67
+ # Discard the inherited PGMQ client after fork.
68
+ # Do NOT call close — the parent's @pgmq_mutex is in undefined
69
+ # state post-fork and attempting to acquire it can deadlock.
70
+ # The next call to Pgbus.client will lazily create a fresh one.
71
+ def reset_client!
72
+ @client = nil
73
+ end
74
+
66
75
  def logger
67
76
  configuration.logger
68
77
  end
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
4
+ version: 0.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mikael Henriksson
@@ -107,12 +107,16 @@ files:
107
107
  - LICENSE.txt
108
108
  - README.md
109
109
  - Rakefile
110
+ - app/controllers/pgbus/api/insights_controller.rb
110
111
  - app/controllers/pgbus/api/stats_controller.rb
111
112
  - app/controllers/pgbus/application_controller.rb
112
113
  - app/controllers/pgbus/dashboard_controller.rb
113
114
  - app/controllers/pgbus/dead_letter_controller.rb
114
115
  - app/controllers/pgbus/events_controller.rb
116
+ - app/controllers/pgbus/insights_controller.rb
115
117
  - app/controllers/pgbus/jobs_controller.rb
118
+ - app/controllers/pgbus/locks_controller.rb
119
+ - app/controllers/pgbus/outbox_controller.rb
116
120
  - app/controllers/pgbus/processes_controller.rb
117
121
  - app/controllers/pgbus/queues_controller.rb
118
122
  - app/controllers/pgbus/recurring_tasks_controller.rb
@@ -120,8 +124,12 @@ files:
120
124
  - app/models/pgbus/application_record.rb
121
125
  - app/models/pgbus/batch_entry.rb
122
126
  - app/models/pgbus/blocked_execution.rb
127
+ - app/models/pgbus/job_lock.rb
128
+ - app/models/pgbus/job_stat.rb
129
+ - app/models/pgbus/outbox_entry.rb
123
130
  - app/models/pgbus/process_entry.rb
124
131
  - app/models/pgbus/processed_event.rb
132
+ - app/models/pgbus/queue_state.rb
125
133
  - app/models/pgbus/recurring_execution.rb
126
134
  - app/models/pgbus/recurring_task.rb
127
135
  - app/models/pgbus/semaphore.rb
@@ -136,10 +144,13 @@ files:
136
144
  - app/views/pgbus/dead_letter/show.html.erb
137
145
  - app/views/pgbus/events/index.html.erb
138
146
  - app/views/pgbus/events/show.html.erb
147
+ - app/views/pgbus/insights/show.html.erb
139
148
  - app/views/pgbus/jobs/_enqueued_table.html.erb
140
149
  - app/views/pgbus/jobs/_failed_table.html.erb
141
150
  - app/views/pgbus/jobs/index.html.erb
142
151
  - app/views/pgbus/jobs/show.html.erb
152
+ - app/views/pgbus/locks/index.html.erb
153
+ - app/views/pgbus/outbox/index.html.erb
143
154
  - app/views/pgbus/processes/_processes_table.html.erb
144
155
  - app/views/pgbus/processes/index.html.erb
145
156
  - app/views/pgbus/queues/_queues_list.html.erb
@@ -151,8 +162,16 @@ files:
151
162
  - config/routes.rb
152
163
  - exe/pgbus
153
164
  - lib/active_job/queue_adapters/pgbus_adapter.rb
165
+ - lib/generators/pgbus/add_job_locks_generator.rb
166
+ - lib/generators/pgbus/add_job_stats_generator.rb
167
+ - lib/generators/pgbus/add_outbox_generator.rb
168
+ - lib/generators/pgbus/add_queue_states_generator.rb
154
169
  - lib/generators/pgbus/add_recurring_generator.rb
155
170
  - lib/generators/pgbus/install_generator.rb
171
+ - lib/generators/pgbus/templates/add_job_locks.rb.erb
172
+ - lib/generators/pgbus/templates/add_job_stats.rb.erb
173
+ - lib/generators/pgbus/templates/add_outbox.rb.erb
174
+ - lib/generators/pgbus/templates/add_queue_states.rb.erb
156
175
  - lib/generators/pgbus/templates/add_recurring_tables.rb.erb
157
176
  - lib/generators/pgbus/templates/migration.rb.erb
158
177
  - lib/generators/pgbus/templates/pgbus.yml.erb
@@ -164,6 +183,7 @@ files:
164
183
  - lib/pgbus/active_job/adapter.rb
165
184
  - lib/pgbus/active_job/executor.rb
166
185
  - lib/pgbus/batch.rb
186
+ - lib/pgbus/circuit_breaker.rb
167
187
  - lib/pgbus/cli.rb
168
188
  - lib/pgbus/client.rb
169
189
  - lib/pgbus/concurrency.rb
@@ -171,6 +191,7 @@ files:
171
191
  - lib/pgbus/concurrency/semaphore.rb
172
192
  - lib/pgbus/config_loader.rb
173
193
  - lib/pgbus/configuration.rb
194
+ - lib/pgbus/dedup_cache.rb
174
195
  - lib/pgbus/engine.rb
175
196
  - lib/pgbus/event.rb
176
197
  - lib/pgbus/event_bus/handler.rb
@@ -178,14 +199,22 @@ files:
178
199
  - lib/pgbus/event_bus/registry.rb
179
200
  - lib/pgbus/event_bus/subscriber.rb
180
201
  - lib/pgbus/instrumentation.rb
202
+ - lib/pgbus/outbox.rb
203
+ - lib/pgbus/outbox/poller.rb
181
204
  - lib/pgbus/pgmq_schema.rb
182
205
  - lib/pgbus/pgmq_schema/pgmq_v1.11.0.sql
183
206
  - lib/pgbus/process/consumer.rb
207
+ - lib/pgbus/process/consumer_priority.rb
184
208
  - lib/pgbus/process/dispatcher.rb
185
209
  - lib/pgbus/process/heartbeat.rb
210
+ - lib/pgbus/process/lifecycle.rb
211
+ - lib/pgbus/process/queue_lock.rb
186
212
  - lib/pgbus/process/signal_handler.rb
187
213
  - lib/pgbus/process/supervisor.rb
214
+ - lib/pgbus/process/wake_signal.rb
188
215
  - lib/pgbus/process/worker.rb
216
+ - lib/pgbus/queue_factory.rb
217
+ - lib/pgbus/rate_counter.rb
189
218
  - lib/pgbus/recurring/already_recorded.rb
190
219
  - lib/pgbus/recurring/command_job.rb
191
220
  - lib/pgbus/recurring/config_loader.rb
@@ -193,6 +222,7 @@ files:
193
222
  - lib/pgbus/recurring/scheduler.rb
194
223
  - lib/pgbus/recurring/task.rb
195
224
  - lib/pgbus/serializer.rb
225
+ - lib/pgbus/uniqueness.rb
196
226
  - lib/pgbus/version.rb
197
227
  - lib/pgbus/web/authentication.rb
198
228
  - lib/pgbus/web/data_source.rb