PlanetScale · MySQL Serverless

Guía de Migración a PlanetScale
Cultiva Commerce SL

Migración sin downtime desde RDS MySQL a PlanetScale con schema branching, Prisma ORM y driver HTTP para Vercel Edge Functions.

Cliente: Cultiva Commerce SL
Stack: Next.js 14 + TypeScript + Prisma
Deploy: Vercel Edge
Filas en orders: 3.8M
🔀
Flujo de Schema Branching
Como Git, pero para tu base de datos — branch → test → deploy request → merge
🗄️
main
producción
🌿
add-budget-cols
branch dev
🔍
Deploy Request
review diff
Deploy
zero downtime
main updated
sin bloqueo
⚙️
Paso 1–2 · CLI + Creación de Base de Datos
Instalación, autenticación y setup inicial en PlanetScale
Terminal
bash
# 1. Instalar CLI (macOS)
$ brew install planetscale/tap/pscale

# 2. Autenticar con tu cuenta PlanetScale
$ pscale auth login
  → Abre navegador · autoriza acceso · vuelves al terminal

# 3. Crear la base de datos para Cultiva Commerce
$ pscale database create cultiva-commerce --region eu-west
  → Database "cultiva-commerce" created in region eu-west

# 4. Conectar en local al branch main (proxy sin password)
$ pscale connect cultiva-commerce main --port 3306
  → Listening on 127.0.0.1:3306
💡
Desarrollo local sin credenciales
Con pscale connect activo, conecta tu app a localhost:3306 sin usuario ni contraseña. El CLI gestiona la autenticación por ti.
🌿
Paso 3 · Branch de Esquema — add-budget-cols
Añadir budget_approved + approved_at + índice compuesto sin tocar main
Crear el branch y conectarse
Terminal
bash
# Crear branch de esquema (equivalente a git branch)
$ pscale branch create cultiva-commerce add-budget-cols
  → Branch "add-budget-cols" created

# Conectar al branch en otro puerto
$ pscale connect cultiva-commerce add-budget-cols --port 3307

# Abrir shell SQL en el branch
$ pscale shell cultiva-commerce add-budget-cols
Migraciones SQL en el branch (sin tocar producción)
migration_add_budget_cols.sql
sql
-- Añadir columnas de aprobación de presupuesto a la tabla orders
-- Se ejecuta en el branch add-budget-cols, NO en main

ALTER TABLE orders
  ADD COLUMN budget_approved DECIMAL(12, 2) NULL COMMENT 'Presupuesto aprobado por el cliente',
  ADD COLUMN approved_at      TIMESTAMP     NULL COMMENT 'Fecha y hora de aprobación';

-- Índice compuesto para los reportes de la agencia
ALTER TABLE orders
  ADD INDEX idx_client_status_created (client_id, status, created_at);

-- Verificar los cambios en el branch
DESCRIBE orders;
⚠️
PlanetScale no soporta Foreign Keys
Vitess no permite FK constraints. Usa relationMode = "prisma" en el schema de Prisma para que las relaciones se resuelvan en aplicación, no en la BD.
🚀
Paso 4 · Deploy Request — Zero Downtime
Como un Pull Request para el esquema: revisa el diff y despliega sin bloquear tablas
Terminal
bash
# Crear deploy request (el "PR" del esquema)
$ pscale deploy-request create cultiva-commerce add-budget-cols
  → Deploy request #7 created

# Revisar el diff antes de desplegar
$ pscale deploy-request diff cultiva-commerce 7
  -- + ALTER TABLE orders ADD COLUMN budget_approved DECIMAL(12,2) NULL
  -- + ALTER TABLE orders ADD COLUMN approved_at TIMESTAMP NULL
  -- + ALTER TABLE orders ADD INDEX idx_client_status_created (...)

# Desplegar sin downtime (PlanetScale usa gh-ost / online DDL)
$ pscale deploy-request deploy cultiva-commerce 7
  → Deploy request #7 deploying... done in 12s
  → 3,847,293 rows migrated with 0 table locks
vs. ALTER TABLE en RDS — de 45 min a 12 seg
PlanetScale aplica los cambios de esquema mediante online DDL (basado en gh-ost). La tabla orders con 3.8M filas se migra en segundo plano sin bloquear lecturas ni escrituras.
🔷
Paso 5 · Integración con Prisma ORM
Schema actualizado con las nuevas columnas y relationMode = "prisma"
prisma/schema.prisma
prisma
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider     = "mysql"
  url          = env("DATABASE_URL")
  relationMode = "prisma"  // ← Obligatorio: PS no soporta FK
}

model Order {
  id              Int       @id @default(autoincrement())
  clientId        Int       @map("client_id")
  campaignId      Int       @map("campaign_id")
  amount          Decimal   @db.Decimal(10, 2)
  status          String    @default("pending")
  // ← Nuevas columnas añadidas en este sprint
  budgetApproved  Decimal?  @db.Decimal(12, 2) @map("budget_approved")
  approvedAt      DateTime? @map("approved_at")
  client          Client    @relation(fields: [clientId], references: [id])
  createdAt       DateTime  @default(now()) @map("created_at")

  @@index([clientId])
  @@index([clientId, status, createdAt])  // índice compuesto reportes
  @@map("orders")
}

model Campaign {
  id        Int     @id @default(autoincrement())
  name      String
  budget    Decimal @db.Decimal(12, 2)
  clientId  Int     @map("client_id")
  @@index([clientId])
  @@map("campaigns")
}
Terminal — push schema al branch
bash
# Conectar al branch en :3309 y hacer push del schema Prisma
$ pscale connect cultiva-commerce add-budget-cols --port 3309 &
$ DATABASE_URL="mysql://root@localhost:3309/cultiva-commerce" \
  npx prisma db push
  → Environment variables loaded from .env
  → ✔ Your database is now in sync with your Prisma schema.
Paso 6 · Driver HTTP para Vercel Edge Functions
@planetscale/database funciona en Edge Runtime sin TCP — ideal para Next.js App Router
src/lib/db.ts
typescript
import { connect } from "@planetscale/database"

// HTTP-based driver: funciona en Edge
const db = connect({
  host:     process.env.DATABASE_HOST,
  username: process.env.DATABASE_USERNAME,
  password: process.env.DATABASE_PASSWORD,
})

export async function getOrdersByClient(
  clientId: number
) {
  const { rows } = await db.execute(
    `SELECT o.id, o.amount, o.status,
       o.budget_approved, o.approved_at,
       c.name AS campaign_name
     FROM orders o
     JOIN campaigns c ON c.id = o.campaign_id
     WHERE o.client_id = ?
       AND o.status IN ('pending','processing')
     ORDER BY o.created_at DESC
     LIMIT 50`,
    [clientId]
  )
  return rows
}
app/api/orders/route.ts
typescript
import { NextRequest, NextResponse }
  from "next/server"
import { getOrdersByClient }
  from "@/lib/db"

// Edge Runtime — requiere driver HTTP
export const runtime = "edge"

export async function GET(
  req: NextRequest
) {
  const clientId = Number(
    req.nextUrl.searchParams.get("clientId")
  )
  const orders = await getOrdersByClient(clientId)
  return NextResponse.json({ orders })
}
🔐
Paso 7 · Variables de Entorno — Vercel + Local
Configuración de credenciales en todos los entornos
.env.local (desarrollo)
env
# Local: pscale connect en :3306
DATABASE_URL="mysql://root@127.0.0.1:3306/cultiva-commerce"

# No necesitas host/user/pass localmente
# pscale connect maneja la auth
Vercel · Production (Edge)
env
# Desde PlanetScale console → Settings → Passwords
DATABASE_HOST="aws.connect.psdb.cloud"
DATABASE_USERNAME="cultiva-app-prod"
DATABASE_PASSWORD="pscale_pw_xxx..."

# Para Prisma (Node.js, no Edge)
DATABASE_URL="mysql://cultiva-app-prod:pscale_pw_xxx@aws.connect.psdb.cloud/cultiva-commerce?sslaccept=strict"
📊
Comparativa: RDS MySQL vs PlanetScale
Por qué Cultiva Commerce migra ahora
Característica RDS MySQL (actual) PlanetScale
ALTER TABLE 3.8M filas ~45 min con lock ~12 seg, zero lock
Schema branching No Sí (tipo Git)
Deploy request (review diff) No
Driver para Edge Runtime No (TCP) Sí (HTTP)
Connection pooling serverless Manual (RDS Proxy) Integrado
Foreign key constraints No (usa relationMode)
Coste base ~$180/mes (db.t3.medium) $0 (hobby) / $39 (scaler)
Query insights CloudWatch + lento Integrado, en tiempo real
📋
Buenas Prácticas para Cultiva Commerce
8 reglas de oro para operar PlanetScale en producción
🌿
Branch siempre
Nunca modifiques main directamente. Crea un branch, testa los cambios y usa deploy request.
🔗
Sin FK → relationMode
Vitess no soporta FK constraints. Usa relationMode = "prisma" para relaciones en capa de aplicación.
HTTP driver en Edge
Usa @planetscale/database para Vercel Edge / Cloudflare Workers. mysql2 solo para Node.js server.
📈
Índices antes de necesitarlos
Añade índices en columnas de filtros y sorts. Query Insights muestra qué queries los necesitan.
👁️
Review el diff
Antes de hacer deploy, revisa siempre el diff con pscale deploy-request diff.
🔒
Env vars separadas
Credenciales distintas por entorno (dev branch / staging / prod). Rota passwords periódicamente.