PlanetScale Serverless Database: Zero-Downtime Schema Migration with Branching

Database tutorial - IT technology blog
Database tutorial - IT technology blog

The Schema Migration Problem Nobody Talks About

Running ALTER TABLE on a production MySQL table with 10 million rows is the kind of thing that keeps backend engineers awake at night. I’ve been there — watching a migration lock a table for 40 minutes while the support channel fills up with complaints. Having worked with MySQL, PostgreSQL, and MongoDB across different projects, each has its own strengths, but MySQL’s schema migration story has historically been the most painful of the three.

The root problem: MySQL’s DDL operations can acquire metadata locks that block reads and writes. Even with tools like pt-online-schema-change or gh-ost, you’re still juggling shadow tables, triggers, and race conditions. For small teams or startups, this complexity is a real barrier to shipping database changes confidently.

PlanetScale takes a fundamentally different approach. It treats your database schema changes the same way Git treats code changes — through branches and merge requests. The result is schema migrations that are non-blocking, reviewable, and reversible.

Core Concepts You Need to Understand First

What PlanetScale Actually Is

PlanetScale is a serverless MySQL-compatible database platform built on top of Vitess — the same sharding and connection management system that YouTube has used since 2011. You get MySQL wire protocol compatibility (your existing ORM and drivers just work) plus Vitess’s non-blocking DDL engine under the hood.

The “serverless” part means you don’t manage instances, replicas, or connection limits manually. Scaling is handled automatically, and you’re billed based on storage and row reads/writes — not by the hour.

Database Branching

Every PlanetScale database has a production branch (protected, no direct DDL allowed) and any number of development branches. A branch is a full copy of your schema — not your data — so you can freely run migrations in isolation without touching production at all.

Think of it like this:

  • main — your production branch (read-only schema changes)
  • add-user-avatar — a dev branch where you add a column
  • refactor-indexes — another dev branch working in parallel

Branches are cheap and fast to create. You can have dozens running simultaneously without any performance impact on production.

Deploy Requests

When your schema change is ready, you open a deploy request — PlanetScale’s equivalent of a pull request, but for database migrations. It shows you a diff of exactly what DDL will run, lets teammates review it, and then executes it against production using Vitess’s Online DDL engine. No table locks, no downtime.

Non-Blocking Schema Changes

Vitess’s DDL strategy (vitess or online) uses a shadow table approach similar to gh-ost, but it’s baked into the platform. When you deploy a schema change, Vitess:

  1. Creates a new shadow table with the desired schema
  2. Copies rows in batches without blocking reads or writes
  3. Applies ongoing changes via binary log tailing
  4. Does an atomic cutover when caught up

The entire process is transparent. Your application keeps serving traffic throughout.

Hands-On: Deploying Your First Schema Migration

Step 1 — Install the PlanetScale CLI

The pscale CLI is how you manage everything locally. Install it with your package manager:

# macOS
brew install planetscale/tap/pscale

# Linux (Debian/Ubuntu)
curl -fsSL https://cli.planetscale.com/install.sh | bash

# Verify
pscale version

Step 2 — Authenticate and Create a Database

# Opens browser for OAuth login
pscale auth login

# Create a new database (free tier available)
pscale database create my-app-db --region us-east

# Check status
pscale database show my-app-db

Your database comes with a main branch ready to go. The free tier gives you 5 GB storage and 1 billion row reads per month — more than enough for most side projects or staging environments.

Step 3 — Connect Locally via Proxy

PlanetScale uses mTLS for all connections. The CLI proxy handles this for you, exposing a local MySQL socket your app can connect to normally:

# Connect to production branch
pscale connect my-app-db main --port 3309

# Now connect with any MySQL client
mysql -h 127.0.0.1 -P 3309 -u root

For your application, use the connection string from the PlanetScale dashboard instead of the proxy — it gives you a proper hostname with credentials.

Step 4 — Create a Development Branch for Your Schema Change

Never run DDL directly on main. Always branch first:

# Create a branch from main
pscale branch create my-app-db add-user-avatar --from main

# Connect to the dev branch
pscale connect my-app-db add-user-avatar --port 3310

Now run your migration against the dev branch:

-- Connect to port 3310 and run your DDL freely
ALTER TABLE users ADD COLUMN avatar_url VARCHAR(500) DEFAULT NULL;
CREATE INDEX idx_users_created_at ON users (created_at);
ALTER TABLE posts ADD COLUMN reading_time_minutes TINYINT UNSIGNED DEFAULT 0;

These DDL statements run instantly on the dev branch because there’s no production data — you’re just modifying the schema definition.

Step 5 — Open a Deploy Request

# Create a deploy request (branch → main)
pscale deploy-request create my-app-db add-user-avatar

You can also do this from the web UI, which shows a clean diff of the schema changes. Share the deploy request link with your team for review. Once approved:

# Get the deploy request number
pscale deploy-request list my-app-db

# Deploy it
pscale deploy-request deploy my-app-db 1

PlanetScale queues the migration and runs it using Online DDL. For a table with millions of rows, this might take a few minutes in the background — but your application sees zero interruption.

Step 6 — Monitor and Roll Back if Needed

# Watch migration progress
pscale deploy-request show my-app-db 1

# If something goes wrong, revert
pscale deploy-request revert my-app-db 1

The revert operation drops the added column or index using the same non-blocking process. I’ve used this twice in production — once when a new index caused an unexpected query plan regression, and once when a column default value was wrong. Both reverts completed without any downtime.

Connecting from Your Application

For a Node.js/TypeScript app using Prisma, the connection is standard MySQL:

# .env — get these values from PlanetScale dashboard → Connect → Prisma
DATABASE_URL="mysql://user:[email protected]/my-app-db?sslaccept=strict"
# Python with SQLAlchemy
import os
from sqlalchemy import create_engine

engine = create_engine(
    os.environ["DATABASE_URL"],
    connect_args={"ssl": {"ca": "/etc/ssl/certs/ca-certificates.crt"}}
)

Tips From Real Usage

Naming Branches After Features, Not Dates

Use descriptive branch names like add-payment-table or drop-legacy-columns instead of migration-2024-07. Six months later, you’ll thank yourself when browsing deploy request history.

Keep Branches Short-Lived

Development branches don’t auto-sync schema changes from main. If your branch lives for weeks while main gets other migrations deployed, you’ll hit merge conflicts in the deploy request. Aim to open, review, and deploy within 1–3 days.

The “Expand/Contract” Pattern for Renaming Columns

PlanetScale doesn’t allow you to rename a column in a single deploy request if it would break existing queries. Use the expand/contract pattern instead:

  1. Deploy: add new column user_name alongside old column username
  2. Update your application to write to both columns
  3. Backfill: copy data from old to new
  4. Deploy: update app to read from new column only
  5. Deploy: drop old column

More steps, but each step is safe and reversible.

Use Boost for Cold Connections

PlanetScale’s serverless nature means cold starts can cause connection latency spikes. Enable PlanetScale Boost (query caching) for read-heavy workloads, and use connection pooling at the application layer (PgBouncer-equivalent isn’t needed here — PlanetScale handles this internally).

Foreign Keys Are Not Supported

This is the biggest gotcha. Because PlanetScale is built on Vitess (which supports horizontal sharding), foreign key constraints are disabled. You enforce referential integrity at the application layer instead. If your schema heavily relies on foreign keys, factor this into your decision to migrate.

When PlanetScale Makes Sense

After using it across a few projects, here’s my honest take on when to reach for PlanetScale:

  • Good fit: Teams that ship schema changes frequently, applications where downtime during migrations is unacceptable, startups that want managed MySQL without ops overhead
  • Think twice: Workloads requiring foreign key constraints, applications doing heavy JOIN-heavy analytics (consider a data warehouse instead), or teams already comfortable with gh-ost on self-managed MySQL

The branching model genuinely changes how you think about database changes. Instead of “let’s plan the maintenance window,” the conversation becomes “open a branch, make the change, get it reviewed.” That shift in workflow is worth more than any specific feature.

Share: