PostgreSQL → PlanetScale
Move a PostgreSQL database onto PlanetScale Postgres with pg_dump and pg_restore. Use --no-owner and --no-privileges. Size the PlanetScale disk to at least 150 percent of the source. Pause writes if you are not using the WAL streaming path.
Should you make this move?
Both platforms have a case. Compare what you gain with what you give up before scheduling the cutover.
PostgreSQL
- Powerful SQL, extensions, integrity, and open governance support demanding workloads
- A mature data model and query ecosystem supports serious production workloads
- Tuning, upgrades, and sophisticated features require experienced operators
- Schema design, migration, tuning, and operations require specialist attention
PlanetScale
- Branching, deploy requests, and Metal performance make Vitess MySQL or managed Postgres appealing for production apps
- A mature data model and query ecosystem supports serious production workloads
- Engine choice, Vitess limits, and paid-plan economics change how you dump, branch, and cut over
- Schema design, migration, tuning, and operations require specialist attention
PlanetScale: Branching, deploy requests, and Metal performance make Vitess MySQL or managed Postgres appealing for production apps. This removes a major source-side concern: Tuning, upgrades, and sophisticated features require experienced operators.
What you lose: Powerful SQL, extensions, integrity, and open governance support demanding workloads. What you inherit: Engine choice, Vitess limits, and paid-plan economics change how you dump, branch, and cut over.
Jump to a section
Know the shape of the move.
This timeline assumes
- The destination is PlanetScale Postgres, not Vitess MySQL. A Postgres-to-MySQL move is a different project with type conversion.
- PlanetScale calls pg_dump the simple path for smaller databases that can take downtime. Larger or online moves use their WAL streaming or assessment path.
- PlanetScale asks for disk at least 150 percent of the source. Network-attached storage can raise minimum disk size. Metal sizes are chosen at create time.
- You have pg_dump, pg_restore, and psql, plus a PlanetScale Postgres connection from the console.
- We keep the source writable-ready until PlanetScale has passed one application cycle.
What survives the move.
“Partial” and “manual” are not footnotes. They are work that must be scheduled and verified.
| Item | Outcome | Impact | What happens | Mitigation |
|---|---|---|---|---|
| Schema and data | partial | critical | pg_dump plus pg_restore copies tables, data, and most schema objects. PlanetScale tells you to use --no-owner and --no-privileges because source owners and GRANTs do not apply. | Dump custom format for large databases, restore with --no-owner --no-privileges, then apply the PlanetScale roles you actually want. |
| Ownership and privileges | lost | critical | Those flags drop owners and grants on purpose. | Recreate roles, default privileges, and app GRANTs on PlanetScale after restore. |
| Extensions | partial | critical | Only extensions PlanetScale Postgres allows will restore. Others fail the restore or create missing objects. | List source extensions and confirm each one on PlanetScale before you dump production. |
| Roles, memberships, and password hashes | manual | critical | Cluster roles are not a portable dump of login roles. | Create PlanetScale roles from the console or SQL you control. Rotate passwords. |
| Physical replication slots and WAL state | lost | high | A dump is not a replica. In-flight WAL does not come along unless you use the streaming import path. | Freeze writes for dump/restore, or follow PlanetScale's WAL streaming guide instead of this playbook's simple path. |
| Tablespaces and source paths | lost | medium | Source tablespaces do not exist on PlanetScale. | Dump without tablespace assignments and restore into the default space. |
| Disk headroom | manual | critical | PlanetScale wants about 150 percent of source size. A restore can fail mid-file if the disk is short. | Measure source size, set minimum disk or pick Metal large enough, and watch free space during restore. |
| Connection pooling and serverless sources | manual | high | Dump from a session or direct endpoint. Transaction poolers break pg_dump. | Use the source direct URL and a PlanetScale connection from the console, not a pooler, for dump and restore. |
| Jobs, logical replication, and foreign servers | manual | high | Cron-like jobs, subscriptions, and FDWs are cluster objects that often fail or need a rewrite. | Inventory them and recreate only what PlanetScale supports. |
| Query performance | manual | high | A restore does not tune work_mem, indexes, or statistics for the new host. | ANALYZE after restore and run the hot query set before you switch the app. |
| Source backups and PITR | lost | medium | Old WAL and snapshots stay on the source. | Keep the source until PlanetScale backups are configured and tested. |
| Application cutover | manual | critical | The dump is stale the moment writes resume on the source. | Freeze writes, restore, verify, then switch DATABASE_URL. |
Where each thing goes.
| Source | Destination | Method | Notes |
|---|---|---|---|
| PostgreSQL database | PlanetScale Postgres database | manual | Create the PlanetScale database and confirm disk size first. |
| Schema and tables | Same names after pg_restore | automatic | Use custom format (-Fc) for large databases. |
| Owners | No destination | unsupported | --no-owner drops them. |
| Privileges | PlanetScale GRANTs you apply after restore | manual | --no-privileges drops them. |
| Extensions | Allowed PlanetScale extensions | transform | Fail the pilot if a required extension is missing. |
| Sequences | Restored sequences | transform | Insert a row and confirm the next value. |
| Roles | PlanetScale roles | manual | Create and rotate. |
| Direct source URL | pg_dump source | transform | Avoid transaction poolers. |
| Application DATABASE_URL | PlanetScale URL after verification | manual | Switch only after ANALYZE and app tests. |
Make the move recoverable.
Create the source-of-truth backup
Hold a source dump and a sized PlanetScale target before you freeze production.
- Record database size, table counts, extensions, and jobs.
- Create the PlanetScale Postgres database with disk at least 150 percent of source size.
- Run PlanetScale's migration assessment if you have it, and decide dump versus WAL streaming.
- Take a rehearsal pg_dump --format=custom --no-owner --no-privileges.
- Store the dump read-only and hash it.
Proof to capture: A dated manifest lists size, disk, extensions, dump hash, and the chosen import path.
Custom-format dump
Follow PlanetScale's recommended dump flags.
- Dump from a direct endpoint.
- Use --no-owner --no-privileges --verbose.
- Keep a SQL-format dump only if you must restore with psql.
Proof to capture: Save the input, output, command or tool settings, warnings, and final item counts.
Parallel restore and ANALYZE
Load faster and refresh statistics.
- Restore into the empty PlanetScale database.
- Fix any failed extension or object.
- ANALYZE and run the hot query set.
Proof to capture: Save the input, output, command or tool settings, warnings, and final item counts.
The things most likely to hurt.
These are operating limits. Treat every “Stop if” condition as a blocked migration, not a suggestion.
Disk fills during restore
PlanetScale disk is near the source size instead of 150 percent.
- Consequence
- Restore stops and the target is half-loaded.
- Mitigation
- Size disk first and watch usage during restore.
Stop if: Free disk cannot finish the restore.
Writes continue during dump
The app still writes to the source after pg_dump starts and you are not using WAL streaming.
- Consequence
- PlanetScale is missing those rows.
- Mitigation
- Freeze writes for the dump/restore path.
Stop if: A production write lands on the source after the freeze.
Required extension missing
pg_restore errors on an extension PlanetScale does not offer.
- Consequence
- Functions or indexes the app needs do not exist.
- Mitigation
- Confirm extensions before production dump.
Stop if: A required extension cannot be created.
Do the work in this order.
- Days 1–3 · inventory
Inventory and size
5–10 hours active2–3 days elapsedExtension review waiting- Measure size and list extensions.
- Create PlanetScale with enough disk.
- Choose dump versus WAL streaming.
Depends on: Source and PlanetScale admin access
Stop / go checkpointDump?
Go when: Disk, extensions, and freeze window are signed.
Stop when: A required extension is unavailable or the window is too short.
- Days 3–5 · backup
Rehearsal dump
3–8 hours active1–2 days elapsedDump runtime waiting- Take a custom-format dump.
- Hash it.
- Confirm restore tools.
Depends on: Approved inventory
Stop / go checkpointRestore?
Go when: Dump hash and size look complete.
Stop when: The dump is short or unreadable.
- Days 5–10 · pilot
Restore rehearsal
8–20 hours active3–6 days elapsedRestore runtime waiting- Restore into PlanetScale.
- Fix grants and extensions.
- ANALYZE and run app tests.
Depends on: Verified dump
Stop / go checkpointCut over?
Go when: Counts, extensions, and app tests pass.
Stop when: A required object is missing.
- Days 10–13 · cutover
Freeze, dump, restore
4–12 hours active1–2 days elapsedWrite freeze waiting- Pause source writes.
- Dump and restore production.
- Switch DATABASE_URL.
Depends on: Passed pilot
Stop / go checkpointOpen writes?
Go when: PlanetScale is the only writer and counts match.
Stop when: A row is missing or the source is still writable.
- Days 12–16 · observe
Observe and close
2–5 hours active3–5 days elapsedApplication cycle waiting- Watch errors and slow queries.
- Confirm PlanetScale backups.
Depends on: Verified cutover
Stop / go checkpointClose rollback?
Go when: No trigger fires through one cycle.
Stop when: Queries or data regress.
Cut over with a way back.
Cutover
Make PlanetScale Postgres the only writable database after a frozen dump and restore.
- Pause application writes on the source.
- Take the production custom-format dump.
- Restore to PlanetScale with --no-owner --no-privileges.
- Apply roles, ANALYZE, and reconcile counts.
- Switch DATABASE_URL and keep the source recoverable.
Proof to capture: PlanetScale serves the app. Counts match. The source is not receiving writes.
Rollback
Return writes to the source PostgreSQL without losing reviewed PlanetScale-era rows.
- Stop writes to PlanetScale.
- Point the app at the source.
- Export PlanetScale rows written after cutover.
- Apply only reviewed rows to the source.
- Run the same app checks on the source.
Proof to capture: The source is writable again and PlanetScale is not receiving production writes.
- Count mismatch
- Missing extension
- Disk-full restore
- App query failure
- Write landed on the wrong host
Prove the migration worked.
Every blocking check must pass. Capture the evidence before cleanup begins.
| Pass | ID | Check | Method | Expected result | Evidence |
|---|---|---|---|---|---|
V-01Blocking | Engine and disk | Confirm PlanetScale Postgres and disk at least 150 percent of source size. | Postgres engine, enough disk. | Console screenshot. | |
V-02Blocking | Row counts | Compare source and PlanetScale table counts after the freeze. | Every scoped table matches or has a signed exception. | Count sheet. | |
V-03Blocking | Required extensions | List extensions on both sides. | Every required extension exists. | \dx output. | |
V-04Blocking | Sequences | Insert a row on a serial table. | The next value is correct. | Insert log. | |
V-05Blocking | Roles and grants | Connect as the app role and as a denied role. | Least privilege after --no-privileges. | Grant sheet. | |
V-06Blocking | Query suite | Run ANALYZE and the application tests. | Hot queries and tests pass. | Test report. | |
V-07Blocking | Direct URLs | Confirm dump and restore did not use a transaction pooler. | Direct endpoints only for dump/restore. | Config audit. | |
V-08Blocking | Source still up | Prove the source accepts the old URL. | You can point the app back. | Rollback note. |
Remove the scaffolding safely.
Safe after: One application cycle, PlanetScale backups configured, and seven stable days.
- Archive the production dump and restore log.
- Revoke temporary dump roles.
- Keep the source until billing and rollback close.
- Document the PlanetScale connection string.
- Remove leftover restore users.
- Schedule the next backup and extension review.
When the plan met reality.
First-hand accounts are preferred. Vendor case studies are labeled, and every note below is an editorial paraphrase—follow the link for the full context.
Autumn, an AI billing platform, moved from an unnamed previous PostgreSQL provider onto PlanetScale Metal after pooling, upgrade, and latency problems. Manual replication stalled. PlanetScale support pointed them at the company's pgcopydb fork. The copy and cutover took two days with seconds of downtime. They report query latency falling from about 100 ms to under 10 ms. They do not name the source vendor.
- Use PlanetScale's pgcopydb fork for an online Postgres cutover instead of inventing your own replication.
- Measure p99 on the same queries before and after Metal. Autumn's story is latency, not a dump-and-restore diary.
- A same-engine Postgres move can finish in days when CDC is already packaged.
- The source provider is unnamed. Treat the latency numbers as Autumn's, not as a named vendor comparison.
Verify against the primary material.
Platform behavior changes. Check these sources and the review dates above before executing a production migration.
- PlanetScale: Postgres pg_dump and restoreAccessed 2026-10-29
- PlanetScale: Postgres import optionsAccessed 2026-10-29
- PlanetScale: Postgres imports with pgcopydbAccessed 2026-10-29
- PostgreSQL: SQL dumpAccessed 2026-10-29