Skip to content

docs: PostgreSQL cross-cluster instance migration guide (KB260720001) + pending PG-solutions review fixes#809

Open
SuJinpei wants to merge 17 commits into
mainfrom
docs/add-postgresql-kb-solutions-31526
Open

docs: PostgreSQL cross-cluster instance migration guide (KB260720001) + pending PG-solutions review fixes#809
SuJinpei wants to merge 17 commits into
mainfrom
docs/add-postgresql-kb-solutions-31526

Conversation

@SuJinpei

Copy link
Copy Markdown
Contributor

What

  • New solution doc: How_to_Migrate_a_PostgreSQL_Instance_Across_Clusters.md (KB260720001) — cross-cluster/cross-platform PostgreSQL instance migration using the operator's hot-standby (XCR) feature with a two-phase switchover and checksum-gated cutover. Companion to KB251000009 (Hot Standby Cluster Configuration Guide).
  • Carried-over review fixes from the PR add PostgreSQL node migration and CloudBeaver solutions (MIDDLEWARE-31526) #792 stream that were committed after its squash-merge and never landed: CodeRabbit fixes to the node-migration and CloudBeaver docs, and removal of hand-written zh mirrors (pipeline auto-generates them).

Validation

The migration guide was validated end-to-end on 2026-07-20 with operator v4.3.3 on two separate ACP platforms (NodePort pairing, arm64, PG 16): standby bootstrap from peer, checksum parity, demote-then-promote switchover, reverse replication after cutover.

The troubleshooting section encodes real failures encountered during validation, including the cross-version XCR schema drift (ECO-703) and the standby-recreate secret cleanup trap.

🤖 Generated with Claude Code

SuJinpei and others added 4 commits June 18, 2026 06:01
…1526)

Precipitate historical internal KB PostgreSQL solutions, modernized to
the current acid.zalan.do/v1 postgresql CR and verified live on ACP
4.2/4.3, bilingual (en + zh):

- KB260515007 How to Migrate a PostgreSQL Instance to Another Node
- KB260515008 How to Deploy CloudBeaver for PostgreSQL Management

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The zh translations are produced by the translation pipeline from the
en sources (tracked via sourceSHA); they should not be committed by hand.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- CloudBeaver: retrieve superuser password from the operator-created Secret
  (postgres.<cluster>.credentials...) instead of a non-existent PGPASSWORD_SUPERUSER env var
- node migration: label only the target node to avoid rescheduling onto the source

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…001)

Validated end-to-end on operator v4.3.3 across two ACP platforms
(NodePort pairing, two-phase switchover, checksum-gated cutover).
Includes troubleshooting for the ECO-703 cross-version XCR schema
drift and standby-recreate secret cleanup.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ordered teardown (standby first, then remove clusterReplication from the
promoted primary), operator-side cleanup behavior (sys_operator schema drop,
xdc_hotstandby slot removal), leftover-object cleanup, and verification.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…slot/xcr svc)

Ran the full teardown on the validated v4.3.3 pair: sys_operator schema
drop confirmed automatic; PVC retention is config-dependent (deleted with
the CR on v4.3.3); the physical xdc_hotstandby slot and the -xcr service
linger and need manual removal.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
For topologies with no inter-cluster network where the workstation reaches
both API servers: pg_dump piped between two kubectl exec sessions. Validated
live (100k rows, exact checksum parity, clean exit); documents the
version-matched-binary and spilo-schema-exclusion requirements found during
validation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…721001

Workstation-relayed logical migration as its own doc, tested verbatim
end-to-end before commit. Testing corrected the restore identity: restore
as the CR-defined application user (plus --no-comments), otherwise objects
land owned by postgres and the app user cannot query them. Migration guide
now links to the standalone doc instead of embedding the procedure.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Verify with exact per-table counts (query_to_xml) instead of the
  n_live_tup estimate, in both baseline and post-restore steps
- Run the relay pipe under pipefail and report per-side PIPESTATUS so a
  source-side dump failure cannot be masked
- Add extension preflight: inventory pg_extension on the source and
  pre-create missing extensions as postgres on the target; new
  troubleshooting entry for 'permission denied to create extension'
- Scope the ownership guidance to the single-owner operator layout and
  extend the fallback ALTER note beyond public tables/sequences
- Add security notes: password visibility in local process listings,
  dump-file permissions/retention, TLS transport
- List required Kubernetes permissions and Spilo image assumptions in
  Prerequisites
- Reword the 'no version-matching constraints' claim (here and in the
  companion guide's pointer) to same-or-newer target major, no downgrades

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Executor feedback: the examples read as if 'appdb' were the whole
instance. Step 1 now enumerates the source's application databases
(pg_database query, validated live) and states that Steps 2-4 repeat
once per database with the name/owner substituted; Step 2 and the
Step 3 pipe restate it at the point of use. The postgres maintenance
database is explicitly excluded from migration.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reduce the procedure from repeat-Steps-2-4-per-database to one script
run: enumerate all application databases, auto-create ones missing on
the target (e.g. postgres-owned databases created outside the CR),
pre-create extensions, transfer each database as its owner, verify
exact per-table counts, and report PASS/FAIL per database. Step 1 also
gains a generator for the target CR users:/databases: sections.

Validated end-to-end on the longterm-c -> longterm-b pair with a
3-database fixture (two CR-owned + one stray postgres-owned): all
databases PASS first try, ownership and per-user access verified.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The scripted whole-instance migration is now the main procedure
(Steps 1-5: create target CR, stop writes, run script, verify app
access, cut over). The per-database manual steps move to a
'Reference: Manual Single-Database Transfer' section — kept because
debugging a per-database FAIL, single-database/partial migrations,
and the resumable file-based variant for very large databases all
need the standalone commands and flag rationale. Troubleshooting
pointers retargeted; added explicit warning not to rerun the whole
script after a partial success (re-restoring populated databases
collides).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Step 1 gains a 'Preserve application credentials' sub-step: copy each
application user's credential secret from the source into the target
namespace BEFORE creating the target CR — the operator adopts existing
secrets and sets role passwords from them instead of generating new
ones, so applications keep their credentials and only the endpoint
changes at cutover. System users (postgres/standby) are skipped.
New troubleshooting entry for the CR-created-first case: patch the
secret AND ALTER ROLE together.

Live-validated on the C->B pair: pre-staged secrets adopted (target
secret == source password, login with source password OK), full
scripted migration PASS, migrated data queried on the target with the
source-era passwords for both users.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The script now accepts database names as arguments (bash migrate.sh
appdb) to migrate a subset — validated live with two sequential
single-database runs — which was the reference section's main job
(redoing one FAIL). Its remaining unique content is folded in place:
the file-based large-database variant becomes a compact Step 3
subsection, the -x re-grant note moves to Step 4, and the multi-owner
scope note plus newer-major binary tip move into the troubleshooting
entries that need them. Doc shrinks by a net 40 lines with a single
flow: 5 steps + troubleshooting.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 (blocker): the script can no longer report success after migrating
zero databases — pod lookups and the source-major probe are guarded
with early exits, and an ATTEMPTED counter turns empty enumeration or
a non-matching subset argument into an explicit error. Validated:
bogus cluster name exits 1 at preflight, full run reports the count,
'bash migrate.sh nosuchdb' exits 1.
2 (major): the credential pre-stage loop derives the user from the
secret's NAME (the CR user the target operator looks up) instead of
the decoded username field, copies credential bytes base64-verbatim,
skips pooler alongside postgres/standby, and documents the
password-rotation limitation.
3 (major): the password recovery is injection-safe — base64 merge
patch for the secret and psql :'pw' literal quoting for ALTER ROLE.
Validated with a password containing quotes, backslash, and SQL.
4 (minor): the verification query schema-qualifies table names and
excludes the schemas the dump excludes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Remove the last manual side-path: instead of a hand-run dump/restore
for large databases, the script now has two modes through the same
code — default pipe, or DUMP_DIR=/path for per-database dump files
with atomic completion (.partial + mv) and reuse on rerun, making
transfers resumable. The failed-restore redo simplifies to DROP
DATABASE + rerun with the database name (the script recreates the
database with the correct owner). Validated live: file-mode full run,
resume run reusing a completed dump after DROP, and pipe mode
unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Finding 1 (major): a leftover completed dump could be silently reused
across maintenance windows, migrating stale data past the count check.
File mode now takes a fresh dump on every run by default; REUSE_DUMPS=1
opts into reuse, documented as safe only while source writes have
stayed stopped since the dump was taken. Validated live: after a
post-window source write, the default rerun re-dumped and migrated the
new row; REUSE_DUMPS=1 reuses the completed dump.
Finding 2 (nit): FAIL message says 'transfer exit' instead of
'pipe exit', correct for both modes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@SuJinpei
SuJinpei deployed to translate July 22, 2026 03:37 — with GitHub Actions Active
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