pgbus 0.23.1 → 0.23.2
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/CHANGELOG.md +12 -0
- data/README.md +1 -1
- data/app/assets/javascripts/pgbus/stream_source_element.js +55 -11
- data/app/assets/javascripts/pgbus/stream_source_element.min.js +4 -4
- data/app/assets/javascripts/pgbus/stream_source_element.min.js.map +1 -1
- data/app/frontend/pgbus/application.min.js.map +1 -1
- data/app/frontend/pgbus/modules/charts.min.js.map +1 -1
- data/app/frontend/pgbus/vendor/turbo.min.js.map +1 -1
- data/app/models/pgbus/batch_entry.rb +70 -29
- data/app/models/pgbus/batch_execution.rb +8 -6
- data/app/models/pgbus/blocked_execution.rb +17 -15
- data/app/models/pgbus/job_stat.rb +16 -13
- data/app/models/pgbus/semaphore.rb +16 -14
- data/app/models/pgbus/uniqueness_key.rb +58 -45
- data/lib/pgbus/active_job/adapter.rb +7 -5
- data/lib/pgbus/batch.rb +76 -37
- data/lib/pgbus/concurrency/blocked_execution.rb +28 -24
- data/lib/pgbus/concurrency/semaphore.rb +17 -9
- data/lib/pgbus/failed_event_recorder.rb +34 -25
- data/lib/pgbus/version.rb +1 -1
- metadata +1 -1
|
@@ -58,28 +58,30 @@ module Pgbus
|
|
|
58
58
|
def promote_next(concurrency_key, client:, delay: 0)
|
|
59
59
|
released = nil
|
|
60
60
|
msg_id = nil
|
|
61
|
-
Pgbus::BlockedExecution.
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
61
|
+
Pgbus::BlockedExecution.connection_pool.with_connection do
|
|
62
|
+
Pgbus::BlockedExecution.transaction(requires_new: true) do
|
|
63
|
+
released = release_next(concurrency_key)
|
|
64
|
+
raise ActiveRecord::Rollback unless released
|
|
65
|
+
|
|
66
|
+
# Resolve the delay first: a parked scheduled job goes back into
|
|
67
|
+
# PGMQ invisible, and nothing renews its lease until a worker
|
|
68
|
+
# picks it up, so the wait has to be part of the lease.
|
|
69
|
+
actual_delay = resolve_delay(released[:payload], delay)
|
|
70
|
+
raise ActiveRecord::Rollback unless slot_taken?(concurrency_key, released[:payload], actual_delay)
|
|
71
|
+
|
|
72
|
+
# Carry the enqueuer's priority through: under priority routing it
|
|
73
|
+
# picks the _pN sub-queue, not just the release order (issue #423).
|
|
74
|
+
headers = released[:payload].is_a?(Hash) ? released[:payload].delete(HEADERS_KEY) : nil
|
|
75
|
+
msg_id = client.send_message(released[:queue_name], released[:payload],
|
|
76
|
+
delay: actual_delay, priority: released[:priority],
|
|
77
|
+
**(headers ? { headers: headers } : {}))
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
next false unless released && msg_id
|
|
81
|
+
|
|
82
|
+
backfill(released, msg_id, client)
|
|
83
|
+
true
|
|
77
84
|
end
|
|
78
|
-
|
|
79
|
-
return false unless released && msg_id
|
|
80
|
-
|
|
81
|
-
backfill(released, msg_id, client)
|
|
82
|
-
true
|
|
83
85
|
rescue StandardError => e
|
|
84
86
|
Pgbus.logger.warn { "[Pgbus] Promote blocked execution failed for #{concurrency_key}: #{e.message}" }
|
|
85
87
|
false
|
|
@@ -112,9 +114,11 @@ module Pgbus
|
|
|
112
114
|
# un-taking the slot while the message is already live, so the job
|
|
113
115
|
# runs a second time.
|
|
114
116
|
def backfill(released, msg_id, client)
|
|
115
|
-
Pgbus::BlockedExecution.
|
|
116
|
-
|
|
117
|
-
|
|
117
|
+
Pgbus::BlockedExecution.connection_pool.with_connection do
|
|
118
|
+
Pgbus::BlockedExecution.transaction(requires_new: true) do
|
|
119
|
+
Batch.backfill_execution(released[:payload], msg_id,
|
|
120
|
+
client.target_queue(released[:queue_name], released[:priority]))
|
|
121
|
+
end
|
|
118
122
|
end
|
|
119
123
|
rescue StandardError => e
|
|
120
124
|
Pgbus.logger.warn { "[Pgbus] Batch execution backfill failed after promote: #{e.message}" }
|
|
@@ -19,17 +19,23 @@ module Pgbus
|
|
|
19
19
|
|
|
20
20
|
# Release one slot in the semaphore. Called after a job completes.
|
|
21
21
|
def release(key)
|
|
22
|
-
Pgbus::Semaphore.
|
|
22
|
+
Pgbus::Semaphore.connection_pool.with_connection do
|
|
23
|
+
Pgbus::Semaphore.where(key: key).update_all("value = GREATEST(value - 1, 0)")
|
|
24
|
+
end
|
|
23
25
|
end
|
|
24
26
|
|
|
25
27
|
# A job holding a slot is done: give the slot back and hand it to the
|
|
26
28
|
# next parked job for this key, in one transaction. The decrement locks
|
|
27
29
|
# the semaphore row first, so an enqueue that is parking a job right
|
|
28
30
|
# now commits before the promote looks for parked rows.
|
|
31
|
+
# The lease is the caller's when it holds one, else taken for the
|
|
32
|
+
# signal and returned at its end (issue #649).
|
|
29
33
|
def signal(key, client:)
|
|
30
|
-
Pgbus::Semaphore.
|
|
31
|
-
|
|
32
|
-
|
|
34
|
+
Pgbus::Semaphore.connection_pool.with_connection do
|
|
35
|
+
Pgbus::Semaphore.transaction do
|
|
36
|
+
release(key)
|
|
37
|
+
BlockedExecution.promote_next(key, client: client)
|
|
38
|
+
end
|
|
33
39
|
end
|
|
34
40
|
end
|
|
35
41
|
|
|
@@ -54,11 +60,13 @@ module Pgbus
|
|
|
54
60
|
# Returns an array of hashes with expired keys.
|
|
55
61
|
# Uses DELETE ... RETURNING for atomicity (no race between pluck and delete).
|
|
56
62
|
def expire_stale
|
|
57
|
-
result = Pgbus::Semaphore.
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
63
|
+
result = Pgbus::Semaphore.connection_pool.with_connection do |conn|
|
|
64
|
+
conn.exec_query(
|
|
65
|
+
"DELETE FROM pgbus_semaphores WHERE expires_at < $1 RETURNING key",
|
|
66
|
+
"Pgbus Semaphore Expire",
|
|
67
|
+
[Time.current]
|
|
68
|
+
)
|
|
69
|
+
end
|
|
62
70
|
result.rows.map { |row| { "key" => row[0] } }
|
|
63
71
|
end
|
|
64
72
|
|
|
@@ -32,11 +32,12 @@ module Pgbus
|
|
|
32
32
|
retry_count
|
|
33
33
|
]
|
|
34
34
|
legacy = !source_queue_column?
|
|
35
|
-
if legacy
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
sql, binds = if legacy
|
|
36
|
+
[LEGACY_RECORD_SQL, [queue_name, msg_id.to_i, *details]]
|
|
37
|
+
else
|
|
38
|
+
[RECORD_SQL, [queue_name, source_queue, msg_id.to_i, *details]]
|
|
39
|
+
end
|
|
40
|
+
with_connection { |conn| conn.exec_query(sql, "FailedEvent Record", binds) }
|
|
40
41
|
rescue StandardError => e
|
|
41
42
|
# A process started before the migration would keep sending the old
|
|
42
43
|
# ON CONFLICT target, whose index the migration drops: probe again.
|
|
@@ -46,7 +47,9 @@ module Pgbus
|
|
|
46
47
|
|
|
47
48
|
def exists?(queue_name:, msg_id:, source_queue:)
|
|
48
49
|
sql, binds = message_scope(queue_name, msg_id, source_queue)
|
|
49
|
-
result =
|
|
50
|
+
result = with_connection do |conn|
|
|
51
|
+
conn.select_value("SELECT 1 FROM pgbus_failed_events WHERE #{sql} LIMIT 1", "FailedEvent Exists", binds)
|
|
52
|
+
end
|
|
50
53
|
!result.nil?
|
|
51
54
|
rescue StandardError => e
|
|
52
55
|
Pgbus.logger.debug { "[Pgbus] FailedEvent exists? check failed: #{e.class}: #{e.message}" }
|
|
@@ -61,12 +64,14 @@ module Pgbus
|
|
|
61
64
|
def last_error(queue_name:, msg_id:, source_queue:)
|
|
62
65
|
sql, binds = message_scope(queue_name, msg_id, source_queue)
|
|
63
66
|
order = binds.size > 2 ? " ORDER BY source_queue IS NULL, failed_at DESC" : ""
|
|
64
|
-
row =
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
row = with_connection do |conn|
|
|
68
|
+
conn.select_one(
|
|
69
|
+
"SELECT error_class, error_message, backtrace, retry_count, failed_at " \
|
|
70
|
+
"FROM pgbus_failed_events WHERE #{sql}#{order} LIMIT 1",
|
|
71
|
+
"FailedEvent Last Error",
|
|
72
|
+
binds
|
|
73
|
+
)
|
|
74
|
+
end
|
|
70
75
|
return unless row
|
|
71
76
|
|
|
72
77
|
{
|
|
@@ -89,11 +94,13 @@ module Pgbus
|
|
|
89
94
|
# case, while a row may carry the executor's configured spelling
|
|
90
95
|
# ("Critical") or a physical name from before #633.
|
|
91
96
|
def clear_queue!(queue_names)
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
+
with_connection do |conn|
|
|
98
|
+
conn.exec_delete(
|
|
99
|
+
"DELETE FROM pgbus_failed_events WHERE lower(queue_name) = ANY($1::text[])",
|
|
100
|
+
"FailedEvent Clear Queue",
|
|
101
|
+
["{#{queue_names.map(&:downcase).join(",")}}"]
|
|
102
|
+
)
|
|
103
|
+
end
|
|
97
104
|
rescue StandardError => e
|
|
98
105
|
Pgbus.logger.error do
|
|
99
106
|
"[Pgbus] Failed to clear failed events for dropped queue(s) #{queue_names.join(", ")}: " \
|
|
@@ -105,7 +112,7 @@ module Pgbus
|
|
|
105
112
|
# existed: a sibling sub-queue's row with the same msg_id stays.
|
|
106
113
|
def clear!(queue_name:, msg_id:, source_queue:)
|
|
107
114
|
sql, binds = message_scope(queue_name, msg_id, source_queue)
|
|
108
|
-
|
|
115
|
+
with_connection { |conn| conn.exec_delete("DELETE FROM pgbus_failed_events WHERE #{sql}", "FailedEvent Clear", binds) }
|
|
109
116
|
rescue StandardError => e
|
|
110
117
|
# ERROR-level: a failed clear leaves a stale row in the dashboard
|
|
111
118
|
# AFTER the job actually succeeded — confusing and load-bearing
|
|
@@ -168,7 +175,7 @@ module Pgbus
|
|
|
168
175
|
end
|
|
169
176
|
|
|
170
177
|
def detect_source_queue_column(warn:)
|
|
171
|
-
supported =
|
|
178
|
+
supported = with_connection { |conn| conn.column_exists?(:pgbus_failed_events, :source_queue) }
|
|
172
179
|
if warn && !supported
|
|
173
180
|
Pgbus.logger.warn do
|
|
174
181
|
"[Pgbus] pgbus_failed_events is missing the source_queue column; under priority routing two " \
|
|
@@ -186,12 +193,14 @@ module Pgbus
|
|
|
186
193
|
value.to_s
|
|
187
194
|
end
|
|
188
195
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
196
|
+
# A lease the caller already holds is reused and kept; otherwise one is
|
|
197
|
+
# taken for the block and returned at its end (issue #649), so the
|
|
198
|
+
# executor's bookkeeping leaves no connection pinned to the job's
|
|
199
|
+
# thread or fiber. BusRecord's pool is the pgbus database's under
|
|
200
|
+
# connects_to and the primary's otherwise; asking first whether it had
|
|
201
|
+
# connected sent a cold process's first failure to the primary.
|
|
202
|
+
def with_connection(&)
|
|
203
|
+
BusRecord.connection_pool.with_connection(&)
|
|
195
204
|
end
|
|
196
205
|
end
|
|
197
206
|
|
data/lib/pgbus/version.rb
CHANGED