pgbus 0.11.2 → 0.11.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 +15 -7
- data/lib/pgbus/cli/dashboard.rb +54 -0
- data/lib/pgbus/cli.rb +4 -0
- data/lib/pgbus/client.rb +13 -2
- data/lib/pgbus/integrations/appsignal/dashboard.json +10 -2
- data/lib/pgbus/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: df6f1cbda597599aaad006242fbb0b964edaa8fa599c413dfde949fc3b7e19a2
|
|
4
|
+
data.tar.gz: 4a57b939f12238bf4aac8fc601ee5cd529fd1311e6c6994dacfb55f925545e4b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 336b503f39a11252b461b98c124617d16f252a20e8ef343ea48d637d8552a6048ff8c0d9465d95fcb7c737616010da63669a8db3eacb9971d79b6ae9286ed432
|
|
7
|
+
data.tar.gz: 33d5b40c21ef5213f4014f27577e19c617ee0e71420dc3c3432c4e3d35f941cafbe0e6a41d33bac42cc9ec2eb65a159d3685f1cce28ff44e97a14ca9749134ab
|
data/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
### Added
|
|
12
12
|
|
|
13
|
+
- **`pgbus dashboard` — print the vendored AppSignal dashboards as import-ready JSON.** The gem ships four AppSignal dashboard definitions (the main one submitted to appsignal/public_config#80 plus the health/streams/throughput extras), but they're stored in the automated-dashboard format — a `metric_keys` trigger wrapping the `dashboard` object — which AppSignal's "Import dashboard" dialog doesn't accept, so trying one on a real app meant hand-unwrapping JSON. `pgbus dashboard [main|health|streams|throughput]` prints the inner dashboard object ready to paste into the import dialog (`pgbus dashboard health | pbcopy`); `pgbus dashboard --list` enumerates the available names and titles.
|
|
13
14
|
- **`config.doctor_on_boot` (+ `pgbus start --doctor` / `--doctor-strict`) — run the doctor preflight inside the booting supervisor, one Rails boot instead of two (issue #347).** The recommended deploy preflight was `bin/pgbus doctor || true` in the container entrypoint before `bin/pgbus start` — but both commands `require config/environment`, so the job container boots the full Rails app **twice** on every deploy (cost scaling with app boot time), and running doctor pre-supervisor makes the `Process liveness` check false-fail (no workers exist yet), which is exactly why the entrypoint has to swallow the exit code with `|| true` — losing any chance to gate on a genuinely-fatal finding. Now `config.doctor_on_boot = :report` (or `pgbus start --doctor`) runs the checks in the already-booted supervisor, after the DB is verified reachable and queues are bootstrapped but **before any worker is forked**: one boot, and the worker-dependent `Process liveness` check is skipped (it has no workers to observe yet and would false-fail on stale prior-generation rows during a redeploy). `config.doctor_on_boot = :strict` (or `--doctor-strict`) additionally **refuses to boot** — raising before forking anything, so the `ensure shutdown` path tears down the heartbeat/health server and no child starts — but *only* on a genuinely-fatal, non-transient check: a `Configuration` failure (a real config bug) or an **absent** PGMQ schema. It deliberately does **not** abort on `Queues`/`Database` failures — those are the transients the lenient queue bootstrap is built to ride out (children crash-and-backoff until the DB recovers), and `verify_connection!` already gated a hard-down DB moments earlier; making them strict-fatal would take down a whole fleet's cold boot in lockstep on a momentarily-saturated primary. Default `nil`/`false` = off (byte-identical to today; the supervisor constructs no `Doctor` and does zero extra work). The standalone `pgbus doctor` command and its exit-code semantics are unchanged. Refs #347.
|
|
14
15
|
- **`Pgbus::Streams::PhlexHelpers` — a Phlex-includable `pgbus_stream_from`, and `Pgbus.stream_name_budget` (issue #334).** Two small helpers that stop consuming apps reimplementing pgbus glue. (1) `pgbus_stream_from` is a Rails view helper, so a Phlex component couldn't call it without hand-registering an output-helper macro (phlex-rails ships no `Phlex::Rails::Helpers::PgbusStreamFrom`). `include Pgbus::Streams::PhlexHelpers` now bridges it exactly like phlex-rails' own `TurboStreamFrom`. phlex-rails stays an **optional** dependency — the module references its `HelperMacros` and is loaded on demand (`require "pgbus/streams/phlex_helpers"`), never eagerly. (2) `Pgbus.stream_name_budget` exposes the maximum stream-name length (the pgmq queue-name cap minus the queue prefix) so apps size/truncate stream identifiers up front instead of hand-computing `MAX_QUEUE_NAME_LENGTH - queue_prefix.length - 1` at every call site. Refs #334.
|
|
15
16
|
- **`EventBus::Registry#setup_all!(safe: true)` — a boot/rake-safe way to eagerly set up event subscribers (issue #334).** Calling `setup_all!` opens a PGMQ connection per subscriber (to create its queue + bind its topic), which is wrong during a `db:`/schema rake task (a live connection blocks `DROP DATABASE`) and crashes boot if the database isn't up yet — so apps wrapped it in a hand-rolled multi-class rescue plus a rake-task skip. `setup_all!(safe: true)` bakes that in: it skips entirely in a schema/asset rake context and swallows a connection error with a warning instead of raising. The default `setup_all!` is unchanged (raises). Refs #334.
|
|
@@ -33,6 +34,7 @@
|
|
|
33
34
|
|
|
34
35
|
### Fixed
|
|
35
36
|
|
|
37
|
+
- **AppSignal main dashboard: job and event lines now split by `job_class` and `routing_key`.** Applied the upstream review suggestions committed on appsignal/public_config#80 to the vendored `dashboard.json`: the "Job status per queue" panel gains a `job_class` wildcard tag filter and label (`%job_class% - %queue% - %status%`), and "Event handler status" gains `routing_key` (`%handler% - %status% - %routing_key%`). Without the wildcard tag entry a `%placeholder%` in the line label never resolves, so lines from different job classes / routing keys were collapsed together. The subscriber already emitted both tags; only the dashboard definition lagged.
|
|
36
38
|
- **Migration generators now route to the pgbus database when `connects_to` is configured, even without an explicit `--database` (issue #344).** `rails g pgbus:add_stream_queues` (and every sibling generator via `Generators::MigrationPath`) keyed the separate-database decision purely on the `--database` flag. An app that had already set `config.connects_to = { database: { writing: :pgbus } }` (so its pgbus migrations live in `db/pgbus_migrate/` per database.yml's `migrations_paths:`) still got the migration written to the **primary** DB's `db/migrate/` on a bare invocation — where it then ran against the wrong database — and the "Next steps" output told the operator to run `rails db:migrate` instead of `rails db:migrate:pgbus`. `MigrationPath` now auto-detects the target database from `connects_to` (via the existing `DatabaseTargetDetector`: runtime config → initializer scan → application.rb scan) when `--database` is absent; an explicit `--database` still wins. The detected name drives both the migration path (resolving `migrations_paths` for that database, falling back to the `db/pgbus_migrate` convention) and the `db:migrate:<name>` suffix in every generator's post-install output. An app with no separate database is unaffected. Refs #344.
|
|
37
39
|
- **`extract_ar_connection_hash` no longer forces `host: "localhost"` / `port: 5432` on a socket-based (host-less) database.yml — pgmq's connections now match ActiveRecord's on Unix-socket dev setups (issue #343).** A local database.yml with no `host:` is a Unix-socket connection: ActiveRecord connects via libpq's default socket (`PGHOST` / the default socket dir), but pgbus's AR-config extraction defaulted the absent `host`/`port` to TCP `localhost:5432`, silently diverging from AR and pointing pgmq's dedicated raw connections at a *different* server (or nothing) on any machine where the socket dir isn't `localhost`. Apps on socket-based dev DBs therefore couldn't use the AR-extraction path at all and had to pin an explicit `connection_params` Hash whose only real job was *not* defaulting `host` (cosmos carried an `after_initialize` block for exactly this). The two fallbacks are dropped: `host`/`port` now pass through as-is and `.compact` removes them when absent, so libpq applies its own socket defaults and matches AR. A config that *does* set `host`/`port` is byte-identical to before. Refs #343.
|
|
38
40
|
- **`config.log_format` no longer breaks `ActiveSupport::TaggedLogging` — tags are preserved instead of dropped, and `logger.tagged` no longer raises (issue #334).** TaggedLogging works by extending the logger's formatter instance with a module that prepends the current tags. Installing a fresh pgbus formatter (`LogFormatter::JSON`/`Text`) stripped that extension, so `logger.tagged("req-42") { ... }` dropped the tag — and, worse, raised `NoMethodError: undefined method 'tagged'` on the pgbus formatter (a deploy-breaker several apps hit, then worked around with a dedicated `$stdout` logger). `log_format=` now re-extends the replacement formatter with `ActiveSupport::TaggedLogging::Formatter` when the previous one carried it, so tagging keeps working. Refs #334.
|
data/README.md
CHANGED
|
@@ -820,15 +820,23 @@ end
|
|
|
820
820
|
|
|
821
821
|
#### Dashboards
|
|
822
822
|
|
|
823
|
-
|
|
823
|
+
Four importable AppSignal dashboards ship with the gem:
|
|
824
824
|
|
|
825
|
-
| File | Purpose |
|
|
826
|
-
|
|
827
|
-
| `lib/pgbus/integrations/appsignal/
|
|
828
|
-
| `lib/pgbus/integrations/appsignal/dashboards/
|
|
829
|
-
| `lib/pgbus/integrations/appsignal/dashboards/
|
|
825
|
+
| Name | File | Purpose |
|
|
826
|
+
|------|------|---------|
|
|
827
|
+
| `main` | `lib/pgbus/integrations/appsignal/dashboard.json` | All-in-one overview (the automated dashboard submitted to appsignal/public_config) |
|
|
828
|
+
| `throughput` | `lib/pgbus/integrations/appsignal/dashboards/pgbus_throughput.json` | Jobs/sec, perform-duration percentiles, send/read counts |
|
|
829
|
+
| `health` | `lib/pgbus/integrations/appsignal/dashboards/pgbus_health.json` | Queue depth, oldest message age, DLQ, dead tuples, MVCC horizon, worker recycles |
|
|
830
|
+
| `streams` | `lib/pgbus/integrations/appsignal/dashboards/pgbus_streams.json` | Broadcasts, fanout, active SSE connections, outbox, recurring tasks |
|
|
831
|
+
|
|
832
|
+
The files are stored in AppSignal's automated-dashboard format (a `metric_keys` trigger wrapping
|
|
833
|
+
the dashboard object); `pgbus dashboard <name>` unwraps one into the JSON that AppSignal's
|
|
834
|
+
"New dashboard" → "Import dashboard" dialog accepts:
|
|
830
835
|
|
|
831
|
-
|
|
836
|
+
```bash
|
|
837
|
+
pgbus dashboard --list # available names and titles
|
|
838
|
+
pgbus dashboard health | pbcopy # paste into the import dialog
|
|
839
|
+
```
|
|
832
840
|
|
|
833
841
|
### Metrics adapter (Prometheus / StatsD)
|
|
834
842
|
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Pgbus
|
|
6
|
+
module CLI
|
|
7
|
+
# Prints the vendored AppSignal dashboard definitions as import-ready
|
|
8
|
+
# JSON. The files ship in the automated-dashboard format used by
|
|
9
|
+
# appsignal/public_config (a `metric_keys` trigger wrapping a
|
|
10
|
+
# `dashboard` object); AppSignal's "Import dashboard" dialog wants the
|
|
11
|
+
# inner object only, so this command unwraps it:
|
|
12
|
+
#
|
|
13
|
+
# pgbus dashboard # main dashboard
|
|
14
|
+
# pgbus dashboard health # one of the extra dashboards
|
|
15
|
+
# pgbus dashboard --list # available names and titles
|
|
16
|
+
module Dashboard
|
|
17
|
+
module_function
|
|
18
|
+
|
|
19
|
+
def start(args)
|
|
20
|
+
name = args.first || "main"
|
|
21
|
+
return list if ["--list", "list"].include?(name)
|
|
22
|
+
|
|
23
|
+
path = available[name]
|
|
24
|
+
unless path
|
|
25
|
+
warn "Unknown dashboard: #{name.inspect} (available: #{available.keys.join(", ")})"
|
|
26
|
+
exit 1
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
puts JSON.pretty_generate(definition(path))
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def list
|
|
33
|
+
available.each do |name, path|
|
|
34
|
+
puts format("%-12s %s", name, definition(path)["title"])
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# The main dashboard plus the extras, keyed by their short CLI name
|
|
39
|
+
# (pgbus_health.json => "health").
|
|
40
|
+
def available
|
|
41
|
+
require "pgbus/integrations/appsignal"
|
|
42
|
+
|
|
43
|
+
extras = Dir[File.join(Integrations::Appsignal::DASHBOARDS_DIR, "*.json")].to_h do |path|
|
|
44
|
+
[File.basename(path, ".json").delete_prefix("pgbus_"), path]
|
|
45
|
+
end
|
|
46
|
+
{ "main" => Integrations::Appsignal::DASHBOARD_PATH }.merge(extras)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def definition(path)
|
|
50
|
+
JSON.parse(File.read(path)).fetch("dashboard")
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
data/lib/pgbus/cli.rb
CHANGED
|
@@ -18,6 +18,8 @@ module Pgbus
|
|
|
18
18
|
list_queues
|
|
19
19
|
when "dlq"
|
|
20
20
|
DLQ.start(args[1..] || [])
|
|
21
|
+
when "dashboard"
|
|
22
|
+
Dashboard.start(args[1..] || [])
|
|
21
23
|
when "mcp"
|
|
22
24
|
start_mcp_server
|
|
23
25
|
when "doctor"
|
|
@@ -196,6 +198,8 @@ module Pgbus
|
|
|
196
198
|
queues List queues with metrics
|
|
197
199
|
dlq Inspect and drain dead-letter queues
|
|
198
200
|
(list/show/retry/retry-all/purge)
|
|
201
|
+
dashboard Print an AppSignal dashboard as import-ready JSON
|
|
202
|
+
(main/health/streams/throughput; --list to enumerate)
|
|
199
203
|
mcp Start the read-only MCP diagnostic server over stdio
|
|
200
204
|
doctor Run environment diagnostics (config, DB, PGMQ, queues,
|
|
201
205
|
LISTEN/NOTIFY, process liveness); exits 1 on any failure
|
data/lib/pgbus/client.rb
CHANGED
|
@@ -1099,6 +1099,14 @@ module Pgbus
|
|
|
1099
1099
|
].freeze
|
|
1100
1100
|
private_constant :STALE_CONNECTION_PATTERNS
|
|
1101
1101
|
|
|
1102
|
+
# Compiled once: matches an error message containing any stale-connection
|
|
1103
|
+
# pattern as a substring, case-insensitively (replacing the previous
|
|
1104
|
+
# `message.downcase` + substring scan). Regexp.union escapes the literal
|
|
1105
|
+
# parens in "pqsocket() ...", so they match literally.
|
|
1106
|
+
STALE_CONNECTION_PATTERN =
|
|
1107
|
+
Regexp.new(Regexp.union(STALE_CONNECTION_PATTERNS).source, Regexp::IGNORECASE).freeze
|
|
1108
|
+
private_constant :STALE_CONNECTION_PATTERN
|
|
1109
|
+
|
|
1102
1110
|
# How many times a matched stale-connection error is retried before it
|
|
1103
1111
|
# propagates. Two attempts (not one) so a transient window — a PgBouncer
|
|
1104
1112
|
# restart or a brief failover — that outlasts the first immediate retry
|
|
@@ -1482,8 +1490,11 @@ module Pgbus
|
|
|
1482
1490
|
end
|
|
1483
1491
|
|
|
1484
1492
|
def stale_connection_error?(error)
|
|
1485
|
-
|
|
1486
|
-
STALE_CONNECTION_PATTERNS.any? { |
|
|
1493
|
+
# Substring match against any stale-connection pattern. Deliberately a
|
|
1494
|
+
# single Regexp rather than `STALE_CONNECTION_PATTERNS.any? { |p| msg.include?(p) }`:
|
|
1495
|
+
# Style/ArrayIntersect misfires on that shape and "corrects" it to
|
|
1496
|
+
# `patterns.intersect?(msg)`, which raises TypeError (String isn't an Array).
|
|
1497
|
+
STALE_CONNECTION_PATTERN.match?(error.message.to_s)
|
|
1487
1498
|
end
|
|
1488
1499
|
|
|
1489
1500
|
# Substring pgmq-ruby uses when a pool checkout times out — a
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"display": "LINE",
|
|
12
12
|
"title": "Job status per queue",
|
|
13
13
|
"description": "Processed, failed, and dead-lettered jobs per queue.",
|
|
14
|
-
"line_label": "%queue% - %status%",
|
|
14
|
+
"line_label": "%job_class% - %queue% - %status%",
|
|
15
15
|
"format": "number",
|
|
16
16
|
"draw_null_as_zero": true,
|
|
17
17
|
"metrics": [
|
|
@@ -23,6 +23,10 @@
|
|
|
23
23
|
}
|
|
24
24
|
],
|
|
25
25
|
"tags": [
|
|
26
|
+
{
|
|
27
|
+
"key": "job_class",
|
|
28
|
+
"value": "*"
|
|
29
|
+
},
|
|
26
30
|
{
|
|
27
31
|
"key": "queue",
|
|
28
32
|
"value": "*"
|
|
@@ -232,7 +236,7 @@
|
|
|
232
236
|
"display": "LINE",
|
|
233
237
|
"title": "Event handler status",
|
|
234
238
|
"description": "Processed and failed event handler invocations.",
|
|
235
|
-
"line_label": "%handler% - %status%",
|
|
239
|
+
"line_label": "%handler% - %status% - %routing_key%",
|
|
236
240
|
"format": "number",
|
|
237
241
|
"draw_null_as_zero": true,
|
|
238
242
|
"metrics": [
|
|
@@ -251,6 +255,10 @@
|
|
|
251
255
|
{
|
|
252
256
|
"key": "status",
|
|
253
257
|
"value": "*"
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
"key": "routing_key",
|
|
261
|
+
"value": "*"
|
|
254
262
|
}
|
|
255
263
|
]
|
|
256
264
|
}
|
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.11.
|
|
4
|
+
version: 0.11.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mikael Henriksson
|
|
@@ -253,6 +253,7 @@ files:
|
|
|
253
253
|
- lib/pgbus/bus_record.rb
|
|
254
254
|
- lib/pgbus/circuit_breaker.rb
|
|
255
255
|
- lib/pgbus/cli.rb
|
|
256
|
+
- lib/pgbus/cli/dashboard.rb
|
|
256
257
|
- lib/pgbus/cli/dlq.rb
|
|
257
258
|
- lib/pgbus/client.rb
|
|
258
259
|
- lib/pgbus/client/connection_health.rb
|