# PlanetScale: Serverless MySQL mit Vitess

**Meta-Description:** PlanetScale für skalierbares MySQL. Database Branching, Non-Blocking Schema Changes und Vitess-powered Performance.

**Keywords:** PlanetScale, MySQL, Vitess, Serverless Database, Database Branching, Schema Migrations, Horizontal Scaling

---

## Einführung

PlanetScale bringt **YouTube-Scale** zu MySQL. Powered by Vitess (entwickelt für YouTube) bietet es Horizontal Sharding, Non-Blocking Schema Changes und Database Branching – ohne die Komplexität selbst zu managen.

---

## PlanetScale Features

```
┌─────────────────────────────────────────────────────────────┐
│                    PLANETSCALE                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Vitess Foundation:                                         │
│  ├── Horizontal Sharding (Auto)                            │
│  ├── Connection Pooling                                    │
│  ├── Query Routing                                         │
│  └── Petabyte-Scale Proven                                 │
│                                                             │
│  Developer Experience:                                      │
│  ├── Database Branching (wie Git)                          │
│  ├── Non-Blocking Schema Changes                           │
│  ├── Schema Revert (1-Click Rollback)                      │
│  └── PlanetScale Insights (Query Analytics)                │
│                                                             │
│  Serverless Benefits:                                       │
│  ├── No Cold Starts                                        │
│  ├── Auto-Scaling                                          │
│  ├── Per-Query Pricing                                     │
│  └── Edge-Compatible Driver                                │
│                                                             │
│  Security:                                                  │
│  ├── No Foreign Keys (by Design - Performance)             │
│  ├── Branch Permissions                                    │
│  └── Audit Logs                                            │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

---

## Setup

```bash
# PlanetScale CLI installieren
brew install planetscale/tap/pscale

# Login
pscale auth login

# Database erstellen
pscale database create my-app --region eu-west

# Branch erstellen
pscale branch create my-app main

# Local Proxy (Development)
pscale connect my-app main --port 3309
```

```bash
# Dependencies
npm install @planetscale/database
```

---

## Database Connection

```typescript
// lib/planetscale.ts
import { connect } from '@planetscale/database';

const config = {
  host: process.env.DATABASE_HOST,
  username: process.env.DATABASE_USERNAME,
  password: process.env.DATABASE_PASSWORD
};

export const conn = connect(config);

// Für Edge (Fetch-basiert)
export async function query<T>(
  sql: string,
  args?: unknown[]
): Promise<T[]> {
  const results = await conn.execute(sql, args);
  return results.rows as T[];
}
```

```typescript
// Mit Drizzle ORM
import { drizzle } from 'drizzle-orm/planetscale-serverless';
import { connect } from '@planetscale/database';
import * as schema from './schema';

const connection = connect({
  host: process.env.DATABASE_HOST,
  username: process.env.DATABASE_USERNAME,
  password: process.env.DATABASE_PASSWORD
});

export const db = drizzle(connection, { schema });
```

---

## Schema Definition (Drizzle)

```typescript
// src/db/schema.ts
import {
  mysqlTable,
  varchar,
  text,
  boolean,
  timestamp,
  int,
  mysqlEnum
} from 'drizzle-orm/mysql-core';

export const users = mysqlTable('users', {
  id: varchar('id', { length: 36 }).primaryKey(),
  email: varchar('email', { length: 255 }).notNull().unique(),
  name: varchar('name', { length: 255 }),
  role: mysqlEnum('role', ['user', 'admin']).default('user'),
  createdAt: timestamp('created_at').defaultNow(),
  updatedAt: timestamp('updated_at').defaultNow().onUpdateNow()
});

export const posts = mysqlTable('posts', {
  id: varchar('id', { length: 36 }).primaryKey(),
  title: varchar('title', { length: 255 }).notNull(),
  content: text('content'),
  slug: varchar('slug', { length: 255 }).notNull().unique(),
  published: boolean('published').default(false),
  authorId: varchar('author_id', { length: 36 }).notNull(),
  // Kein FOREIGN KEY - PlanetScale Design Decision
  createdAt: timestamp('created_at').defaultNow(),
  updatedAt: timestamp('updated_at').defaultNow().onUpdateNow()
});

// Index für Performance
export const postsAuthorIdx = mysqlTable('posts', {
  authorId: varchar('author_id', { length: 36 })
}).indexes((t) => ({
  authorIdx: index('author_idx').on(t.authorId)
}));
```

---

## CRUD Operations

```typescript
import { db } from '@/db';
import { users, posts } from '@/db/schema';
import { eq, and, desc, like, sql } from 'drizzle-orm';
import { nanoid } from 'nanoid';

// CREATE
async function createUser(email: string, name: string) {
  const id = nanoid();

  await db.insert(users).values({
    id,
    email,
    name
  });

  return { id, email, name };
}

// READ
async function getUserById(id: string) {
  const [user] = await db
    .select()
    .from(users)
    .where(eq(users.id, id));
  return user;
}

// Manueller JOIN (ohne Foreign Keys)
async function getPostsWithAuthors() {
  return await db
    .select({
      post: posts,
      author: {
        id: users.id,
        name: users.name,
        email: users.email
      }
    })
    .from(posts)
    .innerJoin(users, eq(posts.authorId, users.id))
    .where(eq(posts.published, true))
    .orderBy(desc(posts.createdAt));
}

// UPDATE
async function updatePost(id: string, data: Partial<typeof posts.$inferInsert>) {
  await db
    .update(posts)
    .set(data)
    .where(eq(posts.id, id));
}

// DELETE
async function deletePost(id: string) {
  await db.delete(posts).where(eq(posts.id, id));
}

// Pagination
async function getPostsPaginated(page: number, pageSize: number = 20) {
  const offset = (page - 1) * pageSize;

  const [results, countResult] = await Promise.all([
    db
      .select()
      .from(posts)
      .where(eq(posts.published, true))
      .orderBy(desc(posts.createdAt))
      .limit(pageSize)
      .offset(offset),

    db
      .select({ count: sql<number>`count(*)` })
      .from(posts)
      .where(eq(posts.published, true))
  ]);

  return {
    posts: results,
    total: countResult[0].count,
    page,
    pageSize,
    totalPages: Math.ceil(countResult[0].count / pageSize)
  };
}
```

---

## Database Branching Workflow

```bash
# 1. Feature Branch erstellen
pscale branch create my-app add-comments

# 2. Lokal verbinden
pscale connect my-app add-comments --port 3310

# 3. Schema ändern (auf Branch)
# In Code oder SQL:
```

```typescript
// Schema-Migration auf Branch
await db.execute(sql`
  CREATE TABLE comments (
    id VARCHAR(36) PRIMARY KEY,
    content TEXT NOT NULL,
    post_id VARCHAR(36) NOT NULL,
    author_id VARCHAR(36) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    INDEX post_idx (post_id),
    INDEX author_idx (author_id)
  )
`);
```

```bash
# 4. Deploy Request erstellen
pscale deploy-request create my-app add-comments

# 5. Review in Dashboard
# Schema Diff wird angezeigt

# 6. Deploy (Non-Blocking!)
pscale deploy-request deploy my-app 1

# 7. Branch löschen
pscale branch delete my-app add-comments
```

---

## Non-Blocking Schema Changes

```sql
-- Diese Änderungen blockieren NICHT die Tabelle:

-- Column hinzufügen
ALTER TABLE users ADD COLUMN avatar_url VARCHAR(255);

-- Index hinzufügen (online)
ALTER TABLE posts ADD INDEX slug_idx (slug);

-- Column umbenennen (via Ghost Tables)
-- PlanetScale kopiert Daten im Hintergrund

-- Achtung: Foreign Keys nicht unterstützt!
-- Stattdessen: Application-Level Referential Integrity
```

---

## PlanetScale Insights

```typescript
// Query Performance analysieren
// In PlanetScale Dashboard: Insights Tab

// Slow Queries finden
// - Queries > 100ms
// - Table Scans
// - Missing Indexes

// Beispiel: Index für häufige Query
await db.execute(sql`
  CREATE INDEX posts_published_created_idx
  ON posts (published, created_at DESC)
`);

// Composite Index für Filter + Sort
await db.execute(sql`
  CREATE INDEX posts_author_published_idx
  ON posts (author_id, published, created_at DESC)
`);
```

---

## Edge-Compatible Driver

```typescript
// Cloudflare Workers / Vercel Edge
import { connect } from '@planetscale/database';

export const runtime = 'edge';

export async function GET() {
  const conn = connect({
    host: process.env.DATABASE_HOST,
    username: process.env.DATABASE_USERNAME,
    password: process.env.DATABASE_PASSWORD
  });

  const results = await conn.execute(
    'SELECT * FROM posts WHERE published = ? ORDER BY created_at DESC LIMIT 10',
    [true]
  );

  return Response.json(results.rows);
}
```

---

## Referential Integrity ohne Foreign Keys

```typescript
// Application-Level Constraints

// Bei Insert: Prüfen ob Author existiert
async function createPost(data: NewPost) {
  const [author] = await db
    .select({ id: users.id })
    .from(users)
    .where(eq(users.id, data.authorId));

  if (!author) {
    throw new Error('Author not found');
  }

  return await db.insert(posts).values(data);
}

// Bei Delete: Cascade manuell
async function deleteUser(userId: string) {
  // Erst abhängige Daten löschen
  await db.delete(posts).where(eq(posts.authorId, userId));
  await db.delete(comments).where(eq(comments.authorId, userId));

  // Dann User
  await db.delete(users).where(eq(users.id, userId));
}

// Oder: Soft Delete Pattern
const users = mysqlTable('users', {
  // ...
  deletedAt: timestamp('deleted_at')
});
```

---

## Fazit

PlanetScale bietet:

1. **Vitess-Powered**: YouTube-Scale für MySQL
2. **Database Branching**: Git-Workflow für Datenbanken
3. **Zero-Downtime Migrations**: Non-Blocking Schema Changes
4. **Edge-Ready**: Fetch-basierter Driver für Serverless

Enterprise MySQL-Performance mit Developer-Friendly Workflow.

---

## Bildprompts

1. "Database branches merging like Git, version control for databases"
2. "MySQL scaling horizontally across servers, Vitess sharding concept"
3. "Schema migration without downtime, zero-interruption deployment"

---

## Quellen

- [PlanetScale Documentation](https://planetscale.com/docs)
- [PlanetScale Branching](https://planetscale.com/docs/vitess/schema-changes/branching)
- [Vitess Overview](https://vitess.io/)
- [PlanetScale Database Driver](https://github.com/planetscale/database-js)
