Skip to content

feat: railway postgres pitr/ha/pgbouncer — CLI parity for Postgres plugin management - #1032

Draft
paulocsanz wants to merge 8 commits into
masterfrom
pcs/pg-mgmt-cli
Draft

feat: railway postgres pitr/ha/pgbouncer — CLI parity for Postgres plugin management#1032
paulocsanz wants to merge 8 commits into
masterfrom
pcs/pg-mgmt-cli

Conversation

@paulocsanz

Copy link
Copy Markdown
Collaborator

Summary

Adds railway postgres {pitr,ha,pgbouncer} — CLI parity for the three biggest Postgres-plugin features, which today are frontend-only: point-in-time recovery / continuous backups, high-availability clustering, and PgBouncer connection pooling.

  • railway postgres pitrstatus, enable/disable (standalone via template overlay, or HA-aware via dedicated enablePitrForHaCluster/disablePitrForHaCluster + progress/cancel/clear), restore --at <time> (point-in-time restore to a new service), backup {list,create,delete,lock,restore,trigger}, schedule {set,list}.
  • railway postgres hastatus (topology + live Patroni role/state/lag), convert/revert, scale --replicas/--coordinators/--edge (creates/deletes cluster member services and restamps consensus wiring), switchover/promote.
  • railway postgres pgbouncerstatus (config + live SHOW POOLS/STATS/SERVERS), add/remove, configure (pool mode/knobs with advisory warnings), scale.

All enable/disable/convert/revert/add/remove actions auto-deploy by default (--no-deploy to only stage, matching the underlying templateDeployV2/templateRevert mechanism's staging behavior). Destructive actions confirm interactively, with --yes for automation.

Notable pieces

  • src/gql/schema.json was significantly stale relative to the live backend — regenerated from the current backboard SDL (offline conversion, not live introspection) as a prerequisite. Picked up one unrelated real schema change along the way (SandboxCreateInput.region).
  • src/controllers/template_apply.rs ports the frontend's template-config adjustment logic (useApplyComposableTemplate.tsx) so replica/coordinator/edge counts and edge-variable overrides are actually respected on enable/convert, not just the template's authored defaults.
  • src/controllers/cluster_scale.rs / src/controllers/patroni.rs are net-new: live cluster member create/delete + wiring restamp, and Patroni REST calls over the existing SSH exec-in-container primitive (promoted from db_stats into src/controllers/exec.rs) since there's no GraphQL mutation for switchover or live node status.
  • Live status probes (Patroni /cluster, PgBouncer SHOW POOLS, pgbackrest info) are all best-effort with short timeouts — status still prints config-only data if a probe is unreachable.

Test plan

  • cargo build clean
  • cargo test --bin railway — 607 passed, 0 failed
  • cargo clippy --bin railway -- -D warnings — clean on every file this PR touches (confirmed the remaining pre-existing findings are in files untouched by this diff)
  • cargo fmt --check clean
  • --help renders correctly at every level (postgres, postgres {pitr,ha,pgbouncer}, and leaf subcommands)
  • Manual end-to-end pass against a real dev/staging Postgres service (standalone and HA), not yet run — opening as draft pending that pass

Paulo Cabral Sanz added 8 commits July 28, 2026 15:32
src/gql/schema.json was stale relative to the live backend (missing
templateRevert, enablePitrForHaCluster/disablePitrForHaCluster,
pitrHaWorkflowProgress, pitrHaClusterReplicationHealth, groupSet, and
dozens more mutations/queries needed to build Postgres PITR/HA/PgBouncer
CLI support). Regenerated by converting the checked-in backboard SDL to
graphql_client's introspection-JSON shape instead of live-introspecting
prod, so it's reproducible offline and stays in sync with mono at build
time.

Picks up one real schema change along the way: SandboxCreateInput grew a
required `region` field since the snapshot was last refreshed.
…nd tree

Phase A of Postgres plugin CLI parity: shared infrastructure, all GraphQL
documents the feature will ever need, and the full command surface with
real handlers for the thin template-apply/revert wrappers (enable, disable,
convert, revert, add, remove, cancel, clear) and config-derived status.
Feature-specific live-probe and backup/schedule verbs are stubbed with
real flag parsing and a `not yet implemented` body, to be filled in by a
later phase without touching the shared GraphQL registration files again.

- ServiceInstance.cluster_role field (environment.rs)
- controllers/postgres_plugins.rs: PITR/HA/PgBouncer topology + guardrail detection
- controllers/template_apply.rs: templateDeployV2/templateRevert wrapper,
  porting the frontend's replica/coordinator/edge count adjustment and
  edge-variable injection logic against the serializedConfig JSON, plus an
  auto-deploy-by-default (--no-deploy to only stage) commit wrapper
- controllers/exec.rs: promoted from db_stats' private exec_command_in_container
- commands/postgres/{mod,pitr,ha,pgbouncer}.rs: full command tree
- 17 new mutation + 5 new query GraphQL documents
Finishes the `railway postgres pgbouncer` stubs left by the Phase A
scaffold:

- `configure`: edits POOL_MODE/MAX_CLIENT_CONN/DEFAULT_POOL_SIZE/
  MAX_PREPARED_STATEMENTS on the resolved edge service via the shared
  stage+commit patch mechanism (`--no-deploy` to only stage), printing
  the same advisory (non-blocking) misconfiguration warnings the
  frontend's PoolSizingRows computes before applying the change.
- `scale --replicas N`: delegates into the existing multi-region
  replica-patch controller (`build_multi_region_patch`/`merge_config`/
  `validate_total_replicas`) scoped to the PgBouncer edge service,
  resolving its single currently-deployed region rather than
  reimplementing region handling.
- `status`: adds a live `SHOW POOLS`/`SHOW STATS`/`SHOW SERVERS` probe
  over the existing SSH exec primitive, with the same utilization
  thresholds (0.8/0.9/0.95) as the frontend; degrades to "probe
  unavailable" under a 5s timeout instead of failing the command.

Promotes `template_apply::commit_staged_patch`'s stage step into a new
`stage_and_commit_patch` helper, and bumps `db_stats`'s section/int
parsing helpers to `pub(crate)` so the live probe can reuse them
instead of duplicating the parsing convention.
… + live status probe

Wires the remaining `railway postgres pitr` stubs left by the phase-A scaffold:
progress (with --watch polling), restore (point-in-time into a new service),
backup {list,create,delete,lock,restore,trigger}, and schedule {set,list}.
Also adds a best-effort live coverage/archiver probe to `status`, SSHing into
the root service's container to run `pgbackrest info` and a `pg_stat_archiver`
query, gated behind a 5s timeout and degrading gracefully when unreachable.

`backup trigger` has no dashboard equivalent -- flagged as such in --help.
…e status probe, revert precheck

Completes the largest remaining piece of `railway postgres ha`:

- `scale --replicas/--coordinators/--edge`: new `controllers/cluster_scale.rs`
  mutates an already-converted HA cluster's live topology, cloning an
  existing sibling replica/coordinator's shape (source, variables, volume)
  to scale up, deleting the highest-numbered replica or the lowest-numbered
  ("primary") coordinator to scale down, and restamping the cluster's
  declared wiring (identity vars, data-node list, coordinator hosts,
  quorum) against `EnvironmentConfig` -- re-derived from
  `template_apply.rs`'s conversion-time wiring logic, not force-converted
  from its serde_json::Value shape. Edge scaling delegates to the same
  multi-region-config patching concept `scale.rs`/`regions.rs` already use.
  Coordinator counts are validated odd up front. New service/volume
  create+delete happen via direct mutations; the wiring/variable diff is
  staged and committed through the same `EnvironmentPatchCommitStaged`
  auto-deploy toggle `convert`/`revert` already use.
- `switchover --to <service>` (alias `promote`): new `controllers/patroni.rs`
  exec-probes Patroni's REST API in any reachable cluster member's
  container to find the current leader, then POSTs `/switchover` to
  promote the candidate. No GraphQL mutation exists for this -- it's the
  CLI's own primitive, mirroring what the frontend does over its browser
  tunnel.
- `status` now shows a live per-member role/state/lag column (best-effort,
  5s timeout per member, degrades to "unreachable" instead of failing).
- `revert` now prechecks that the root is the current Patroni leader before
  allowing the revert, erroring with a pointer to `switchover` if not
  (matches the frontend's own gate); degrades to a warning if no member is
  reachable at all, mirroring `pitr disable`'s replication-health check.

Adds `ClusterWiring`/`group_id` fields to `ServiceInstance` (the
`environment.config` shape) and promotes `template_apply::commit_staged_patch`/
`format_data_node_entry`/`private_domain_ref` to `pub(crate)` for reuse.
# Conflicts:
#	src/controllers/template_apply.rs
Inline the three clippy::print_literal hits in ha.rs/pitr.rs's table
headers, and drop the now-fully-unused not_yet_implemented helper now
that every phase-B verb has a real implementation.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant