Class: Pgbus::Doctor

Inherits:
Object
  • Object
show all
Defined in:
lib/pgbus/doctor.rb

Overview

Preflight diagnostics for a pgbus deployment — the single command that answers "is this environment healthy enough to run?". Runs nine checks and returns a machine-readable result plus a human report, so pgbus doctor and rake pgbus:doctor can gate a deploy or CI run (exit 0 on success, 1 on any failure).

Doctor never touches PGMQ or PostgreSQL directly: every probe goes through Pgbus::Client (DB, PGMQ schema, queues, NOTIFY) or Pgbus::Web::DataSource (process liveness, via Pgbus::MCP::HealthAnalyzer). It also never raises — a broken environment turns into :fail results, never a crash — so it is safe to run against a database that is down or a half-installed schema.

Defined Under Namespace

Classes: Check

Constant Summary collapse

STATUS_ICON =
{ ok: "", warn: "!", fail: "" }.freeze
CHECKS =

The ordered check suite, name → method. The name strings are the public, stable identity of each check (used in the report and to select subsets).

{
  "Configuration" => :check_configuration,
  "Database" => :check_database,
  "PGMQ schema" => :check_pgmq_schema,
  "Queues" => :check_queues,
  "LISTEN/NOTIFY" => :check_notify,
  "Process liveness" => :check_processes,
  "GlobalID allowlist" => :check_allowed_global_id_models,
  "Broadcast queue" => :check_broadcast_queue,
  "Primary affinity" => :check_primary
}.freeze
BOOT_SKIP =

Process liveness reads the pgbus_processes table (via HealthAnalyzer), so it is only meaningful once workers have registered. The supervisor boot preflight (issue #347) runs BEFORE forking any worker, so it excludes this check — otherwise stale prior-generation worker rows plus a visible backlog would produce a false STALLED verdict on a redeploy.

["Process liveness"].freeze
STRICT_FATAL =

The subset of checks whose :fail is genuinely deploy-fatal AND not a transient the supervisor is designed to ride out. Only these abort a :strict boot. Configuration#validate! failing is a real config bug; an absent PGMQ schema means the migrations never ran. Deliberately NOT Queues/Database: the lenient queue bootstrap swallows a boot-time DB blip so children crash-and-backoff and recover, and verify_connection! already gated a hard-down DB before the preflight — making either strict-fatal would turn a tolerated transient into a fleet-wide lockstep boot abort.

["Configuration", "PGMQ schema"].freeze

Instance Method Summary collapse

Constructor Details

#initialize(config: Pgbus.configuration, client: Pgbus.client, data_source: nil) ⇒ Doctor

Returns a new instance of Doctor.



59
60
61
62
63
# File 'lib/pgbus/doctor.rb', line 59

def initialize(config: Pgbus.configuration, client: Pgbus.client, data_source: nil)
  @config = config
  @client = client
  @data_source = data_source
end

Instance Method Details

#boot_checksObject

The checks safe to run inside the booting supervisor before any worker is forked: everything except the worker-dependent process-liveness check. Runs a genuine SUBSET — check_processes is never invoked — so there is no pre-fork HealthAnalyzer/DataSource round-trip.



83
84
85
# File 'lib/pgbus/doctor.rb', line 83

def boot_checks
  @boot_checks ||= run_checks(CHECKS.keys - BOOT_SKIP)
end

#boot_ok?Boolean

True unless a strict-fatal check (see STRICT_FATAL) failed. Warnings and transient-shaped failures (Queues, Database) never block a :strict boot.

Returns:

  • (Boolean)


89
90
91
# File 'lib/pgbus/doctor.rb', line 89

def boot_ok?
  boot_checks.none? { |c| c[:status] == :fail && STRICT_FATAL.include?(c[:name]) }
end

#boot_reportObject

Human-readable report for the boot preflight — the boot_checks subset.



94
95
96
# File 'lib/pgbus/doctor.rb', line 94

def boot_report
  report(boot_checks)
end

#config_summaryObject

Resolved-config summary for the report. Password-bearing fields (database_url, connection_params) are redacted.



117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/pgbus/doctor.rb', line 117

def config_summary
  {
    queue_prefix: @config.queue_prefix,
    default_queue: @config.default_queue,
    pgmq_schema_mode: @config.pgmq_schema_mode,
    resolved_pool_size: safe { @config.resolved_pool_size },
    listen_notify: @config.listen_notify,
    roles: @config.roles || "all",
    capsules: capsule_summary,
    database_url: redacted_database_url,
    connection_params: redacted_connection_params
  }.compact
end

#report(checks = run) ⇒ Object

Human-readable report: one line per check, then a resolved-config summary with passwords redacted. Suitable for stdout in the CLI and rake task. Defaults to the full run; pass a filtered result array (e.g. boot_checks) to render a subset.



102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/pgbus/doctor.rb', line 102

def report(checks = run)
  lines = ["Pgbus Doctor", "=" * 40]
  checks.each do |check|
    icon = STATUS_ICON.fetch(check[:status], "?")
    lines << format("%<icon>s %<name>-22s %<detail>s", icon: icon, name: check[:name], detail: check[:detail])
  end
  lines << ""
  lines << "Configuration"
  lines << ("-" * 40)
  config_summary.each { |key, value| lines << format("  %<key>-20s %<value>s", key: key, value: value) }
  lines.join("\n")
end

#runObject

Run all checks and return an array of result hashes:

{ name:, status: :ok|:warn|:fail, detail: }


67
68
69
# File 'lib/pgbus/doctor.rb', line 67

def run
  @run ||= run_checks(CHECKS.keys)
end

#success?Boolean

True when no check failed. Warnings do not fail the run — they surface a concern (e.g. an outdated PGMQ schema) without blocking a deploy.

Returns:

  • (Boolean)


73
74
75
# File 'lib/pgbus/doctor.rb', line 73

def success?
  run.none? { |c| c[:status] == :fail }
end