pgbus 0.3.6 → 0.3.8
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/generators/pgbus/add_failed_events_index_generator.rb +51 -0
- data/lib/generators/pgbus/templates/add_failed_events_unique_index.rb.erb +7 -0
- data/lib/generators/pgbus/templates/migration.rb.erb +2 -0
- data/lib/pgbus/active_job/executor.rb +13 -2
- data/lib/pgbus/failed_event_recorder.rb +59 -0
- data/lib/pgbus/recurring/schedule.rb +5 -2
- data/lib/pgbus/version.rb +1 -1
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 76f2d5b122b3a379c36fcef393e8b7eabcfdbc7c8534d76948b1349dd97cb58b
|
|
4
|
+
data.tar.gz: 4059948c68407aebeebd6f45d3b550158c6d38455c95cf255ccf1faf2d10889a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b21741c2de21716ce47c0246c8de08bb440e9a4dfc750bc60e95484b97fe9c7626b357c47c4b2b7583c6d8273fef08b363aa84ed5424bf21a64bb64519ce64b7
|
|
7
|
+
data.tar.gz: 48983a1abcbfc000936caf9ff2cac0d7451633aee2ee05c68c4168b84c0aed4ee38e4108501214b91c5045e9cc97f269f55c3e9e6050d778ac06b07a11c6c45d
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
require "rails/generators/active_record"
|
|
5
|
+
|
|
6
|
+
module Pgbus
|
|
7
|
+
module Generators
|
|
8
|
+
class AddFailedEventsIndexGenerator < Rails::Generators::Base
|
|
9
|
+
include ActiveRecord::Generators::Migration
|
|
10
|
+
|
|
11
|
+
source_root File.expand_path("templates", __dir__)
|
|
12
|
+
|
|
13
|
+
desc "Add unique index on pgbus_failed_events (queue_name, msg_id) for failure tracking upserts"
|
|
14
|
+
|
|
15
|
+
class_option :database,
|
|
16
|
+
type: :string,
|
|
17
|
+
default: nil,
|
|
18
|
+
desc: "Use a separate database for pgbus tables (e.g. --database=pgbus)"
|
|
19
|
+
|
|
20
|
+
def create_migration_file
|
|
21
|
+
if separate_database?
|
|
22
|
+
migration_template "add_failed_events_unique_index.rb.erb",
|
|
23
|
+
"db/pgbus_migrate/add_pgbus_failed_events_unique_index.rb"
|
|
24
|
+
else
|
|
25
|
+
migration_template "add_failed_events_unique_index.rb.erb",
|
|
26
|
+
"db/migrate/add_pgbus_failed_events_unique_index.rb"
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def display_post_install
|
|
31
|
+
say ""
|
|
32
|
+
say "Pgbus failed events unique index added!", :green
|
|
33
|
+
say ""
|
|
34
|
+
say "Next steps:"
|
|
35
|
+
say " 1. Run: rails db:migrate#{":#{options[:database]}" if separate_database?}"
|
|
36
|
+
say " 2. Failed jobs will now be tracked in the dashboard"
|
|
37
|
+
say ""
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def migration_version
|
|
43
|
+
"[#{ActiveRecord::Migration.current_version}]"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def separate_database?
|
|
47
|
+
options[:database].present?
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -65,6 +65,8 @@ class CreatePgbusTables < ActiveRecord::Migration<%= migration_version %>
|
|
|
65
65
|
|
|
66
66
|
add_index :pgbus_failed_events, :queue_name, name: "idx_pgbus_failed_events_queue"
|
|
67
67
|
add_index :pgbus_failed_events, :failed_at, name: "idx_pgbus_failed_events_time"
|
|
68
|
+
add_index :pgbus_failed_events, [:queue_name, :msg_id],
|
|
69
|
+
unique: true, name: "idx_pgbus_failed_events_queue_msg"
|
|
68
70
|
|
|
69
71
|
# Concurrency semaphores (counting locks for job concurrency limits)
|
|
70
72
|
create_table :pgbus_semaphores do |t|
|
|
@@ -20,6 +20,7 @@ module Pgbus
|
|
|
20
20
|
|
|
21
21
|
if read_count > config.max_retries
|
|
22
22
|
handle_dead_letter(message, queue_name, payload, source_queue: source_queue)
|
|
23
|
+
FailedEventRecorder.clear!(queue_name: queue_name, msg_id: message.msg_id.to_i)
|
|
23
24
|
signal_concurrency(payload)
|
|
24
25
|
signal_batch_discarded(payload)
|
|
25
26
|
Uniqueness.release_lock(Uniqueness.extract_key(payload))
|
|
@@ -54,6 +55,7 @@ module Pgbus
|
|
|
54
55
|
job = ::ActiveJob::Base.deserialize(payload)
|
|
55
56
|
execute_job(job)
|
|
56
57
|
archive_from(queue_name, message.msg_id.to_i, source_queue: source_queue)
|
|
58
|
+
FailedEventRecorder.clear!(queue_name: queue_name, msg_id: message.msg_id.to_i)
|
|
57
59
|
job_succeeded = true
|
|
58
60
|
end
|
|
59
61
|
|
|
@@ -61,7 +63,7 @@ module Pgbus
|
|
|
61
63
|
record_stat(payload, queue_name, "success", execution_start, message: message)
|
|
62
64
|
:success
|
|
63
65
|
rescue StandardError => e
|
|
64
|
-
handle_failure(message, queue_name, e)
|
|
66
|
+
handle_failure(message, queue_name, e, payload: payload)
|
|
65
67
|
instrument("pgbus.job_failed", queue: queue_name, job_class: payload&.dig("job_class"), error: e.class.name)
|
|
66
68
|
record_stat(payload, queue_name, "failed", execution_start, message: message)
|
|
67
69
|
# Don't signal concurrency on transient failure — the job will be retried.
|
|
@@ -143,13 +145,22 @@ module Pgbus
|
|
|
143
145
|
[((Time.now.utc - enqueued_at) * 1000).round, 0].max
|
|
144
146
|
end
|
|
145
147
|
|
|
146
|
-
def handle_failure(
|
|
148
|
+
def handle_failure(message, queue_name, error, payload: nil)
|
|
147
149
|
Pgbus.logger.error { "[Pgbus] Job failed: #{error.class}: #{error.message}" }
|
|
148
150
|
Pgbus.logger.debug { error.backtrace&.join("\n") }
|
|
149
151
|
|
|
152
|
+
# Record failure for dashboard visibility.
|
|
150
153
|
# Message visibility timeout will expire and it becomes available again.
|
|
151
154
|
# read_ct tracks delivery attempts — when it exceeds max_retries,
|
|
152
155
|
# the next read will route to DLQ.
|
|
156
|
+
FailedEventRecorder.record!(
|
|
157
|
+
queue_name: queue_name,
|
|
158
|
+
msg_id: message.msg_id.to_i,
|
|
159
|
+
payload: payload || message.message,
|
|
160
|
+
headers: message.respond_to?(:headers) ? message.headers : nil,
|
|
161
|
+
error: error,
|
|
162
|
+
retry_count: [message.read_ct.to_i - 1, 0].max
|
|
163
|
+
)
|
|
153
164
|
end
|
|
154
165
|
|
|
155
166
|
def instrument(event_name, payload = {})
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pgbus
|
|
4
|
+
# Records job failures to pgbus_failed_events for dashboard visibility.
|
|
5
|
+
# Uses upsert (INSERT ON CONFLICT UPDATE) keyed on (queue_name, msg_id)
|
|
6
|
+
# so each message has at most one failed_event row tracking its latest error.
|
|
7
|
+
class FailedEventRecorder
|
|
8
|
+
class << self
|
|
9
|
+
def record!(queue_name:, msg_id:, payload:, headers:, error:, retry_count:)
|
|
10
|
+
connection.exec_query(
|
|
11
|
+
<<~SQL.squish,
|
|
12
|
+
INSERT INTO pgbus_failed_events
|
|
13
|
+
(queue_name, msg_id, payload, headers, error_class, error_message, backtrace, retry_count, failed_at)
|
|
14
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, CURRENT_TIMESTAMP)
|
|
15
|
+
ON CONFLICT (queue_name, msg_id) DO UPDATE SET
|
|
16
|
+
error_class = EXCLUDED.error_class,
|
|
17
|
+
error_message = EXCLUDED.error_message,
|
|
18
|
+
backtrace = EXCLUDED.backtrace,
|
|
19
|
+
retry_count = EXCLUDED.retry_count,
|
|
20
|
+
failed_at = EXCLUDED.failed_at
|
|
21
|
+
SQL
|
|
22
|
+
"FailedEvent Record",
|
|
23
|
+
[
|
|
24
|
+
queue_name,
|
|
25
|
+
msg_id.to_i,
|
|
26
|
+
payload.is_a?(String) ? payload : JSON.generate(payload),
|
|
27
|
+
headers.is_a?(String) ? headers : headers&.then { |h| JSON.generate(h) },
|
|
28
|
+
error.class.name,
|
|
29
|
+
error.message.to_s.truncate(10_000),
|
|
30
|
+
error.backtrace&.first(30)&.join("\n"),
|
|
31
|
+
retry_count
|
|
32
|
+
]
|
|
33
|
+
)
|
|
34
|
+
rescue StandardError => e
|
|
35
|
+
Pgbus.logger.debug { "[Pgbus] Failed to record failed event: #{e.message}" }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def clear!(queue_name:, msg_id:)
|
|
39
|
+
connection.exec_delete(
|
|
40
|
+
"DELETE FROM pgbus_failed_events WHERE queue_name = $1 AND msg_id = $2",
|
|
41
|
+
"FailedEvent Clear",
|
|
42
|
+
[queue_name, msg_id.to_i]
|
|
43
|
+
)
|
|
44
|
+
rescue StandardError => e
|
|
45
|
+
Pgbus.logger.debug { "[Pgbus] Failed to clear failed event: #{e.message}" }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def connection
|
|
51
|
+
if defined?(BusRecord) && BusRecord.connected?
|
|
52
|
+
BusRecord.connection
|
|
53
|
+
else
|
|
54
|
+
ActiveRecord::Base.connection
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -151,8 +151,11 @@ module Pgbus
|
|
|
151
151
|
:already_locked
|
|
152
152
|
end
|
|
153
153
|
rescue StandardError => e
|
|
154
|
-
Pgbus.logger.
|
|
155
|
-
|
|
154
|
+
Pgbus.logger.error do
|
|
155
|
+
"[Pgbus] Uniqueness lock check failed for #{task.key}: #{e.class}: #{e.message} — " \
|
|
156
|
+
"skipping enqueue to prevent duplicates"
|
|
157
|
+
end
|
|
158
|
+
:already_locked # Fail closed — skip enqueue when lock check errors
|
|
156
159
|
end
|
|
157
160
|
|
|
158
161
|
# Release a uniqueness lock. Safe to call with nil or :already_locked.
|
data/lib/pgbus/version.rb
CHANGED
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.3.
|
|
4
|
+
version: 0.3.8
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mikael Henriksson
|
|
@@ -198,6 +198,7 @@ files:
|
|
|
198
198
|
- config/routes.rb
|
|
199
199
|
- exe/pgbus
|
|
200
200
|
- lib/active_job/queue_adapters/pgbus_adapter.rb
|
|
201
|
+
- lib/generators/pgbus/add_failed_events_index_generator.rb
|
|
201
202
|
- lib/generators/pgbus/add_job_locks_generator.rb
|
|
202
203
|
- lib/generators/pgbus/add_job_stats_generator.rb
|
|
203
204
|
- lib/generators/pgbus/add_job_stats_latency_generator.rb
|
|
@@ -206,6 +207,7 @@ files:
|
|
|
206
207
|
- lib/generators/pgbus/add_recurring_generator.rb
|
|
207
208
|
- lib/generators/pgbus/install_generator.rb
|
|
208
209
|
- lib/generators/pgbus/migrate_job_locks_generator.rb
|
|
210
|
+
- lib/generators/pgbus/templates/add_failed_events_unique_index.rb.erb
|
|
209
211
|
- lib/generators/pgbus/templates/add_job_locks.rb.erb
|
|
210
212
|
- lib/generators/pgbus/templates/add_job_stats.rb.erb
|
|
211
213
|
- lib/generators/pgbus/templates/add_job_stats_latency.rb.erb
|
|
@@ -240,6 +242,7 @@ files:
|
|
|
240
242
|
- lib/pgbus/event_bus/publisher.rb
|
|
241
243
|
- lib/pgbus/event_bus/registry.rb
|
|
242
244
|
- lib/pgbus/event_bus/subscriber.rb
|
|
245
|
+
- lib/pgbus/failed_event_recorder.rb
|
|
243
246
|
- lib/pgbus/instrumentation.rb
|
|
244
247
|
- lib/pgbus/outbox.rb
|
|
245
248
|
- lib/pgbus/outbox/poller.rb
|