pgbus 0.9.7 → 0.9.9

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 (76) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +367 -0
  3. data/README.md +454 -25
  4. data/Rakefile +10 -1
  5. data/app/helpers/pgbus/application_helper.rb +37 -0
  6. data/app/views/pgbus/processes/_processes_table.html.erb +4 -1
  7. data/config/locales/da.yml +4 -0
  8. data/config/locales/de.yml +4 -0
  9. data/config/locales/en.yml +4 -0
  10. data/config/locales/es.yml +4 -0
  11. data/config/locales/fi.yml +4 -0
  12. data/config/locales/fr.yml +4 -0
  13. data/config/locales/it.yml +4 -0
  14. data/config/locales/ja.yml +4 -0
  15. data/config/locales/nb.yml +4 -0
  16. data/config/locales/nl.yml +4 -0
  17. data/config/locales/pt.yml +4 -0
  18. data/config/locales/sv.yml +4 -0
  19. data/lib/generators/pgbus/{add_job_locks_generator.rb → add_uniqueness_keys_generator.rb} +5 -5
  20. data/lib/pgbus/active_job/adapter.rb +1 -1
  21. data/lib/pgbus/active_job/executor.rb +25 -4
  22. data/lib/pgbus/cli/dlq.rb +164 -0
  23. data/lib/pgbus/cli.rb +18 -1
  24. data/lib/pgbus/client/connection_health.rb +194 -0
  25. data/lib/pgbus/client.rb +592 -73
  26. data/lib/pgbus/config_loader.rb +50 -4
  27. data/lib/pgbus/configuration.rb +294 -79
  28. data/lib/pgbus/dedup_cache.rb +8 -0
  29. data/lib/pgbus/doctor.rb +275 -0
  30. data/lib/pgbus/engine.rb +15 -0
  31. data/lib/pgbus/execution_pools/async_pool.rb +9 -2
  32. data/lib/pgbus/execution_pools/thread_pool.rb +7 -0
  33. data/lib/pgbus/generators/config_converter.rb +7 -5
  34. data/lib/pgbus/generators/migration_detector.rb +20 -16
  35. data/lib/pgbus/instrumentation.rb +3 -1
  36. data/lib/pgbus/integrations/appsignal/probe.rb +23 -1
  37. data/lib/pgbus/integrations/appsignal/subscriber.rb +17 -2
  38. data/lib/pgbus/metrics/backend.rb +38 -0
  39. data/lib/pgbus/metrics/backends/prometheus.rb +123 -0
  40. data/lib/pgbus/metrics/backends/statsd.rb +64 -0
  41. data/lib/pgbus/metrics/prometheus_exporter.rb +34 -0
  42. data/lib/pgbus/metrics/subscriber.rb +214 -0
  43. data/lib/pgbus/metrics.rb +43 -0
  44. data/lib/pgbus/pgmq_schema/pgmq_v1.11.1.sql +2126 -0
  45. data/lib/pgbus/pgmq_schema.rb +7 -2
  46. data/lib/pgbus/process/consumer.rb +354 -18
  47. data/lib/pgbus/process/consumer_priority.rb +34 -0
  48. data/lib/pgbus/process/dispatcher.rb +265 -41
  49. data/lib/pgbus/process/heartbeat.rb +18 -5
  50. data/lib/pgbus/process/memory_usage.rb +48 -0
  51. data/lib/pgbus/process/notify_listener.rb +26 -7
  52. data/lib/pgbus/process/notify_probe.rb +96 -0
  53. data/lib/pgbus/process/primary_validator.rb +53 -0
  54. data/lib/pgbus/process/signal_handler.rb +6 -0
  55. data/lib/pgbus/process/supervisor.rb +423 -50
  56. data/lib/pgbus/process/worker.rb +288 -35
  57. data/lib/pgbus/recurring/schedule.rb +1 -2
  58. data/lib/pgbus/recurring/scheduler.rb +15 -1
  59. data/lib/pgbus/serializer.rb +4 -4
  60. data/lib/pgbus/streams/broadcastable_override.rb +0 -8
  61. data/lib/pgbus/streams/signed_name.rb +2 -2
  62. data/lib/pgbus/streams/turbo_broadcastable.rb +7 -5
  63. data/lib/pgbus/table_maintenance.rb +13 -2
  64. data/lib/pgbus/uniqueness.rb +11 -12
  65. data/lib/pgbus/version.rb +1 -1
  66. data/lib/pgbus/web/data_source.rb +36 -4
  67. data/lib/pgbus/web/health_app.rb +102 -0
  68. data/lib/pgbus/web/health_server.rb +144 -0
  69. data/lib/pgbus/web/payload_filter.rb +3 -3
  70. data/lib/pgbus/web/streamer/instance.rb +58 -2
  71. data/lib/pgbus/web/streamer/listener.rb +69 -21
  72. data/lib/pgbus.rb +77 -0
  73. data/lib/tasks/pgbus_doctor.rake +12 -0
  74. metadata +19 -4
  75. data/app/models/pgbus/job_lock.rb +0 -98
  76. data/lib/generators/pgbus/templates/add_job_locks.rb.erb +0 -21
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.9.7
4
+ version: 0.9.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mikael Henriksson
@@ -150,7 +150,6 @@ files:
150
150
  - app/models/pgbus/application_record.rb
151
151
  - app/models/pgbus/batch_entry.rb
152
152
  - app/models/pgbus/blocked_execution.rb
153
- - app/models/pgbus/job_lock.rb
154
153
  - app/models/pgbus/job_stat.rb
155
154
  - app/models/pgbus/outbox_entry.rb
156
155
  - app/models/pgbus/process_entry.rb
@@ -209,7 +208,6 @@ files:
209
208
  - exe/pgbus
210
209
  - lib/active_job/queue_adapters/pgbus_adapter.rb
211
210
  - lib/generators/pgbus/add_failed_events_index_generator.rb
212
- - lib/generators/pgbus/add_job_locks_generator.rb
213
211
  - lib/generators/pgbus/add_job_stats_generator.rb
214
212
  - lib/generators/pgbus/add_job_stats_latency_generator.rb
215
213
  - lib/generators/pgbus/add_job_stats_queue_index_generator.rb
@@ -218,11 +216,11 @@ files:
218
216
  - lib/generators/pgbus/add_queue_states_generator.rb
219
217
  - lib/generators/pgbus/add_recurring_generator.rb
220
218
  - lib/generators/pgbus/add_stream_stats_generator.rb
219
+ - lib/generators/pgbus/add_uniqueness_keys_generator.rb
221
220
  - lib/generators/pgbus/install_generator.rb
222
221
  - lib/generators/pgbus/migrate_job_locks_generator.rb
223
222
  - lib/generators/pgbus/migration_path.rb
224
223
  - lib/generators/pgbus/templates/add_failed_events_unique_index.rb.erb
225
- - lib/generators/pgbus/templates/add_job_locks.rb.erb
226
224
  - lib/generators/pgbus/templates/add_job_stats.rb.erb
227
225
  - lib/generators/pgbus/templates/add_job_stats_latency.rb.erb
228
226
  - lib/generators/pgbus/templates/add_job_stats_queue_index.rb.erb
@@ -252,7 +250,9 @@ files:
252
250
  - lib/pgbus/bus_record.rb
253
251
  - lib/pgbus/circuit_breaker.rb
254
252
  - lib/pgbus/cli.rb
253
+ - lib/pgbus/cli/dlq.rb
255
254
  - lib/pgbus/client.rb
255
+ - lib/pgbus/client/connection_health.rb
256
256
  - lib/pgbus/client/ensure_stream_queue.rb
257
257
  - lib/pgbus/client/notify_stream.rb
258
258
  - lib/pgbus/client/read_after.rb
@@ -263,6 +263,7 @@ files:
263
263
  - lib/pgbus/configuration.rb
264
264
  - lib/pgbus/configuration/capsule_dsl.rb
265
265
  - lib/pgbus/dedup_cache.rb
266
+ - lib/pgbus/doctor.rb
266
267
  - lib/pgbus/engine.rb
267
268
  - lib/pgbus/error_reporter.rb
268
269
  - lib/pgbus/event.rb
@@ -305,16 +306,26 @@ files:
305
306
  - lib/pgbus/mcp/tools/recurring_tool.rb
306
307
  - lib/pgbus/mcp/tools/stats_tool.rb
307
308
  - lib/pgbus/mcp/tools/throughput_tool.rb
309
+ - lib/pgbus/metrics.rb
310
+ - lib/pgbus/metrics/backend.rb
311
+ - lib/pgbus/metrics/backends/prometheus.rb
312
+ - lib/pgbus/metrics/backends/statsd.rb
313
+ - lib/pgbus/metrics/prometheus_exporter.rb
314
+ - lib/pgbus/metrics/subscriber.rb
308
315
  - lib/pgbus/outbox.rb
309
316
  - lib/pgbus/outbox/poller.rb
310
317
  - lib/pgbus/pgmq_schema.rb
311
318
  - lib/pgbus/pgmq_schema/pgmq_v1.11.0.sql
319
+ - lib/pgbus/pgmq_schema/pgmq_v1.11.1.sql
312
320
  - lib/pgbus/process/consumer.rb
313
321
  - lib/pgbus/process/consumer_priority.rb
314
322
  - lib/pgbus/process/dispatcher.rb
315
323
  - lib/pgbus/process/heartbeat.rb
316
324
  - lib/pgbus/process/lifecycle.rb
325
+ - lib/pgbus/process/memory_usage.rb
317
326
  - lib/pgbus/process/notify_listener.rb
327
+ - lib/pgbus/process/notify_probe.rb
328
+ - lib/pgbus/process/primary_validator.rb
318
329
  - lib/pgbus/process/queue_lock.rb
319
330
  - lib/pgbus/process/signal_handler.rb
320
331
  - lib/pgbus/process/supervisor.rb
@@ -355,6 +366,8 @@ files:
355
366
  - lib/pgbus/version.rb
356
367
  - lib/pgbus/web/authentication.rb
357
368
  - lib/pgbus/web/data_source.rb
369
+ - lib/pgbus/web/health_app.rb
370
+ - lib/pgbus/web/health_server.rb
358
371
  - lib/pgbus/web/metrics_serializer.rb
359
372
  - lib/pgbus/web/payload_filter.rb
360
373
  - lib/pgbus/web/stream_app.rb
@@ -370,6 +383,7 @@ files:
370
383
  - lib/pgbus/web/streamer/stream_event_dispatcher.rb
371
384
  - lib/puma/plugin/pgbus_streams.rb
372
385
  - lib/tasks/pgbus_autovacuum.rake
386
+ - lib/tasks/pgbus_doctor.rake
373
387
  - lib/tasks/pgbus_pgmq.rake
374
388
  - lib/tasks/pgbus_queues.rake
375
389
  - lib/tasks/pgbus_streams.rake
@@ -380,6 +394,7 @@ metadata:
380
394
  homepage_uri: https://github.com/mhenrixon/pgbus
381
395
  source_code_uri: https://github.com/mhenrixon/pgbus/tree/main
382
396
  changelog_uri: https://github.com/mhenrixon/pgbus/blob/main/CHANGELOG.md
397
+ documentation_uri: https://pgbus.zoolutions.llc
383
398
  rubygems_mfa_required: 'true'
384
399
  rdoc_options: []
385
400
  require_paths:
@@ -1,98 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Pgbus
4
- class JobLock < BusRecord
5
- self.table_name = "pgbus_job_locks"
6
-
7
- # States:
8
- # queued — lock held from enqueue time (:until_executed), no worker yet
9
- # executing — lock held by an active worker process
10
- STATES = %w[queued executing].freeze
11
-
12
- scope :executing, -> { where(state: "executing") }
13
- scope :queued_locks, -> { where(state: "queued") }
14
- scope :expired, ->(now = Time.current) { where("expires_at < ?", now) }
15
-
16
- # Atomically try to acquire a lock.
17
- # Cleans up expired locks for this key first (crash recovery at acquire time).
18
- # Returns true if acquired, false if already locked.
19
- #
20
- # Uses raw SQL on the hot path to minimize ActiveRecord allocations
21
- # (~29 objects vs ~304 per acquire+release cycle with AR query builder).
22
- def self.acquire!(lock_key, job_class:, ttl:, job_id: nil, state: "queued", owner_pid: nil, owner_hostname: nil) # rubocop:disable Naming/PredicateMethod
23
- expires_at = Time.current + ttl
24
-
25
- # Remove any expired lock for this key inline (last-resort TTL recovery)
26
- connection.exec_delete(
27
- "DELETE FROM #{table_name} WHERE lock_key = $1 AND expires_at < $2",
28
- "JobLock Expire", [lock_key, Time.current]
29
- )
30
-
31
- result = connection.exec_query(
32
- "INSERT INTO #{table_name} (lock_key, job_class, job_id, state, owner_pid, owner_hostname, expires_at) " \
33
- "VALUES ($1, $2, $3, $4, $5, $6, $7) ON CONFLICT (lock_key) DO NOTHING RETURNING id",
34
- "JobLock Acquire", [lock_key, job_class, job_id, state, owner_pid, owner_hostname, expires_at]
35
- )
36
- result.rows.any?
37
- end
38
-
39
- # Transition a queued lock to executing state and claim ownership.
40
- # Called when a worker starts executing a job that was locked at enqueue time.
41
- def self.claim_for_execution!(lock_key, owner_pid:, owner_hostname:, ttl:)
42
- connection.exec_update(
43
- "UPDATE #{table_name} SET state = $1, owner_pid = $2, owner_hostname = $3, expires_at = $4 " \
44
- "WHERE lock_key = $5",
45
- "JobLock Claim", ["executing", owner_pid, owner_hostname, Time.current + ttl, lock_key]
46
- )
47
- end
48
-
49
- # Release a lock by key.
50
- def self.release!(lock_key)
51
- connection.exec_delete(
52
- "DELETE FROM #{table_name} WHERE lock_key = $1",
53
- "JobLock Release", [lock_key]
54
- )
55
- end
56
-
57
- # Check if a lock is currently held (regardless of expiry — reaper handles orphans).
58
- def self.locked?(lock_key)
59
- result = connection.select_value(
60
- "SELECT 1 FROM #{table_name} WHERE lock_key = $1 LIMIT 1", "JobLock Check", [lock_key]
61
- )
62
- !result.nil?
63
- end
64
-
65
- # Reap orphaned locks whose owner is no longer alive, plus stale queued
66
- # locks that were never claimed by a worker.
67
- # Returns the total number of orphaned locks released.
68
- def self.reap_orphaned!
69
- reaped = 0
70
-
71
- # 1. Executing locks whose owner process has no healthy heartbeat
72
- alive_workers = ProcessEntry
73
- .where("last_heartbeat_at >= ?", Time.current - Process::Heartbeat::ALIVE_THRESHOLD)
74
- .pluck(:pid, :hostname)
75
-
76
- orphaned_executing = executing.select do |lock|
77
- alive_workers.none? { |pid, hostname| pid == lock.owner_pid && hostname == lock.owner_hostname }
78
- end
79
-
80
- reaped += where(id: orphaned_executing.map(&:id)).delete_all if orphaned_executing.any?
81
-
82
- # 2. Queued locks older than the visibility timeout that were never
83
- # claimed. These are left behind when enqueue fails after lock
84
- # acquisition (e.g. network error, process crash).
85
- threshold = Pgbus.configuration.visibility_timeout
86
- stale_queued = queued_locks.where("locked_at < ?", Time.current - threshold)
87
- reaped += stale_queued.delete_all if stale_queued.exists?
88
-
89
- reaped
90
- end
91
-
92
- # Last-resort cleanup: delete locks whose expires_at has passed.
93
- # This only fires when the reaper itself can't run (e.g., entire supervisor dead).
94
- def self.cleanup_expired!
95
- expired.delete_all
96
- end
97
- end
98
- end
@@ -1,21 +0,0 @@
1
- class AddPgbusJobLocks < ActiveRecord::Migration<%= migration_version %>
2
- def change
3
- create_table :pgbus_job_locks do |t|
4
- t.string :lock_key, null: false
5
- t.string :job_class, null: false
6
- t.string :job_id
7
- t.string :state, null: false, default: "queued"
8
- t.integer :owner_pid
9
- t.string :owner_hostname
10
- t.datetime :locked_at, null: false, default: -> { "CURRENT_TIMESTAMP" }
11
- t.datetime :expires_at, null: false
12
- end
13
-
14
- add_index :pgbus_job_locks, :lock_key,
15
- unique: true, name: "idx_pgbus_job_locks_key"
16
- add_index :pgbus_job_locks, :expires_at,
17
- name: "idx_pgbus_job_locks_expires"
18
- add_index :pgbus_job_locks, [:state, :owner_pid],
19
- name: "idx_pgbus_job_locks_reaper"
20
- end
21
- end