pgbus 0.21.2 → 0.21.3
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 +2 -0
- data/README.md +3 -2
- data/lib/pgbus/configuration.rb +27 -0
- data/lib/pgbus/mcp/health_analyzer.rb +40 -4
- data/lib/pgbus/mcp/tools/health_tool.rb +4 -2
- data/lib/pgbus/version.rb +1 -1
- data/lib/pgbus/web/data_source.rb +8 -3
- data/sig/pgbus/configuration.rbs +2 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: afa142ad44c901764a77528d7e387f708321353b5323c6dafcf23577cd27556e
|
|
4
|
+
data.tar.gz: 3bdcb708e9547c70115ccc9babd430d038a8b0ffb483faf615842a5a96cbbdff
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3204bb1c094c7765ca32942b76857774e6696070b3b1787057b06da5f185d55de3069397bb1deb1dbf7221daf22eb064dddcb7a7906471e40fddafd5cf14cb38
|
|
7
|
+
data.tar.gz: 28c7d2ff21d9c8f97a2db27694c1e21184c9549d5af9cbebbef7244e3690d14b13aa546f79d006eb14104189ca3246d1416a6bb1679d59cfe80353d59511e6e5
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
- **`config.boot_timeout` (default 120 seconds): the supervisor kills a child that never checks in (issue #587).** A fork checks in when it registers its `pgbus_processes` row on this host or writes its first liveness byte. One that has done neither `boot_timeout` seconds after the fork is logged at ERROR, sent `TTIN`, given 2 seconds, then SIGKILLed, and the normal restart path forks it again. A boot-deadline kill always counts as a crash, whatever the child's uptime, so a boot that hangs every time is re-forked with the crash-loop backoff (1 s doubling to 60 s) instead of every `boot_timeout`. Applies to workers, consumers, the dispatcher, the scheduler and the outbox poller. `nil` turns it off; anything else must be a positive number, or `validate!` raises `ConfigurationError`. When the watchdog's read of the process table fails, the boot deadline waits for a pass that can read it. Refs #587.
|
|
6
6
|
- **A process whose registration in `pgbus_processes` failed at start retries it on each heartbeat (issue #587).** It used to stay without a row for good after one failed `create!` (a database blip at boot); a dispatcher, scheduler or outbox poller in that state would now be killed by the boot deadline even though it runs. Refs #587.
|
|
7
7
|
- **`kill -TTIN <child pid>` logs every thread of a pgbus child with its backtrace at ERROR; the child keeps running (issue #587).** The watchdog sends it before every SIGKILL (boot timeout or stalled loop), so a hang is diagnosable from the log. Best effort: a child blocked in native code holding the GVL does not answer. Refs #587.
|
|
8
|
+
- **`config.health_unclaimed_grace` (default 60 seconds) and the per-queue metric `oldest_unread_claimable_age_sec` (issue #589).** The grace is how long a visible, never-read (`read_ct = 0`) message must have been claimable before the health verdict counts it as "never claimed while draining processes are alive". It takes a number of seconds or an `ActiveSupport::Duration`; `0` restores the old rule (any such message counts), and `validate!` raises `ConfigurationError` on nil, a negative or a non-finite value. `Web::DataSource#queues_with_metrics` and `#queue_detail` report `oldest_unread_claimable_age_sec`, the seconds since the oldest visible `read_ct = 0` message became claimable (`min(vt)`, so a delayed job's age starts when its wait expires and a retried message is left out), nil when there is none. The verdict's `summary` carries it as the oldest over the drained queues, and the STALLED reason names it (`never claimed, oldest claimable for 74s`). `Pgbus::MCP::HealthAnalyzer.new` takes an optional `grace:`. Refs #589.
|
|
8
9
|
- **The supervisor's standalone `/readyz` body carries `forked` (issue #587).** `forked` is the fork-table size (what `live` used to be); `live` now counts the children that have checked in. `Pgbus::Process::ReadinessSnapshot` gains `forked:`, defaulting to `live`. Refs #587.
|
|
9
10
|
|
|
10
11
|
### Changed
|
|
@@ -13,6 +14,7 @@
|
|
|
13
14
|
|
|
14
15
|
### Fixed
|
|
15
16
|
|
|
17
|
+
- **`/readyz` and `pgbus_health` no longer flap to `STALLED` on a healthy fleet (issue #589).** The verdict reported `STALLED` (and the mounted `/readyz` 503) whenever a drained queue held one visible, never-read message while a worker or consumer was alive, however briefly it had been claimable: every enqueue and every delayed job whose wait just expired is such a message until a worker's next poll, so a busy install answered 503 on a large share of checks. That branch now counts a queue only once its oldest such message has been claimable for `health_unclaimed_grace` seconds; a wedged worker still reads `STALLED` after the grace. A queue hash without the age (an older or stubbed data source) still counts at once. The worker-reported stalled branch (heart-beating, claim loop not advancing) is unchanged. Refs #589.
|
|
16
18
|
- **A child that hangs before its first liveness signal is killed and forked again, and `/readyz` no longer counts it as live (issue #587).** A fork stuck between `fork` and its first check-in (in an initializer, the eager load or a connection) never exited, so nothing reaped it, and the watchdog skipped it because no liveness channel had spoken; the container-local `/readyz` counted it as live because it was in the fork table, so a rolling deploy passed its health gate with a dead capsule. The boot deadline above now kills it, and `/readyz` stays at 503 `DEGRADED` (`live < expected`) until every child has checked in. A dispatcher, scheduler or outbox poller checks in through its process row, which the watchdog reads every 10 seconds, so `/readyz` turns 200 up to 10 seconds after the last one registers, and stays at 503 while the database is unreachable at boot (those children cannot be seen to be alive). The watchdog reads only this host's process rows last beaten since the fork, so a row another container wrote under the same pid, or one a SIGKILLed predecessor left behind (a container restarted in place reuses its hostname and pids), no longer vouches for a local child or hides a stalled loop. Refs #587.
|
|
17
19
|
- **A failed job's retry backoff applies under priority routing (issue #582).** With `priority_levels > 1` the executor set the backoff's visibility timeout on the logical queue's base table, which priority routing never creates; the call raised, was logged at debug, and the job came back after the read's visibility timeout instead of its backoff delay. The backoff now goes to the `_pN` sub-queue the worker read the message from, as the visibility heartbeat and the archive already did. Refs #582.
|
|
18
20
|
- **Two priority sub-queues' failures with the same msg_id keep a row each (issue #583).** Each `_pN` sub-queue numbers its messages from its own sequence, and `pgbus_failed_events` was unique on the logical queue and the msg_id, so a second job failing with the same msg_id in another sub-queue overwrote the first one's error and kept its payload: the dashboard showed one failed job where two had failed, Retry and Discard acted on the first job's message and deleted the only row, and the first job's success cleared the second one's failure. Rows now record the physical table (`source_queue`) and are unique on `(queue_name, msg_id, source_queue)`. The executor, the event consumer and the worker's zombie check read, write and clear only the row of the table the message came from. On the Jobs page a failed row joins only its own table's message and shows as an orphan once that message is gone, Retry, Discard and Discard all look the message up in the table the row names, and Discard releases the job's uniqueness lock bounded by when that table's message was sent, not by a sibling sub-queue's. A row written before the migration has no `source_queue` and is treated as before: it can belong to any table of its queue. Refs #583.
|
data/README.md
CHANGED
|
@@ -1260,7 +1260,7 @@ All tools below are read-only — none mutates state, and there is no raw-SQL pa
|
|
|
1260
1260
|
|
|
1261
1261
|
| Tool | Purpose |
|
|
1262
1262
|
|------|---------|
|
|
1263
|
-
| `pgbus_health` | One-call verdict: `OK` / `DEGRADED` / `STALLED`. STALLED is the silent-worker-wedge signal
|
|
1263
|
+
| `pgbus_health` | One-call verdict: `OK` / `DEGRADED` / `STALLED`. STALLED is the silent-worker-wedge signal: a worker reports its claim loop stalled, or a visible backlog older than `health_unclaimed_grace` (default 60s) while workers heart-beat but don't claim. The summary carries `oldest_unread_claimable_age_sec`. Suitable for automated alerting. |
|
|
1264
1264
|
| `pgbus_queues` | All queues: depth, visible count, oldest-message age, paused state. |
|
|
1265
1265
|
| `pgbus_queue_detail` | Per-queue metrics + paused state + table health (dead tuples, bloat, vacuum age). |
|
|
1266
1266
|
| `pgbus_processes` | Every process with kind, pid, heartbeat age, and `healthy`/`stale`/`stalled` status. |
|
|
@@ -1348,7 +1348,7 @@ For the **HTTP** transport, point the client at the mounted URL with a streamabl
|
|
|
1348
1348
|
|
|
1349
1349
|
For orchestrators like Kubernetes, Pgbus exposes two HTTP probes: `/livez` (is the serving process up?) and `/readyz`. Readiness means different things in the two places the probes are served:
|
|
1350
1350
|
|
|
1351
|
-
- **Mounted in Rails** (`Pgbus::Web::HealthApp`): `/readyz` runs the cluster-wide `OK` / `DEGRADED` / `STALLED` verdict, same as the MCP `pgbus_health` tool — `STALLED` (
|
|
1351
|
+
- **Mounted in Rails** (`Pgbus::Web::HealthApp`): `/readyz` runs the cluster-wide `OK` / `DEGRADED` / `STALLED` verdict, same as the MCP `pgbus_health` tool — `STALLED` (a never-claimed backlog older than `health_unclaimed_grace` while workers heart-beat but don't claim) fails readiness.
|
|
1352
1352
|
- **Standalone from the supervisor** (`health_port`): `/readyz` is **container-local** — did *this* supervisor finish booting, and are all the children *it* forked alive? That is the signal a rolling deploy's health gate needs; the cluster verdict would let a brand-new container pass on the strength of the *old* container's workers.
|
|
1353
1353
|
|
|
1354
1354
|
| Path | Method | 200 | 503 | Touches DB |
|
|
@@ -2560,6 +2560,7 @@ Curated headline options for the README. The full operator reference (with types
|
|
|
2560
2560
|
| `statsd_port` | `8125` | StatsD UDP port (used when `metrics_backend = :statsd`) |
|
|
2561
2561
|
| `health_port` | `nil` | Port for standalone HTTP liveness/readiness probes served by the supervisor; nil disables |
|
|
2562
2562
|
| `health_bind` | `"127.0.0.1"` | Bind address for the standalone health server |
|
|
2563
|
+
| `health_unclaimed_grace` | `60` | Seconds a visible, never-read message must have been claimable before the health verdict reports it as never claimed (`STALLED`); `0` counts any such message |
|
|
2563
2564
|
| `pgmq_schema_mode` | `:auto` | PGMQ schema install mode (`:auto`, `:extension`, `:embedded`) |
|
|
2564
2565
|
| `doctor_on_boot` | `nil` | Run doctor inside the booting supervisor: `nil`/`false` = off, `:report` = log and boot, `:strict` = refuse to boot on a fatal check. Also via `--doctor` / `--doctor-strict`. |
|
|
2565
2566
|
| `eager_validation` | `true` | Run `Configuration#validate!` automatically after the `Pgbus.configure` block yields; an invalid value raises `Pgbus::ConfigurationError` at boot. Set `false` to suppress and validate manually. |
|
data/lib/pgbus/configuration.rb
CHANGED
|
@@ -241,6 +241,14 @@ module Pgbus
|
|
|
241
241
|
# plain TCPServer bound to `health_bind` (default localhost).
|
|
242
242
|
attr_accessor :health_port, :health_bind
|
|
243
243
|
|
|
244
|
+
# Seconds a visible, never-read (read_ct = 0) message must have been
|
|
245
|
+
# claimable before the health verdict counts it as "never claimed while
|
|
246
|
+
# draining processes are alive" (issue #589). Below it a worker's next
|
|
247
|
+
# poll is still coming, so a fresh enqueue or a delayed job whose wait just
|
|
248
|
+
# expired no longer reads STALLED. 0 restores the pre-0.22 rule (any
|
|
249
|
+
# visible read_ct = 0 row counts). Default 60.
|
|
250
|
+
attr_reader :health_unclaimed_grace
|
|
251
|
+
|
|
244
252
|
# Streams (turbo-rails replacement, SSE-based)
|
|
245
253
|
attr_accessor :streams_enabled, :streams_path, :streams_signed_name_secret,
|
|
246
254
|
:streams_default_retention, :streams_retention, :streams_heartbeat_interval, :streams_peer_close_interval,
|
|
@@ -419,6 +427,7 @@ module Pgbus
|
|
|
419
427
|
# HTTP health endpoints — nil port disables the standalone server.
|
|
420
428
|
@health_port = nil
|
|
421
429
|
@health_bind = "127.0.0.1"
|
|
430
|
+
@health_unclaimed_grace = 60
|
|
422
431
|
|
|
423
432
|
@streams_enabled = true
|
|
424
433
|
@streams_path = nil
|
|
@@ -951,6 +960,8 @@ module Pgbus
|
|
|
951
960
|
|
|
952
961
|
raise Pgbus::ConfigurationError, "health_bind must be a non-empty String" unless health_bind.is_a?(String) && !health_bind.empty?
|
|
953
962
|
|
|
963
|
+
validate_health_unclaimed_grace!
|
|
964
|
+
|
|
954
965
|
raise Pgbus::ConfigurationError, "statsd_host must be a non-empty String" unless statsd_host.is_a?(String) && !statsd_host.empty?
|
|
955
966
|
|
|
956
967
|
unless statsd_port.is_a?(Integer) && statsd_port.between?(1, 65_535)
|
|
@@ -1539,6 +1550,14 @@ module Pgbus
|
|
|
1539
1550
|
@recurring_execution_retention = coerce_duration!(value, :recurring_execution_retention)
|
|
1540
1551
|
end
|
|
1541
1552
|
|
|
1553
|
+
# Not coerce_duration!: 0 is valid here (the pre-0.22 rule), and nil is not
|
|
1554
|
+
# a "disabled" sentinel. A Duration becomes integer seconds; anything else
|
|
1555
|
+
# is kept for validate! to judge.
|
|
1556
|
+
def health_unclaimed_grace=(value)
|
|
1557
|
+
duration = defined?(ActiveSupport::Duration) && value.is_a?(ActiveSupport::Duration)
|
|
1558
|
+
@health_unclaimed_grace = duration ? value.to_i : value
|
|
1559
|
+
end
|
|
1560
|
+
|
|
1542
1561
|
# recurring_tasks_file (singular) is deprecated in favor of the plural
|
|
1543
1562
|
# recurring_tasks_files. The engine still writes the singular internally to
|
|
1544
1563
|
# record which default file was loaded, so it stays a plain accessor; the
|
|
@@ -1867,6 +1886,14 @@ module Pgbus
|
|
|
1867
1886
|
raise Pgbus::ConfigurationError, "immediate_shutdown_grace must be a finite number >= 0"
|
|
1868
1887
|
end
|
|
1869
1888
|
|
|
1889
|
+
# A finite real number >= 0; nil is not "off" (0 is).
|
|
1890
|
+
def validate_health_unclaimed_grace!
|
|
1891
|
+
grace = health_unclaimed_grace
|
|
1892
|
+
return if grace.is_a?(Numeric) && grace.real? && grace.finite? && grace >= 0
|
|
1893
|
+
|
|
1894
|
+
raise Pgbus::ConfigurationError, "health_unclaimed_grace must be a finite number of seconds >= 0, got #{grace.inspect}"
|
|
1895
|
+
end
|
|
1896
|
+
|
|
1870
1897
|
def validate_positive_duration!(numeric, name)
|
|
1871
1898
|
raise Pgbus::ConfigurationError, "#{name} must be a positive number, got #{numeric}" unless numeric.positive?
|
|
1872
1899
|
|
|
@@ -35,8 +35,12 @@ module Pgbus
|
|
|
35
35
|
# a multi-KB log line (issue #367 minor).
|
|
36
36
|
NAME_LIMIT = 10
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
# +grace+ overrides Pgbus.configuration.health_unclaimed_grace (issue #589):
|
|
39
|
+
# seconds a visible read_ct=0 message must have been claimable before it
|
|
40
|
+
# counts as "never claimed". nil reads the config when the verdict runs.
|
|
41
|
+
def initialize(data_source, grace: nil)
|
|
39
42
|
@data_source = data_source
|
|
43
|
+
@grace = grace
|
|
40
44
|
end
|
|
41
45
|
|
|
42
46
|
# Returns a machine-readable verdict hash suitable for both interactive
|
|
@@ -81,7 +85,9 @@ module Pgbus
|
|
|
81
85
|
status: status,
|
|
82
86
|
reasons: stalled + degraded,
|
|
83
87
|
checked_at: Time.now.utc.iso8601,
|
|
84
|
-
summary: build_summary(queues, non_dlq, processes, health)
|
|
88
|
+
summary: build_summary(queues, non_dlq, processes, health).merge(
|
|
89
|
+
oldest_unread_claimable_age_sec: oldest_unread_age(drained)
|
|
90
|
+
)
|
|
85
91
|
}
|
|
86
92
|
end
|
|
87
93
|
|
|
@@ -139,7 +145,8 @@ module Pgbus
|
|
|
139
145
|
"while #{active_backlog.size} queue(s) have visible backlog: #{backlog_names(active_backlog)}"
|
|
140
146
|
elsif live_drainers.any? && drained_backlog.any? && any_unread?(drained_backlog)
|
|
141
147
|
unread = drained_backlog.select { |q| unread?(q) }
|
|
142
|
-
reasons << "#{unread.size} queue(s) have visible messages with read_ct=0
|
|
148
|
+
reasons << "#{unread.size} queue(s) have visible messages with read_ct=0 " \
|
|
149
|
+
"(never claimed#{unread_age_clause(unread)}) " \
|
|
143
150
|
"while #{live_drainers.size} draining process(es) are alive: #{backlog_names(unread)}"
|
|
144
151
|
end
|
|
145
152
|
reasons
|
|
@@ -211,10 +218,39 @@ module Pgbus
|
|
|
211
218
|
#
|
|
212
219
|
# When metrics don't expose the count (an old hash / a stubbed test) we
|
|
213
220
|
# conservatively treat the queue as unread so we never miss the wedge.
|
|
221
|
+
#
|
|
222
|
+
# Issue #589: the oldest such row must also have been claimable for at
|
|
223
|
+
# least the grace. A fresh enqueue, or a delayed job whose wait just
|
|
224
|
+
# expired, is visible with read_ct=0 until a worker's next poll, so
|
|
225
|
+
# without the grace a healthy fleet read STALLED (and /readyz 503) on a
|
|
226
|
+
# large share of checks. An unknown age (key missing or nil) stays
|
|
227
|
+
# conservative and counts.
|
|
214
228
|
def unread?(queue)
|
|
215
229
|
return true unless queue.key?(:visible_unread_length)
|
|
230
|
+
return false unless queue[:visible_unread_length].to_i.positive?
|
|
231
|
+
|
|
232
|
+
age = unread_age(queue)
|
|
233
|
+
age.nil? || age >= grace
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def grace
|
|
237
|
+
@grace || Pgbus.configuration.health_unclaimed_grace
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def unread_age(queue)
|
|
241
|
+
queue[:oldest_unread_claimable_age_sec]
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# Oldest known unread claimable age over +queues+, nil when none is known.
|
|
245
|
+
def oldest_unread_age(queues)
|
|
246
|
+
queues.filter_map { |q| unread_age(q) }.max
|
|
247
|
+
end
|
|
216
248
|
|
|
217
|
-
|
|
249
|
+
# ", oldest claimable for 74s" so an operator can tell a wedge from a
|
|
250
|
+
# spike; empty when no flagged queue reports an age.
|
|
251
|
+
def unread_age_clause(queues)
|
|
252
|
+
age = oldest_unread_age(queues)
|
|
253
|
+
age ? ", oldest claimable for #{age}s" : ""
|
|
218
254
|
end
|
|
219
255
|
|
|
220
256
|
# Join backlog queue names for a reason string, truncated so a flagged
|
|
@@ -13,8 +13,10 @@ module Pgbus
|
|
|
13
13
|
description <<~DESC
|
|
14
14
|
Top-level pgbus health verdict. Returns OK, DEGRADED, or STALLED with
|
|
15
15
|
the specific reasons. STALLED means the silent-wedge condition:
|
|
16
|
-
queues
|
|
17
|
-
|
|
16
|
+
queues hold a visible, never-claimed backlog older than
|
|
17
|
+
health_unclaimed_grace (default 60s) while workers are heart-beating
|
|
18
|
+
but not claiming (or a worker's claim loop has stalled); the summary's
|
|
19
|
+
oldest_unread_claimable_age_sec tells a wedge from a spike. DEGRADED covers
|
|
18
20
|
stale processes, paused queues holding a backlog, dead-letter
|
|
19
21
|
buildup, or a long-running transaction pinning the MVCC horizon. Use
|
|
20
22
|
this first when triaging — it summarizes the whole system in one call
|
data/lib/pgbus/version.rb
CHANGED
|
@@ -1340,7 +1340,8 @@ module Pgbus
|
|
|
1340
1340
|
(SELECT CASE WHEN is_called THEN last_value ELSE 0 END FROM pgmq.#{seq_name}) AS total_messages,
|
|
1341
1341
|
(SELECT max(read_ct) FROM pgmq.#{qtable}) AS max_read_ct,
|
|
1342
1342
|
(SELECT count(*) FROM pgmq.#{qtable} WHERE vt <= NOW() AND read_ct = 0) AS visible_unread_length,
|
|
1343
|
-
(SELECT EXTRACT(epoch FROM (NOW() - min(vt)))::int FROM pgmq.#{qtable} WHERE vt <= NOW()) AS oldest_claimable_age_sec
|
|
1343
|
+
(SELECT EXTRACT(epoch FROM (NOW() - min(vt)))::int FROM pgmq.#{qtable} WHERE vt <= NOW()) AS oldest_claimable_age_sec,
|
|
1344
|
+
(SELECT EXTRACT(epoch FROM (NOW() - min(vt)))::int FROM pgmq.#{qtable} WHERE vt <= NOW() AND read_ct = 0) AS oldest_unread_claimable_age_sec
|
|
1344
1345
|
SQL
|
|
1345
1346
|
rescue StandardError => e
|
|
1346
1347
|
Pgbus.logger.debug { "[Pgbus::Web] Skipping queue metrics for #{name}: #{e.message}" }
|
|
@@ -1362,7 +1363,8 @@ module Pgbus
|
|
|
1362
1363
|
newest_msg_age_sec: row["newest_msg_age_sec"]&.to_i,
|
|
1363
1364
|
total_messages: row["total_messages"].to_i,
|
|
1364
1365
|
max_read_ct: row["max_read_ct"]&.to_i,
|
|
1365
|
-
visible_unread_length: row["visible_unread_length"].to_i
|
|
1366
|
+
visible_unread_length: row["visible_unread_length"].to_i,
|
|
1367
|
+
oldest_unread_claimable_age_sec: row["oldest_unread_claimable_age_sec"]&.to_i
|
|
1366
1368
|
}
|
|
1367
1369
|
end
|
|
1368
1370
|
rescue StandardError => e
|
|
@@ -1382,6 +1384,7 @@ module Pgbus
|
|
|
1382
1384
|
EXTRACT(epoch FROM (NOW() - max(enqueued_at)))::int AS newest_msg_age_sec,
|
|
1383
1385
|
EXTRACT(epoch FROM (NOW() - min(enqueued_at)))::int AS oldest_msg_age_sec,
|
|
1384
1386
|
EXTRACT(epoch FROM (NOW() - min(vt) FILTER (WHERE vt <= NOW())))::int AS oldest_claimable_age_sec,
|
|
1387
|
+
EXTRACT(epoch FROM (NOW() - min(vt) FILTER (WHERE vt <= NOW() AND read_ct = 0)))::int AS oldest_unread_claimable_age_sec,
|
|
1385
1388
|
max(read_ct) AS max_read_ct,
|
|
1386
1389
|
count(CASE WHEN vt <= NOW() AND read_ct = 0 THEN 1 END) AS visible_unread_length
|
|
1387
1390
|
FROM pgmq.#{qtable}
|
|
@@ -1398,6 +1401,7 @@ module Pgbus
|
|
|
1398
1401
|
q_summary.oldest_claimable_age_sec,
|
|
1399
1402
|
q_summary.max_read_ct,
|
|
1400
1403
|
q_summary.visible_unread_length,
|
|
1404
|
+
q_summary.oldest_unread_claimable_age_sec,
|
|
1401
1405
|
all_metrics.total_messages
|
|
1402
1406
|
FROM q_summary, all_metrics
|
|
1403
1407
|
SQL
|
|
@@ -1414,7 +1418,8 @@ module Pgbus
|
|
|
1414
1418
|
newest_msg_age_sec: row["newest_msg_age_sec"]&.to_i,
|
|
1415
1419
|
total_messages: row["total_messages"].to_i,
|
|
1416
1420
|
max_read_ct: row["max_read_ct"]&.to_i,
|
|
1417
|
-
visible_unread_length: row["visible_unread_length"].to_i
|
|
1421
|
+
visible_unread_length: row["visible_unread_length"].to_i,
|
|
1422
|
+
oldest_unread_claimable_age_sec: row["oldest_unread_claimable_age_sec"]&.to_i
|
|
1418
1423
|
}
|
|
1419
1424
|
rescue StandardError => e
|
|
1420
1425
|
Pgbus.logger.error { "[Pgbus::Web] Error fetching metrics for #{queue_name}: #{e.class}: #{e.message}" }
|
data/sig/pgbus/configuration.rbs
CHANGED
|
@@ -85,6 +85,8 @@ module Pgbus
|
|
|
85
85
|
def health_bind: () -> String
|
|
86
86
|
def health_bind=: (untyped) -> untyped
|
|
87
87
|
def health_port: () -> untyped
|
|
88
|
+
def health_unclaimed_grace: () -> untyped
|
|
89
|
+
def health_unclaimed_grace=: (untyped value) -> untyped
|
|
88
90
|
def health_port=: (untyped) -> untyped
|
|
89
91
|
def idempotency_ttl: () -> Numeric?
|
|
90
92
|
def idempotency_ttl=: (untyped value) -> untyped
|