Upgrading pgbus
The standard upgrade procedure, plus what changes on each hop from 0.9.7 to 1.0.0.
Overview#
Every pgbus upgrade — patch, minor, or major — follows the same six-step procedure: update the gem, review and apply the migration generator, migrate the database, check the vendored PGMQ schema, deploy, then verify with pgbus doctor. The steps below are generic; per-version sections list only what's different for that hop.
Work through the sections oldest first if you're behind by more than one release — each section assumes the previous one is done.
The standard upgrade procedure#
Every version, every hop.
- Update the gem
bundle update pgbus- Review the upgrade plan —
pgbus:updateinspects your live database and reports exactly which migrations are missing;--dry-runprints the plan without creating any files.
rails generate pgbus:update --dry-run- Apply it — drop
--dry-runto create the migration files. The generator auto-detects a separate database fromPgbus.configuration.connects_toor by scanning your initializer /config/application.rb— you don't need to pass--database=pgbusyourself unless auto-detection can't find it.
rails generate pgbus:update- Migrate the database — use the
:pgbusvariant if you run pgbus on a separate database.
rails db:migrate # single database
rails db:migrate:pgbus # separate-database install- Check the vendored PGMQ schema —
pgbus:updateonly handles pgbus's own tables; PGMQ's internal schema (thepgmq.*functions and types) is versioned separately and upgraded on its own generator.
rake pgbus:pgmq:statusIf it reports an update available, generate and run the upgrade migration:
rails generate pgbus:upgrade_pgmq
rails db:migrate- Deploy, then verify:
bundle exec pgbus doctordoctor runs seven checks — configuration validity, database connectivity, PGMQ schema version, queue existence, LISTEN/NOTIFY liveness, process liveness, and the GlobalID allowlist (a security warning when allowed_global_id_models is nil in production) — and exits non-zero on any failure, so it's safe to wire into a deploy gate or a post-deploy CI job.
0.9.x → 0.9.8#
Two behavior changes, one PGMQ schema bump.
Breaking: queue names must be alphanumeric + underscores
Queue names containing dashes (my-app-queue) now raise ArgumentError at boot. This closes a SQL-injection surface — PGMQ queue identifiers are interpolated into table names and can't be parameterized — but it means a dashed queue name that worked on 0.9.7 will crash on 0.9.8.
Rename any dashed queue names to underscored form before upgrading (my-app-queue → my_app_queue), in both your Pgbus.configure block and any code that references the queue name directly. There is no automatic migration for this — a queue is just a Postgres table name, so renaming means creating the new queue and draining the old one.
Breaking: configuration is now validated eagerly at boot
Pgbus.configure now calls Configuration#validate! automatically after your block runs. An invalid value — visibility_timeout = 0, for example — now raises ArgumentError at boot instead of surfacing later, far from the misconfiguration, the first time a worker touches that setting.
If you rely on a config that is transiently invalid between multiple sequential configure blocks, opt out with:
Pgbus.configure do |c|
c.eager_validation = false
endPGMQ vendored schema: 1.11.0 → 1.11.1
This hop moves the vendored PGMQ schema forward one patch version — a concrete example of step 5 in the standard procedure above. Run rake pgbus:pgmq:status after updating the gem; it will report installed 1.11.0, vendored 1.11.1 and tell you to run:
rails generate pgbus:upgrade_pgmq
rails db:migrateNew in 0.9.8 (all opt-in)
None of the following change existing behavior — each is a new, off-by-default capability:
| Feature | What it adds |
|---|---|
| Observability | metrics_backend — Prometheus/StatsD metrics without hand-writing subscribers. |
| Running workers | health_port / HTTP /livez and /readyz endpoints for orchestrators. |
pgbus dlq | CLI dead-letter management (list/show/retry/purge) without the dashboard. |
pgbus doctor | The single preflight command this guide uses to verify every upgrade. |
0.9.8 → 1.0.0#
The 1.0 API freeze: error hierarchy, config renames, and dead-surface removal.
The 1.0.0 commitment
1.0.0 marks pgbus's semver commitment: after 1.0.0, a breaking change to any documented public API bumps the major version. The 0.x series has made breaking changes in minor releases (see the 0.9.8 section above); 1.0.0 is where that stops. Everything below is surface the two API-freeze issues identified as needing to change before that commitment takes effect — either because it's a genuine correctness fix (the error hierarchy) or because it's free to rename now and expensive to rename after (config keys, dead code).
Breaking: unified error hierarchy (#282)
Every operational error pgbus raises now descends from Pgbus::Error, so rescue Pgbus::Error catches them all. Four call sites that previously raised bare stdlib errors changed:
| Raised before | Raises now | Where |
|---|---|---|
ArgumentError | Pgbus::ConfigurationError | Configuration#validate! and its setters |
RuntimeError | Pgbus::ExecutionPoolError | AsyncPool |
RuntimeError | Pgbus::EnqueueError | ActiveJob adapter (perform_all_later msg_id mismatch) |
ArgumentError | Pgbus::SerializationError | Serializer#locate_global_id |
Three error classes that bypassed Pgbus::Error entirely (PgmqSchema::VersionNotFoundError, Streams::SignedName::InvalidSignedName and MissingSecret) are now re-parented underneath it. Three classes that reject a malformed argument shape — CapsuleDSL::ParseError, Streams::Cursor::InvalidCursor, Streams::StreamNameTooLong — deliberately stay ArgumentError subclasses, since that's what ArgumentError means. The policy ("argument-shape errors are ArgumentError, operational errors are Pgbus::Error") is documented at the top of lib/pgbus.rb.
The one breaking change for existing code: if you rescue ArgumentError around Pgbus.configure or a boot-time config read, that rescue no longer catches config errors — Configuration#validate! and its setters now raise Pgbus::ConfigurationError, which is not an ArgumentError. Switch to:
begin
Pgbus.configure { |c| c.visibility_timeout = 0 }
rescue Pgbus::Error => e # was: rescue ArgumentError
Rails.logger.error("pgbus config invalid: #{e.message}")
raise
endError messages are unchanged, so any rescue ... => e that only reads e.message keeps working. Only the rescued class changed.
Config renames and dead-surface removal (#283)
Renames ship as a deprecated alias in 1.0.0 — the old name still works but logs a warning once — with removal in a future 2.0. Nothing breaks at 1.0.0 except surface confirmed to have zero real-world callers (verified in the API-freeze audit):
| Old | 1.0.0 | Path |
|---|---|---|
skip_recurring | Renamed to recurring_enabled (positive polarity — true means run). | Old name aliases (inverting the boolean) and warns once; removed in 2.0. |
dashboard_filter_parameters | Renamed to web_filter_parameters (unify on the web_ prefix). | Old name aliases and warns once; removed in 2.0. |
dashboard_filter_sensitive | Renamed to web_filter_sensitive. | Old name aliases and warns once; removed in 2.0. |
recurring_tasks_file | Deprecated in favor of recurring_tasks_files (plural). | Setting both now warns once (the singular was silently ignored before); a lone singular still works. |
lock_ttl: | Removed from ensures_uniqueness — validated but never read by anything. | Passing it raises ArgumentError naming the removal and this page. |
pgbus:add_job_locks | Generator removed; Pgbus::JobLock model removed (zero references). | No replacement — new installs use pgbus:add_uniqueness_keys; pgbus:migrate_job_locks still retires the legacy table. |
with_pgbus_durable | Removed (internal streams helper, zero callers). | Use with_pgbus_broadcast_opts(durable:). |
reconnect_via_reset | Removed — the streamer's conn.reset reconnect fallback (only test wiring reached it). | connection_factory is now required on Streamer::Listener and always injected; reconnect always rebuilds a fresh connection. |
New in 1.0.0:
Pgbus.publish/Pgbus.publish_later— top-level shortcuts forPgbus::EventBus::Publisher.publish/.publish_later, symmetric withPgbus.stream. The long form still works.config.drain_timeout(default 30s) replaces the hardcodedWorker::DRAIN_TIMEOUTconstant — raise it if your jobs legitimately run longer than the graceful-shutdown window.pgbus doctornow warns whenallowed_global_id_modelsisnil(allow-all) in production. The default is unchanged for upgrade continuity, but set an explicit allowlist — it is security-relevant.
log_format= no longer overwrites a custom logger's formatter. streams_presence_*, group_mode, and streams_falcon_streaming_body are marked experimental and are exempt from the 1.0 stability promise.