system-design67 min read

How to Design PlanetScale - Serverless MySQL Platform — A Senior+ Guide

How to Design PlanetScale — Serverless MySQL Platform

A Senior+ Guide to Building a Git-like Database Platform on Vitess

Article #199 Published: June 4, 2024 ~35 min read

1. Introduction: PlanetScale at Scale

PlanetScale represents one of the most ambitious projects in the modern database infrastructure space. Founded in 2018 by former GitHub, Square, and Google engineers, PlanetScale reimagines how teams interact with MySQL databases by bringing Git-like workflows to database schema management. Built on top of Vitess — the open-source MySQL clustering system originally developed at YouTube — PlanetScale delivers a fully managed, serverless MySQL platform that scales horizontally without sacrificing compatibility with the vast MySQL ecosystem.

The core insight behind PlanetScale is profound: software development teams have spent decades perfecting version control workflows for application code, yet database schema changes remain a dangerous, manual, and error-prone process. By applying the principles of Git branching, pull requests, and code review to database schemas, PlanetScale eliminates the fear traditionally associated with production database changes. Developers can create schema branches, make changes in isolation, review those changes with teammates, and deploy them to production through a controlled, reversible process — all without downtime.

At its technical foundation, PlanetScale leverages Vitess to provide horizontal sharding transparently. Vitess was originally built at YouTube to handle billions of queries per day across thousands of MySQL instances, and PlanetScale has productized this battle-tested technology into a cloud-native service. When a customer creates a database on PlanetScale, they interact with what appears to be a single MySQL endpoint, but behind the scenes, Vitess distributes data across multiple shards, manages connection pooling, handles query routing, and coordinates schema changes across the entire cluster.

The serverless nature of PlanetScale means that customers never need to worry about provisioning servers, configuring replication, managing failover, or tuning connection pools. The platform automatically scales compute resources based on demand, charges only for actual usage, and provides built-in high availability through multi-zone replication. This model fundamentally changes the economics and operational burden of running MySQL at scale, making it accessible to startups running on a free tier as well as enterprises processing millions of transactions per second.

What makes PlanetScale particularly compelling in the current landscape is its approach to developer experience. Unlike traditional database-as-a-service offerings that focus primarily on operations teams, PlanetScale is designed for developers. The CLI tool, web console, and API all emphasize workflow integration — connecting to CI/CD pipelines, supporting GitHub integration for deploy requests, and providing detailed query insights that help developers understand and optimize their data access patterns. The platform essentially bridges the gap between application development and database operations, creating a unified experience that accelerates development velocity while maintaining production rigor.

In this comprehensive system design guide, we will dissect every major component of PlanetScale's architecture, from the Vitess foundation through schema branching, deploy requests, connection management, query insights, online DDL, replication, backup strategies, multi-region deployment, and the billing infrastructure. Each section provides both the conceptual framework and the implementation details necessary for senior engineers to understand, design, and potentially build similar systems. We will explore the trade-offs PlanetScale makes, the constraints it operates within, and the engineering decisions that shape its behavior.

Whether you are preparing for a system design interview at a database infrastructure company, evaluating PlanetScale for your organization's migration from self-hosted MySQL, or simply seeking to understand how world-class database platforms are engineered, this guide provides the depth and breadth of coverage you need. The diagrams, code examples, tables, and architectural discussions are designed to give you both the big picture and the implementation-level detail that senior+ roles demand.

Why PlanetScale Matters

The significance of PlanetScale extends beyond its technical capabilities. It represents a paradigm shift in how we think about database management. For decades, the database was the domain of specialized DBAs who guarded production systems with carefully guarded access controls andChange Advisory Board approvals. PlanetScale democratizes this process, empowering application developers to manage schemas as confidently as they manage application code. This cultural shift, enabled by technical innovation, is arguably as important as the technical architecture itself.

The platform also addresses a critical pain point in the modern stack: the impedance mismatch between horizontally scalable NoSQL systems and the relational data model that most applications require. By providing horizontal scalability through Vitess while maintaining full MySQL compatibility, PlanetScale offers teams the best of both worlds — the familiar SQL interface and ACID transactions of MySQL, combined with the virtually unlimited horizontal scaling of a distributed system.

2. Platform Overview

PlanetScale's platform is organized around a clear hierarchy of concepts that mirror software development workflows. At the top level, an organization owns one or more databases. Each database contains one or more branches, with the primary branch (typically named main) representing the production schema. Developers create additional branches to make schema changes in isolation, submit deploy requests to propose changes to production, and leverage insights to monitor and optimize query performance.

Core Concepts

The database is the fundamental unit of organization in PlanetScale. When a customer creates a database, they specify a name, a cloud provider region, and an initial configuration. The platform provisions the necessary Vitess cluster infrastructure, configures sharding (if applicable based on the expected data volume), and provides a MySQL-compatible connection endpoint. The customer interacts with this endpoint using any standard MySQL client, driver, or ORM — no special connection libraries are required for basic usage.

Branches in PlanetScale serve the same purpose as branches in Git. The primary branch contains the production schema and is the target for all deploy requests. Developers create feature branches to experiment with schema changes, add new tables, modify existing columns, or drop unnecessary structures. Each branch maintains its own isolated schema definition, and changes to one branch do not affect others. This isolation is critical for enabling parallel development workflows where multiple team members may be working on different features simultaneously.

Deploy requests are PlanetScale's equivalent of pull requests for database schemas. When a developer is ready to apply changes from a feature branch to production, they create a deploy request. This triggers a comprehensive analysis process that evaluates the proposed changes for potential issues, including destructive operations, index changes that could cause locking, and compatibility concerns. The deploy request provides a detailed diff of the schema changes, shows the impact on existing queries, and allows team members to review and discuss the changes before they are applied to production.

The insights engine is one of PlanetScale's most valuable differentiators. It continuously analyzes query patterns, identifies slow queries, provides index recommendations, and tracks query performance over time. This telemetry data is surfaced through the web console, CLI, and API, giving developers unprecedented visibility into how their application's data access patterns interact with the database schema. The insights engine essentially brings the expertise of a senior DBA to every developer on the team.

Platform Components at a Glance

ComponentDescriptionKey Technology
Database EngineMySQL-compatible, distributed across shardsVitess + MySQL
Schema ManagementGit-like branching and versioningCustom schema store + Vitess DDL
Deploy PipelineSchema review and progressive rolloutgh-ost + Vitess Online DDL
Connection LayerServerless connection pooling and routingVitess vtgate + PlanetScale proxy
Insights EngineQuery analysis and optimization recommendationsCustom analytics pipeline
Backup SystemContinuous backups with point-in-time recoveryVitess backup + cloud storage
Multi-RegionRead replicas across geographic regionsVitess replicas + Vitess Operator

The Developer Workflow

A typical PlanetScale workflow begins with creating a new branch from the primary branch. The developer makes schema changes locally using the pscale CLI tool or through the web interface. These changes are tracked as a series of DDL statements that define the branch's schema relative to its parent. When the developer is ready, they push the branch to PlanetScale and create a deploy request. The platform analyzes the changes, runs them through a compatibility check, and presents the results for review. Once approved, the changes are applied to the primary branch using Vitess's online DDL mechanism, which ensures zero-downtime schema changes even on large tables.

This workflow provides several critical benefits over traditional database change management. First, it eliminates the risk of applying untested schema changes directly to production. Second, it creates an auditable history of every schema change, including who made the change, when it was made, and what it looked like. Third, it enables collaboration on schema design, allowing team members to comment on and discuss proposed changes before they are applied. Fourth, it integrates with CI/CD pipelines, enabling automated testing of schema changes against realistic data sets.

The platform also supports features like column-level recommendations, query statistics, and explain plan analysis. When a developer submits a deploy request, PlanetScale can show which existing queries will be affected by the proposed changes, whether any queries will break, and whether the changes will improve or degrade performance. This level of analysis is typically only available through expensive database monitoring tools, yet PlanetScale provides it as a built-in feature of its platform.

Pricing Model

PlanetScale operates on a usage-based pricing model that charges for storage, rows read, rows written, and branching compute. The free tier (Hobby) provides enough capacity for small projects and prototyping, while paid tiers scale up to handle enterprise workloads. This pricing model aligns costs with actual usage, eliminating the waste associated with over-provisioned database servers. Customers pay for what they use, and the platform handles all capacity planning automatically.

3. System Architecture Overview

The PlanetScale platform is a distributed system composed of multiple layers, each responsible for specific aspects of the database service. Understanding the architecture requires examining how these layers interact, what protocols they use for communication, and how data flows through the system from client request to storage and back.

graph TB subgraph "Client Layer" APP[Application Servers] CLI[pscale CLI] WEB[Web Console] end subgraph "API Gateway" GW[API Gateway / Load Balancer] AUTH[Authentication Service] RATE[Rate Limiter] end subgraph "Control Plane" SCHEMA[Schema Management Service] DEPLOY[Deploy Request Service] INSIGHTS[Insights Analytics Service] BILLING[Billing & Metering Service] end subgraph "Data Plane" PROXY[PlanetScale Proxy] VTGATE[vtgate Query Router] VTTABLET[vttablet Shards] subgraph "Storage Layer" MYSQL1[(MySQL Primary)] MYSQL2[(MySQL Replica)] MYSQL3[(MySQL Replica)] end end subgraph "Infrastructure" K8S[Kubernetes Cluster] ETCD[etcd Metadata Store] CONSUL[Service Discovery] end APP --> GW CLI --> GW WEB --> GW GW --> AUTH GW --> RATE GW --> VTGATE GW --> SCHEMA SCHEMA --> DEPLOY SCHEMA --> VTTABLET DEPLOY --> VTTABLET INSIGHTS --> VTTABLET VTGATE --> VTTABLET VTTABLET --> MYSQL1 MYSQL1 --> MYSQL2 MYSQL1 --> MYSQL3 K8S --> VTTABLET ETCD --> VTGATE ETCD --> VTTABLET BILLING --> INSIGHTS

Layer-by-Layer Breakdown

The Client Layer represents all the ways customers interact with PlanetScale. Applications connect using standard MySQL protocol through any language's MySQL driver. The pscale CLI provides command-line access for schema management, branch operations, and debugging. The web console offers a rich graphical interface for managing databases, reviewing deploy requests, and exploring insights. All three interfaces converge on the same backend services, ensuring consistent behavior regardless of the access method.

The API Gateway serves as the entry point for all control plane operations. It handles authentication, authorization, rate limiting, request routing, and protocol translation. For the data plane, client connections are routed directly to the PlanetScale proxy layer, bypassing the API gateway entirely to minimize latency. The gateway communicates with the control plane services over gRPC, providing efficient inter-service communication for operations like branch creation, deploy request management, and billing metering.

The Control Plane consists of several microservices that manage the platform's metadata and operational workflows. The Schema Management Service maintains the canonical schema definitions for all branches, tracks DDL history, and coordinates schema changes across Vitess clusters. The Deploy Request Service manages the review workflow, including change analysis, compatibility checking, and progressive rollout. The Insights Analytics Service processes query logs and telemetry data to provide performance recommendations. The Billing and Metering Service tracks usage metrics and generates invoices.

The Data Plane is where customer data actually lives and is processed. The PlanetScale Proxy is a custom-built connection pooler and protocol handler that manages MySQL connections between clients and the Vitess query router. The vtgate layer routes queries to the appropriate vttablet instances based on the query's target shard. Each vttablet manages one or more MySQL instances, handling query execution, transaction management, and replication coordination. The MySQL instances store the actual data on disk using InnoDB.

The Infrastructure Layer provides the foundation for all other layers. Kubernetes orchestrates the deployment and scaling of all services. etcd stores cluster metadata, including shard mappings, tablet assignments, and schema versions. Service discovery ensures that components can find each other reliably even as instances are created and destroyed by Kubernetes.

Data Flow for a Typical Query

When an application executes a SQL query against PlanetScale, the request follows a well-defined path through the system. First, the MySQL client establishes a connection to the PlanetScale proxy, which authenticates the connection using the customer's credentials and assigns it to a connection pool. The query is then forwarded to the vtgate layer, which parses the SQL to determine the target shard. If the query includes a shard key (the column used for horizontal partitioning), vtgate routes it directly to the appropriate vttablet. If no shard key is specified, vtgate may need to scatter the query across all shards and aggregate the results.

The vttablet receives the query, applies any necessary query rewriting, and executes it against the local MySQL instance. Results are streamed back through the vttablet to vtgate, which performs any necessary aggregation or ordering before returning the results to the client. Throughout this process, the system collects telemetry data including query latency, rows examined, rows returned, and error rates. This data feeds into the insights pipeline for later analysis.

Architecture Comparison

AspectTraditional MySQLPlanetScaleTrade-off
ScalingVertical only (bigger server)Horizontal (more shards)Sharding complexity vs. capacity
Schema ChangesManual DDL with downtime riskOnline DDL with branchingAbstraction overhead vs. safety
High AvailabilityManual failover setupAutomatic multi-zone replicationCost vs. operational burden
Connection ManagementApplication-managed poolsServerless connection poolingLatency vs. connection limits
Query InsightsExternal monitoring toolsBuilt-in analyticsVendor lock-in vs. convenience
Backupmysqldump / xtrabackupContinuous automatic backupsStorage cost vs. recovery time

4. Vitess Foundation

PlanetScale is built on Vitess, and understanding Vitess's architecture is essential to understanding PlanetScale. Vitess was originally developed at YouTube in 2010 to solve a specific problem: YouTube's MySQL deployment had grown to thousands of instances, and managing them manually was no longer feasible. The Vitess project created a middleware layer that abstracted the complexity of MySQL sharding, connection management, and query routing behind a single MySQL-compatible endpoint.

Core Vitess Components

Vitess is composed of several key components that work together to provide a distributed MySQL-compatible database system. The vtgate (Vitess Gateway) is the query router that sits between clients and the storage layer. It parses incoming SQL queries, determines which shard(s) need to be queried, and routes the query accordingly. vtgate also handles connection pooling, query rewriting, and result aggregation for cross-shard queries. In a PlanetScale deployment, clients connect to vtgate as if it were a MySQL server, and the vtgate layer handles all the complexity of distributed query execution.

The vttablet (Vitess Tablet) is the per-shard component that manages one or more MySQL instances. Each vttablet acts as a proxy between vtgate and the underlying MySQL server, providing connection pooling, query execution, transaction management, and replication coordination. vttablet also implements Vitess's online DDL mechanism, which allows schema changes to be applied without blocking production traffic. In PlanetScale's architecture, each customer's database is backed by a set of vttablet instances, one per shard, running on Kubernetes.

The vtctld (Vitess Control Daemon) is the management component that handles cluster topology, schema management, and operational commands. It maintains the mapping of keyspaces to shards, tablets to MySQL instances, and provides APIs for creating, splitting, and merging shards. In PlanetScale, vtctld is wrapped by the platform's schema management service, which adds the branching and deploy request workflows on top of Vitess's native DDL capabilities.

graph LR subgraph "Vitess Query Path" CLIENT[MySQL Client] --> VTGATE[vtgate] VTGATE --> PARSE[Query Parser] PARSE --> ROUTE{Shard Routing} ROUTE -->|Shard 0| VT0[vttablet 0] ROUTE -->|Shard 1| VT1[vttablet 1] ROUTE -->|Shard 2| VT2[vttablet 2] VT0 --> MY0[(MySQL 0)] VT1 --> MY1[(MySQL 1)] VT2 --> MY2[(MySQL 2)] end

Sharding in Vitess

Vitess implements range-based sharding, where each shard owns a contiguous range of the shard key's value space. The shard key is specified when a table is created and determines how rows are distributed across shards. For example, if a users table is sharded on the user_id column with three shards, shard 0 might own user IDs 0-333, shard 1 might own 334-666, and shard 2 might own 667-999. The shard mapping is stored in etcd and cached in vtgate, allowing the query router to determine the target shard with minimal latency overhead.

When a query includes the shard key in its WHERE clause, vtgate can route it directly to a single shard, making it as efficient as a query against a single MySQL instance. This is the optimal query pattern for Vitess, and PlanetScale encourages customers to design their schemas such that the most common access patterns include the shard key. When a query does not include the shard key, vtgate must scatter the query across all shards and aggregate the results, which is significantly more expensive.

Connection Pooling in Vitess

One of Vitess's most important features is its connection pooling mechanism. In a traditional MySQL deployment, each application server maintains a pool of connections to the database server. These connections are expensive to create and limited in number — MySQL's default max_connections is 151, and increasing it too much can cause performance degradation due to memory usage and thread contention. Vitess solves this problem by pooling connections between vtgate and vttablet, allowing thousands of client connections to share a smaller number of MySQL connections.

The pooling works at two levels. First, vtgate maintains a pool of connections to each vttablet. When a client sends a query, vtgate selects an available connection from the pool, executes the query, and returns the connection to the pool. This means that even if thousands of clients are connected to vtgate, the number of connections between vtgate and vttablet can be kept small. Second, each vttablet maintains a pool of connections to its MySQL instance, further reducing the total number of MySQL connections required.

Key Vitess Concepts for PlanetScale

ConceptDescriptionPlanetScale Usage
KeyspaceLogical database that maps to one or more shardsMaps to a PlanetScale database
ShardHorizontal partition of a keyspaceAuto-managed based on data size
TabletProcess managing one MySQL instanceRuns on Kubernetes pods
Shard KeyColumn used for row distributionRequired for sharded tables
VStreamChange data capture from MySQL binlogPowers real-time streaming and replicas
VTGateQuery router and connection poolerFronts all data plane queries

Vitess vs. Alternative Sharding Solutions

Compared to other MySQL sharding solutions, Vitess provides several distinct advantages. ProxySQL requires manual shard configuration and does not provide online DDL or schema versioning. MySQL's built-in Partitioning is limited to a single server and does not provide horizontal scalability. Vitess, by contrast, provides a complete solution that handles sharding, connection pooling, query routing, online DDL, and replication coordination. This comprehensive feature set is what makes it possible for PlanetScale to offer a fully managed database service that truly abstracts the complexity of distributed MySQL.

However, Vitess also introduces certain constraints. Queries that span multiple shards without including the shard key are more expensive than single-shard queries. Transactions that modify rows across multiple shards require special handling and may have higher latency. Schema changes that affect the shard key require resharding, which is a complex operation. PlanetScale manages many of these complexities automatically, but understanding the underlying Vitess constraints is essential for designing schemas and queries that perform well on the platform.

5. Schema Management and Branching

Schema management is arguably PlanetScale's most distinctive feature, and it represents a fundamental rethinking of how database schema changes are handled in production. Traditional database schema management involves a DBA or developer writing DDL (Data Definition Language) statements, testing them against a staging environment, and then carefully applying them to production during a maintenance window. This process is slow, error-prone, and creates anxiety for everyone involved. PlanetScale replaces this entire workflow with a Git-like branching model that makes schema changes as routine and safe as code changes.

The Branching Model

In PlanetScale, every database has a primary branch (typically named main) that represents the current production schema. When a developer wants to make schema changes, they create a new branch from the primary branch. This branch contains an isolated copy of the schema that can be modified independently. The developer makes changes using standard MySQL DDL statements — CREATE TABLE, ALTER TABLE, DROP TABLE, and so on — and these changes are recorded as a series of schema diffs relative to the parent branch.

The branching model supports several important workflows. Feature branches allow developers to add new tables or columns without affecting the production schema. Bug fix branches enable quick schema patches. Experiment branches allow testing of schema changes that may or may not be kept. Each branch tracks its full history of changes, making it easy to understand how the schema evolved over time and to roll back changes if necessary.

Schema Storage and Versioning

PlanetScale stores schema definitions in a centralized schema store that tracks the complete history of every branch. When a developer creates a new branch, the platform records the parent branch's schema at that point in time as the branch's starting point. Subsequent DDL operations on the branch are recorded as incremental changes, similar to how Git stores commits. This approach means that reconstructing the schema for any branch at any point in time is a matter of replaying the DDL history from the branch's creation point.

The schema store also maintains metadata about each change, including the DDL statement, the timestamp, the author, and the deployment status. This metadata powers the deploy request workflow, the audit log, and the schema diffing algorithm that generates the human-readable comparison between branches.

C#
// SchemaChange represents a single DDL operation on a branch
public class SchemaChange
{
    public string Id { get; set; }
    public string DatabaseName { get; set; }
    public string BranchName { get; set; }
    public string DdlStatement { get; set; }
    public string Author { get; set; }
    public DateTime CreatedAt { get; set; }
    public SchemaChangeStatus Status { get; set; }
    public string ParentChangeId { get; set; }
    public string SchemaHash { get; set; }
}

public enum SchemaChangeStatus
{
    Pending,
    Applied,
    Failed,
    RolledBack
}

// SchemaStore manages the versioned schema definitions
public class SchemaStore
{
    private readonly IKeyValueStore _metadataStore;
    private readonly IDdlParser _ddlParser;

    public async Task<SchemaSnapshot> GetSchemaAtPointInTime(
        string databaseName, string branchName, DateTime timestamp)
    {
        var changes = await _metadataStore.GetChanges(
            databaseName, branchName, before: timestamp);
        
        var schema = new SchemaSnapshot();
        foreach (var change in changes.Where(c => c.Status == SchemaChangeStatus.Applied))
        {
            var parsedDdl = _ddlParser.Parse(change.DdlStatement);
            schema.Apply(parsedDdl);
        }
        return schema;
    }

    public async Task<SchemaDiff> DiffBranches(
        string databaseName, string sourceBranch, string targetBranch)
    {
        var sourceSchema = await GetCurrentSchema(databaseName, sourceBranch);
        var targetSchema = await GetCurrentSchema(databaseName, targetBranch);
        return SchemaDiff.Compare(sourceSchema, targetSchema);
    }
}

Branch Operations

PlanetScale supports several operations on branches, each designed to support a specific part of the development workflow. Create Branch creates a new branch from an existing branch (typically the primary). Delete Branch removes a branch and its schema history, freeing up resources. Promote Branch changes a branch's role, for example promoting a development branch to be the new primary. Diff Branches shows the schema differences between two branches, similar to git diff.

The most important operation is Deploy Request, which proposes merging schema changes from one branch into the primary branch. We will cover deploy requests in detail in the next section, but it is important to note that this operation is the bridge between the isolated development environment and production. The deploy request triggers a comprehensive analysis of the proposed changes, ensures compatibility with existing queries, and applies the changes using online DDL to avoid downtime.

Schema Design Patterns

PlanetScale encourages several schema design patterns that are particularly well-suited to its branching and sharding model. First, the platform recommends using a shard key on tables that will exceed single-shard capacity. The shard key should be chosen based on the most common access patterns — a table that is primarily accessed by user ID should be sharded on user_id, for example. Second, the platform encourages denormalization where appropriate, since cross-shard joins are expensive. Third, it supports schema-first development, where the database schema is designed and reviewed before application code is written.

Branching Workflow Example

gitgraph commit id: "Initial Schema" branch add-orders-table checkout add-orders-table commit id: "Create orders table" commit id: "Add indexes" checkout main commit id: "Fix users column" checkout add-orders-table commit id: "Deploy Request" checkout main merge add-orders-table id: "Merge orders schema"

6. Deploy Requests

Deploy requests are the mechanism through which schema changes are promoted from development branches to production. They represent one of PlanetScale's most innovative features, providing a structured, auditable, and reversible process for applying database schema changes. Understanding the deploy request workflow is essential for anyone designing or operating a database platform, as it addresses many of the risks and pain points associated with traditional database change management.

The Deploy Request Lifecycle

A deploy request goes through several stages from creation to completion. When a developer creates a deploy request, the platform captures the current state of the source branch's schema and compares it with the target branch (usually the primary branch). This comparison produces a detailed diff showing every table, column, index, and constraint that will be added, modified, or removed. The diff is presented in a human-readable format that highlights the specific DDL statements that will be executed.

Once the deploy request is created, the platform runs a comprehensive analysis of the proposed changes. This analysis includes several checks: syntax validation ensures the DDL statements are valid MySQL; destructive operation detection identifies drops and renames that could cause data loss; index impact analysis evaluates how proposed index changes will affect query performance; and query compatibility checking verifies that existing queries will continue to work with the proposed schema. The results of this analysis are displayed on the deploy request page, giving reviewers a clear picture of the change's impact.

Reviewers can then examine the changes, leave comments, request modifications, or approve the deploy request. The approval process supports multiple reviewers and can be configured to require specific approvals before the changes are applied. Once approved, the changes are applied to the primary branch using Vitess's online DDL mechanism, which ensures that the DDL statements execute without blocking production traffic. The deploy request is then marked as deployed and becomes part of the schema history.

Schema Analysis During Deploy Requests

The analysis performed during a deploy request is sophisticated and goes beyond simple syntax checking. PlanetScale's analysis engine examines each proposed DDL statement in the context of the current production schema and the application's query patterns. For example, if a developer proposes adding an index to a large table, the analysis will estimate the time required to build the index, estimate the impact on write performance during the build, and identify any queries that would benefit from the new index.

Conversely, if a developer proposes removing a column, the analysis will check whether any tracked queries reference that column. If so, the deploy request will be flagged as potentially breaking, and the reviewer will be warned about the specific queries that would be affected. This level of analysis is possible because PlanetScale tracks query patterns through its insights engine, which continuously monitors the traffic flowing through the vtgate layer.

C#
// DeployRequest represents a proposed schema change from one branch to another
public class DeployRequest
{
    public string Id { get; set; }
    public string DatabaseName { get; set; }
    public string SourceBranch { get; set; }
    public string TargetBranch { get; set; }
    public string Title { get; set; }
    public string Description { get; set; }
    public string Author { get; set; }
    public DateTime CreatedAt { get; set; }
    public DeployRequestStatus Status { get; set; }
    public List<SchemaAnalysisResult> AnalysisResults { get; set; }
    public List<DeployRequestReview> Reviews { get; set; }
}

// DeployRequestAnalyzer performs comprehensive analysis of proposed schema changes
public class DeployRequestAnalyzer
{
    private readonly ISchemaStore _schemaStore;
    private readonly IQueryInsightsStore _insightsStore;
    private readonly IDdlExecutor _ddlExecutor;

    public async Task<DeployRequestAnalysis> Analyze(DeployRequest request)
    {
        var sourceSchema = await _schemaStore.GetCurrentSchema(
            request.DatabaseName, request.SourceBranch);
        var targetSchema = await _schemaStore.GetCurrentSchema(
            request.DatabaseName, request.TargetBranch);
        
        var diff = SchemaDiff.Compare(targetSchema, sourceSchema);
        var analysis = new DeployRequestAnalysis { Diff = diff };

        foreach (var change in diff.Changes)
        {
            var result = new SchemaAnalysisResult
            {
                Change = change,
                IsDestructive = IsDestructiveOperation(change),
                EstimatedDuration = await EstimateExecutionTime(change),
                AffectedQueries = await FindAffectedQueries(
                    request.DatabaseName, change),
                IndexImpact = await AnalyzeIndexImpact(change),
                BreakingChanges = await DetectBreakingChanges(
                    request.DatabaseName, change)
            };
            analysis.Results.Add(result);
        }

        return analysis;
    }

    private bool IsDestructiveOperation(SchemaChange change)
    {
        return change.Type == SchemaChangeType.DropTable ||
               change.Type == SchemaChangeType.DropColumn ||
               change.Type == SchemaChangeType.DropIndex ||
               change.Type == SchemaChangeType.RenameColumn;
    }
}

Non-Blocking Schema Changes

One of the most important aspects of PlanetScale's deploy request system is that it applies schema changes without blocking production traffic. This is achieved through Vitess's online DDL mechanism, which we will cover in detail in the Online DDL section. In summary, instead of acquiring a metadata lock and blocking all DML (Data Manipulation Language) operations while the DDL executes, Vitess performs the schema change in the background while the production system continues to serve traffic. This means that even large schema changes — such as adding an index to a table with hundreds of millions of rows — can be applied without any perceptible impact on application performance.

Deploy Request Best Practices

PlanetScale recommends several best practices for using deploy requests effectively. First, keep deploy requests small and focused — each deploy request should address a single logical change, making it easier to review and easier to roll back if needed. Second, write descriptive titles and descriptions for deploy requests, explaining the motivation for the change and any relevant context. Third, review deploy requests promptly to avoid blocking other team members' work. Fourth, use the analysis results to identify potential issues before they affect production, and address any warnings before approving the deploy request.

Deploy Request Status Flow

StatusDescriptionAllowed Actions
OpenDeploy request has been created and is awaiting reviewEdit, Comment, Approve, Close
AnalyzingPlatform is analyzing the proposed changesComment, Close
ApprovedAll required approvals have been obtainedDeploy, Close
DeployingChanges are being applied to the target branchView progress
DeployedChanges have been successfully appliedView details
ClosedDeploy request was closed without deployingReopen
FailedDeployment failed due to an errorView error, Retry

Integration with CI/CD

PlanetScale provides a robust API and CLI tooling that enables integration with CI/CD pipelines. Teams can automate the creation of deploy requests as part of their development workflow, automatically run tests against proposed schema changes, and even auto-merge deploy requests when all checks pass. This integration is critical for organizations that want to maintain high development velocity while ensuring database schema changes are properly reviewed and tested.

7. Connection Management

Connection management is one of the most challenging aspects of operating MySQL at scale, and PlanetScale addresses this challenge through a sophisticated multi-layered connection pooling architecture. Traditional MySQL deployments face a fundamental limitation: each client connection requires a dedicated thread on the MySQL server, consuming memory and CPU resources. A server with 16GB of RAM might support only a few hundred connections before performance degrades significantly. PlanetScale's connection management system overcomes this limitation by intelligently multiplexing thousands of client connections across a smaller pool of MySQL connections.

The Connection Multiplexing Architecture

PlanetScale's connection architecture consists of three layers: the client-facing proxy, the vtgate query router, and the vttablet connection pooler. When an application connects to PlanetScale, it establishes a MySQL connection to the PlanetScale proxy, which authenticates the connection and assigns it to a session. The session maintains the client's state, including the current database, session variables, and transaction context. The actual query execution happens on a shared pool of connections between vtgate and vttablet, which are reused across multiple client sessions.

This multiplexing approach provides several benefits. First, it dramatically reduces the number of MySQL connections required. A PlanetScale database serving thousands of concurrent clients might only need a few dozen MySQL connections, since client connections are not held open while waiting for results. Second, it enables automatic connection failover — if a vttablet instance becomes unavailable, the proxy layer can transparently route requests to a healthy instance without the client being aware of the failure. Third, it enables serverless scaling — the connection pool can grow or shrink based on demand, and clients do not need to manage connection limits or handle connection exhaustion errors.

graph TB subgraph "Client Applications" C1[App Server 1] C2[App Server 2] C3[App Server 3] C4[Serverless Function] end subgraph "PlanetScale Proxy" AUTH[Auth Handler] SESSION[Session Manager] POOL1[Connection Pool - Branch A] POOL2[Connection Pool - Branch B] end subgraph "vtgate Cluster" VG1[vtgate 1] VG2[vtgate 2] VG3[vtgate 3] end subgraph "vttablet Cluster" VT0[vttablet Shard 0] VT1[vttablet Shard 1] VT2[vttablet Shard 2] end C1 --> AUTH C2 --> AUTH C3 --> AUTH C4 --> AUTH AUTH --> SESSION SESSION --> POOL1 SESSION --> POOL2 POOL1 --> VG1 POOL1 --> VG2 POOL2 --> VG2 POOL2 --> VG3 VG1 --> VT0 VG1 --> VT1 VG2 --> VT0 VG2 --> VT2 VG3 --> VT1 VG3 --> VT2

Serverless Drivers

PlanetScale provides serverless database drivers for popular programming languages that are optimized for the serverless compute model. Traditional MySQL drivers are designed for long-running server processes where connections can be established once and reused throughout the application's lifetime. Serverless compute platforms like AWS Lambda, Vercel Edge Functions, and Cloudflare Workers have much shorter execution times and may spin up thousands of concurrent instances, each needing a database connection. Traditional drivers would quickly exhaust the connection limit under these conditions.

The PlanetScale serverless driver addresses this by using HTTP-based connection multiplexing instead of persistent TCP connections. Each query is sent as an individual HTTP request to the PlanetScale proxy, which processes it and returns the results. This approach eliminates the need for persistent connections, allowing serverless functions to query the database without consuming connection pool resources. The driver also includes connection pooling and caching features that minimize latency for common query patterns.

C#
// PlanetScale connection configuration using the serverless driver pattern
public class PlanetScaleConnectionConfig
{
    public string Host { get; set; }
    public int Port { get; set; } = 3306;
    public string Username { get; set; }
    public string Password { get; set; }
    public string Database { get; set; }
    public string Branch { get; set; } = "main";
    public int MaxConnections { get; set; } = 10;
    public int ConnectionTimeoutMs { get; set; } = 5000;
    public int QueryTimeoutMs { get; set; } = 30000;
    public bool UseSsl { get; set; } = true;
}

// Connection pool manager for PlanetScale
public class PlanetScaleConnectionPool : IDisposable
{
    private readonly PlanetScaleConnectionConfig _config;
    private readonly ConcurrentBag<MySqlConnection> _availableConnections;
    private readonly SemaphoreSlim _connectionSemaphore;
    private readonly Timer _healthCheckTimer;

    public PlanetScaleConnectionPool(PlanetScaleConnectionConfig config)
    {
        _config = config;
        _availableConnections = new ConcurrentBag<MySqlConnection>();
        _connectionSemaphore = new SemaphoreSlim(
            config.MaxConnections, config.MaxConnections);
        _healthCheckTimer = new Timer(
            HealthCheckConnections, null, 
            TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1));
    }

    public async Task<MySqlConnection> AcquireConnectionAsync(
        CancellationToken cancellationToken = default)
    {
        await _connectionSemaphore.WaitAsync(cancellationToken);
        
        if (_availableConnections.TryTake(out var connection))
        {
            if (connection.State == ConnectionState.Open)
                return connection;
            connection.Dispose();
        }
        
        return await CreateNewConnectionAsync();
    }

    public void ReturnConnection(MySqlConnection connection)
    {
        if (connection.State == ConnectionState.Open)
            _availableConnections.Add(connection);
        else
            connection.Dispose();
        
        _connectionSemaphore.Release();
    }

    private async Task<MySqlConnection> CreateNewConnectionAsync()
    {
        var connectionString = new MySqlConnectionStringBuilder
        {
            Server = _config.Host,
            Port = (uint)_config.Port,
            UserID = _config.Username,
            Password = _config.Password,
            Database = _config.Database,
            SslMode = _config.UseSsl ? SslMode.Required : SslMode.None,
            ConnectionTimeout = _config.ConnectionTimeoutMs / 1000,
            CommandTimeout = _config.QueryTimeoutMs / 1000
        }.ConnectionString;

        var connection = new MySqlConnection(connectionString);
        await connection.OpenAsync();
        return connection;
    }

    private void HealthCheckConnections(object state)
    {
        var healthyConnections = new ConcurrentBag<MySqlConnection>();
        while (_availableConnections.TryTake(out var connection))
        {
            try
            {
                if (connection.Ping())
                    healthyConnections.Add(connection);
                else
                    connection.Dispose();
            }
            catch
            {
                connection.Dispose();
            }
        }
        
        foreach (var conn in healthyConnections)
            _availableConnections.Add(conn);
    }

    public void Dispose()
    {
        _healthCheckTimer?.Dispose();
        _connectionSemaphore?.Dispose();
        while (_availableConnections.TryTake(out var conn))
            conn.Dispose();
    }
}

Session State Management

Managing session state in a connection-multiplexed environment is one of the trickiest aspects of PlanetScale's architecture. MySQL sessions maintain state that affects query execution, including the current database, character set, collation, transaction isolation level, and user-defined variables. In a traditional MySQL deployment, this state is maintained for the lifetime of the connection. In PlanetScale's multiplexed model, each query may execute on a different physical MySQL connection, so the session state must be tracked and applied at the vtgate layer.

PlanetScale's vtgate maintains session state for each client connection and applies it to every query before forwarding it to vttablet. When a client issues a SET command, vtgate records the new value and applies it to subsequent queries. When a client begins a transaction, vtgate ensures that all queries within that transaction are routed to the same vttablet and executed on the same underlying MySQL connection, preserving transactional consistency.

Connection Limits and Quotas

PlanMax ConnectionsConnection Pool SizeIdle Timeout
Hobby (Free)1,000105 minutes
Scaler10,0005010 minutes
Enterprise100,000+CustomConfigurable

Handling Connection Failures

PlanetScale's connection management system includes sophisticated failure handling. When a vttablet becomes unavailable due to a network partition, hardware failure, or planned maintenance, the vtgate layer detects the failure and automatically reroutes queries to a healthy replica. This failover is transparent to the client, which continues to receive responses as if nothing happened. For transactions in progress, the vtgate layer may need to retry the transaction on the new primary, which can introduce latency but preserves correctness.

The connection proxy also implements circuit breaker patterns to prevent cascading failures. If a vttablet is experiencing high error rates or latency, the circuit breaker trips and temporarily stops sending traffic to that instance, allowing it time to recover. Queries that would have been routed to the failing instance are instead sent to a healthy replica or retried after a backoff period. This pattern prevents a single failing component from overwhelming the system with retry traffic.

8. Query Performance and Insights

PlanetScale's insights engine provides deep visibility into query performance, index utilization, and schema efficiency. This feature transforms the database from a black box into a transparent system where developers can understand exactly how their queries interact with the schema. The insights engine collects telemetry data from every query that flows through the vtgate layer, aggregates this data into actionable insights, and presents it through the web console, CLI, and API. This capability is particularly valuable for teams that lack dedicated DBA resources, as it effectively democratizes database performance expertise.

Query Statistics Collection

Every query that passes through PlanetScale's vtgate layer is instrumented with detailed telemetry. The system records the query template (with parameter values normalized to protect sensitive data), execution time, rows examined, rows returned, bytes sent, errors, and the execution plan. This data is collected continuously and aggregated into time-series buckets that allow customers to track performance trends over hours, days, weeks, and months.

The query statistics pipeline uses Vitess's query logging capabilities combined with a custom analytics pipeline. The raw query logs are processed in near-real-time by a streaming analytics system that extracts key metrics, groups similar queries together, and computes aggregate statistics. The aggregated data is stored in a time-series database optimized for fast analytical queries, enabling the insights engine to answer questions like "what is the p99 latency for this query pattern over the last 7 days?" in milliseconds.

Slow Query Analysis

One of the most valuable features of the insights engine is slow query analysis. PlanetScale automatically identifies queries that exceed configurable latency thresholds and surfaces them in the insights dashboard. For each slow query, the platform provides the query template, execution frequency, average and p99 latencies, rows examined versus rows returned (a key indicator of query efficiency), and the execution plan. This information is exactly what a DBA would need to diagnose and fix performance issues.

The slow query analysis also provides actionable recommendations. If a query is slow because it is performing a full table scan, the insights engine will suggest adding an index. If a query is examining far more rows than it returns, the engine will suggest adding a WHERE clause or modifying the existing index. If a query is slow due to a cross-shard scatter, the engine will suggest adding the shard key to the query or redesigning the schema to avoid the scatter.

graph TB subgraph "Query Telemetry Pipeline" VTGATE[vtgate] -->|Query Logs| COLLECTOR[Log Collector] COLLECTOR -->|Raw Events| PROCESSOR[Stream Processor] PROCESSOR -->|Aggregated Metrics| AGGREGATOR[Time-Series Aggregator] AGGREGATOR -->|Stored Metrics| TSDB[(Time-Series DB)] TSDB -->|Read| INSIGHTS[Insights API] INSIGHTS -->|Read| WEB[Web Console] INSIGHTS -->|Read| CLI[pscale CLI] end subgraph "Analysis Services" SLOW[Slow Query Detector] INDEX[Index Recommender] SCHEMA[Schema Analyzer] TREND[Trend Analyzer] end TSDB --> SLOW TSDB --> INDEX TSDB --> SCHEMA TSDB --> TREND

Index Recommendations

PlanetScale's index recommendation engine analyzes query patterns and suggests indexes that would improve performance. The engine considers several factors: the frequency of the query, the current execution time, the selectivity of the proposed index (how many rows it would eliminate from the scan), the impact on write performance (indexes slow down inserts and updates), and the overall storage cost of the additional index. The recommendations are ranked by estimated impact, helping developers prioritize the most beneficial changes.

C#
// QueryInsight represents an aggregated view of a query pattern
public class QueryInsight
{
    public string QueryTemplate { get; set; }
    public string NormalizedQuery { get; set; }
    public long ExecutionCount { get; set; }
    public double AverageLatencyMs { get; set; }
    public double P99LatencyMs { get; set; }
    public long TotalRowsExamined { get; set; }
    public long TotalRowsReturned { get; set; }
    public double RowsExaminedPerReturn 
        => ExecutionCount > 0 
            ? (double)TotalRowsExamined / ExecutionCount 
            : 0;
    public List<IndexRecommendation> Recommendations { get; set; }
    public List<QueryPlanSnapshot> RecentPlans { get; set; }
}

// IndexRecommendationEngine suggests indexes based on query patterns
public class IndexRecommendationEngine
{
    private readonly IQueryInsightsStore _insightsStore;
    private readonly ISchemaStore _schemaStore;

    public async Task<List<IndexRecommendation>> GetRecommendations(
        string databaseName, string branchName)
    {
        var insights = await _insightsStore
            .GetQueryInsights(databaseName, branchName);
        var schema = await _schemaStore
            .GetCurrentSchema(databaseName, branchName);
        var recommendations = new List<IndexRecommendation>();

        foreach (var insight in insights.Where(i => i.RowsExaminedPerReturn > 100))
        {
            var possibleIndexes = ExtractPotentialIndexColumns(
                insight.NormalizedQuery, schema);
            
            foreach (var candidate in possibleIndexes)
            {
                var recommendation = new IndexRecommendation
                {
                    Table = candidate.TableName,
                    Columns = candidate.Columns,
                    Reason = $"Query examines {insight.RowsExaminedPerReturn:F0} rows per return",
                    EstimatedLatencyReduction = 
                        await EstimateLatencyReduction(insight, candidate),
                    WriteImpact = EstimateWriteImpact(candidate),
                    StorageCost = await EstimateStorageCost(candidate)
                };
                
                if (recommendation.EstimatedLatencyReduction > 0.2)
                    recommendations.Add(recommendation);
            }
        }

        return recommendations
            .OrderByDescending(r => r.EstimatedLatencyReduction)
            .ToList();
    }
}

Query Execution Plans

PlanetScale provides detailed execution plans for any query, accessible through the web console, CLI, or API. The execution plan shows how MySQL's query optimizer would execute the query, including which indexes it would use, how it would join tables, and how many rows it estimates it will examine at each step. This information is invaluable for understanding query performance and identifying optimization opportunities.

The platform also tracks execution plan changes over time. When a schema change alters the available indexes or table statistics, the query optimizer may choose a different execution plan. PlanetScale detects these plan changes and alerts the developer, helping to catch performance regressions before they affect users. This proactive monitoring is particularly valuable for queries that rely on specific index configurations.

Insights Dashboard Metrics

MetricDescriptionUse Case
Query CountNumber of queries executed per time periodTraffic analysis and capacity planning
Average LatencyMean query execution timePerformance baseline and regression detection
P99 Latency99th percentile query execution timeWorst-case user experience assessment
Rows ExaminedTotal rows scanned during query executionIndex effectiveness evaluation
Rows ReturnedRows in the result setQuery selectivity analysis
Error RatePercentage of queries that returned errorsStability and reliability monitoring
ThroughputQueries per second by query typeWorkload characterization

9. Online DDL

Online DDL is the mechanism that makes PlanetScale's zero-downtime schema changes possible. In traditional MySQL, applying a DDL statement like ALTER TABLE acquires a metadata lock on the table, blocking all DML (INSERT, UPDATE, DELETE) operations until the DDL completes. For small tables, this lock is brief and inconsequential. For large tables with hundreds of millions of rows, the DDL can take minutes or hours, during which the table is effectively unavailable. PlanetScale uses Vitess's online DDL implementation to eliminate this problem entirely.

The Problem with Traditional DDL

MySQL's InnoDB storage engine supports some online DDL operations natively, but many common operations still require exclusive locks or cause significant performance degradation. For example, adding an index to a large table traditionally required either blocking all writes for the duration of the index build or using third-party tools like pt-online-schema-change or gh-ost that work by creating a shadow table, copying data incrementally, and then swapping the tables. These tools work well but require careful configuration and monitoring, and they add operational complexity.

PlanetScale eliminates this complexity by integrating online DDL directly into the platform. When a deploy request is applied, the DDL statements are executed through Vitess's online DDL mechanism, which handles all the complexity of shadow table creation, incremental data copying, and table swapping automatically. The developer simply submits the DDL statement, and the platform handles the rest, ensuring that the schema change is applied without blocking production traffic.

How Vitess Online DDL Works

Vitess implements online DDL using a shadow table approach similar to gh-ost. When an online DDL operation is initiated, Vitess creates a shadow table with the new schema definition. It then begins copying data from the original table to the shadow table in small batches, using a combination of INSERT...SELECT statements and binlog replication to keep the shadow table in sync with the original table. Once the shadow table is fully synchronized, Vitess performs an atomic rename operation to swap the shadow table into place, making the new schema effective immediately.

The key insight of Vitess's approach is that it leverages MySQL's native binlog replication mechanism to capture ongoing DML changes to the original table and apply them to the shadow table. This means that even as the copy operation is running and new writes are arriving, the shadow table stays in sync. The entire process is managed by the vttablet layer, which coordinates with MySQL's replication system to ensure consistency.

C#
// OnlineDdlOperation represents an in-progress schema change
public class OnlineDdlOperation
{
    public string Id { get; set; }
    public string DatabaseName { get; set; }
    public string BranchName { get; set; }
    public string DdlStatement { get; set; }
    public OnlineDdlStatus Status { get; set; }
    public DateTime StartedAt { get; set; }
    public DateTime? CompletedAt { get; set; }
    public double ProgressPercent { get; set; }
    public long RowsCopied { get; set; }
    public long TotalRows { get; set; }
    public string ShadowTableName { get; set; }
    public List<DdlEvent> Events { get; set; }
}

// OnlineDdlManager coordinates the shadow table workflow
public class OnlineDdlManager
{
    private readonly IVttabletClient _vttabletClient;
    private readonly IBinlogReader _binlogReader;

    public async Task<OnlineDdlOperation> StartOnlineDdl(
        string databaseName, string ddlStatement, string branchName)
    {
        var operation = new OnlineDdlOperation
        {
            Id = Guid.NewGuid().ToString(),
            DatabaseName = databaseName,
            BranchName = branchName,
            DdlStatement = ddlStatement,
            Status = OnlineDdlStatus.Initializing,
            StartedAt = DateTime.UtcNow
        };

        // Step 1: Parse DDL and create shadow table
        var parsedDdl = ParseDdl(ddlStatement);
        var shadowTableName = $"_vs_ddl_{parsedDdl.TableName}_{operation.Id}";
        await _vttabletClient.ExecuteDdl(
            databaseName, $"CREATE TABLE {shadowTableName} LIKE {parsedDdl.TableName}");
        await _vttabletClient.ExecuteDdl(
            databaseName, $"ALTER TABLE {shadowTableName} {parsedDdl.AlterClause}");

        // Step 2: Start incremental copy
        operation.Status = OnlineDdlStatus.Copying;
        operation.ShadowTableName = shadowTableName;
        await StartIncrementalCopy(operation);

        // Step 3: Start binlog replication to keep shadow in sync
        await _binlogReader.StartReplication(
            databaseName, parsedDdl.TableName, shadowTableName, operation.Id);

        return operation;
    }

    public async Task<OnlineDdlOperation> CompleteDdl(string operationId)
    {
        var operation = await GetOperation(operationId);
        
        // Atomic swap: rename shadow table into place
        await _vttabletClient.ExecuteDdl(
            operation.DatabaseName,
            $"RENAME TABLE {operation.DatabaseName}.{operation.ShadowTableName} TO {operation.DatabaseName}.{operation.TableName}");

        operation.Status = OnlineDdlStatus.Completed;
        operation.CompletedAt = DateTime.UtcNow;
        return operation;
    }
}

Vitess vs. pt-online-schema-change vs. gh-ost

FeatureVitess Online DDLpt-online-schema-changegh-ost
Trigger-based or binlogBinlogTriggersBinlog
Table lockingMinimal (rename only)MinimalMinimal
Server-side executionYes (vttablet)Requires separate processRequires separate process
Automatic cleanupYesNoNo
Progress trackingBuilt-inExternal monitoringBuilt-in
Rollback supportAutomatic on failureManual cleanupManual cleanup
Sharding awarenessYesNoNo

Limitations of Online DDL

While online DDL eliminates downtime, it is not without trade-offs. The shadow table approach requires additional storage space equal to the size of the table being modified. The copy operation consumes I/O bandwidth, which can affect query performance during the schema change. Tables with very large amounts of data may take hours to complete the copy operation, during which time the additional storage and I/O costs are incurred. PlanetScale mitigates these impacts by throttling the copy operation based on current system load, but customers should be aware of the resource implications.

Some DDL operations cannot be performed online, even with Vitess's mechanism. Operations that fundamentally change the data type of a column, for example, may require data conversion that cannot be done incrementally. In these cases, PlanetScale will flag the operation as requiring downtime and provide guidance on how to minimize the impact. The platform's schema analysis during deploy requests will also flag operations that are expected to take a long time, giving developers the opportunity to plan accordingly.

10. Replication and High Availability

High availability is a foundational requirement for any production database system, and PlanetScale delivers it through a multi-layered replication architecture built on top of Vitess's replication capabilities. Every PlanetScale database runs with at least three replicas distributed across multiple availability zones, ensuring that the failure of any single zone does not affect the availability or durability of customer data. The platform automatically manages failover, replica promotion, and data consistency, removing the operational burden that typically accompanies high-availability MySQL deployments.

Replication Architecture

PlanetScale uses MySQL's native asynchronous replication mechanism, enhanced by Vitess's tablet management system. Each shard has one primary tablet that handles all write operations and one or more replica tablets that receive replicated data from the primary. The replicas can serve read queries, offloading traffic from the primary and improving read scalability. When the primary becomes unavailable, Vitess automatically promotes one of the replicas to primary, reroutes traffic, and rebuilds the replication topology.

The replication process works at the MySQL level: the primary tablet records all data changes in its binary log (binlog), and the replica tablets read these logs and apply the changes to their local MySQL instances. Vitess enhances this process by adding health checks, lag monitoring, and automatic failover. If a replica falls too far behind the primary (replication lag exceeds a configurable threshold), Vitess removes it from the read pool to prevent serving stale data. If the primary fails, Vitess selects the most up-to-date replica and promotes it to primary, updating the routing tables in vtgate to direct all traffic to the new primary.

graph TB subgraph "Availability Zone A" PRIMARY[(MySQL Primary)] VT_PRIMARY[vttablet Primary] end subgraph "Availability Zone B" REPLICA1[(MySQL Replica 1)] VT_REPLICA1[vttablet Replica 1] end subgraph "Availability Zone C" REPLICA2[(MySQL Replica 2)] VT_REPLICA2[vttablet Replica 2] end subgraph "Failover Controller" HC[Health Checker] PROMOTE[Failover Coordinator] TOPO[Topology Manager] end PRIMARY -->|Binlog Stream| REPLICA1 PRIMARY -->|Binlog Stream| REPLICA2 VT_PRIMARY --> HC HC -->|Lag Detection| PROMOTE PROMOTE -->|Promote Replica| TOPO TOPO -->|Update Routing| VT_PRIMARY TOPO -->|Update Routing| VT_REPLICA1 TOPO -->|Update Routing| VT_REPLICA2

Automatic Failover

PlanetScale's failover system is designed to detect and recover from primary failures within seconds. The health checker continuously monitors the primary tablet's responsiveness by executing lightweight health check queries. If the primary fails to respond within the configured timeout (typically 10 seconds), the health checker initiates a failover sequence. The failover coordinator examines the replication lag of all available replicas and selects the one with the least lag as the candidate for promotion.

The promotion process involves several carefully coordinated steps. First, the failover coordinator ensures that the candidate replica has received all committed transactions from the primary. This is critical for data consistency — promoting a replica that is behind the primary would result in data loss. Second, the coordinator promotes the replica to primary by reconfiguring MySQL replication and updating Vitess's topology. Third, the coordinator updates the vtgate routing tables to direct all write traffic to the new primary. Fourth, the remaining replicas are reconfigured to replicate from the new primary. This entire process typically completes in under 30 seconds.

Consistency Guarantees

PlanetScale's replication architecture provides specific consistency guarantees that customers should understand. Write operations are committed to the primary before the client receives an acknowledgment, ensuring that committed writes are durable. However, replica reads may return slightly stale data due to replication lag. PlanetScale provides a READ CONSISTENCY option that allows clients to request strong consistency for specific queries, at the cost of additional latency.

For transactions that span multiple operations, PlanetScale ensures that all operations within a transaction are executed on the same primary and committed atomically. This provides the same ACID guarantees as a single MySQL server, despite the distributed nature of the underlying system. Cross-shard transactions use a two-phase commit protocol to ensure atomicity, though these are discouraged due to their performance implications.

HA Architecture Comparison

FeaturePlanetScaleAWS RDS Multi-AZSelf-Hosted MySQL
Failover Time~10-30 seconds~60-120 secondsMinutes to manual
Automatic FailoverYesYesRequires orchestration
Cross-Region ReplicasYes (paid plans)Read Replicas onlyManual setup
Data Durability3x replication + backups3x replication + backupsDepends on setup
Read ScalingAutomatic replica routingManual read replica setupManual proxy configuration
Schema Change HAOnline DDL with zero downtimeMulti-AZ sync on failover onlyManual online DDL tools

Disaster Recovery

Beyond standard high availability, PlanetScale provides disaster recovery capabilities through continuous backups and point-in-time recovery. Every write to the primary is captured in the binary log and can be replayed to recover the database to any point in time within the retention period. Backups are stored in geographically separate locations from the primary data, ensuring that even a catastrophic regional failure does not result in data loss. We will cover backup and recovery in more detail in the next section.

The combination of multi-zone replication, automatic failover, continuous backups, and point-in-time recovery provides a comprehensive high-availability and disaster recovery solution. PlanetScale's approach is to handle all of these concerns automatically, removing the need for customers to design, implement, and operate their own HA infrastructure. This managed approach is particularly valuable for small to medium-sized teams that lack dedicated infrastructure engineers.

11. Backup and Recovery

Backup and recovery is a critical aspect of any database platform, and PlanetScale implements a comprehensive backup strategy that combines continuous binary log streaming with periodic full backups to provide both point-in-time recovery and disaster recovery capabilities. Understanding PlanetScale's backup architecture is essential for customers who need to meet compliance requirements, recover from accidental data corruption, or restore to a specific point in time.

Backup Architecture

PlanetScale's backup system operates at two levels. First, every write to the primary MySQL instance is recorded in the binary log, which is continuously streamed to cloud storage (typically Amazon S3 or Google Cloud Storage). This streaming provides the foundation for point-in-time recovery, as any transaction that has been committed to the primary can be replayed from the binary log. The binary log retention period determines how far back in time a customer can recover — PlanetScale typically retains binary logs for 7 days on paid plans.

Second, PlanetScale performs periodic full backups using Vitess's backup mechanism, which creates a consistent snapshot of the entire database. Full backups are stored in cloud storage and can be used to restore a database to the state it was in at the time of the backup. This is significantly faster than replaying binary logs from the beginning of time, as the full backup provides a known starting point. PlanetScale typically performs full backups daily, though the frequency may vary based on the database size and plan tier.

The combination of full backups and binary log streaming enables efficient point-in-time recovery. To recover to a specific timestamp, the system first restores the most recent full backup before the target timestamp, then replays the binary logs from the backup time to the target timestamp. This approach minimizes recovery time while providing precise point-in-time recovery.

C#
// BackupManager handles backup creation and point-in-time recovery
public class BackupManager
{
    private readonly ICloudStorageProvider _storageProvider;
    private readonly IBinaryLogStreamer _logStreamer;
    private readonly IMySQLRestorer _mysqlRestorer;

    public async Task<BackupManifest> CreateFullBackup(
        string databaseName, string shardName)
    {
        var backupId = $"backup_{databaseName}_{shardName}_{DateTime.UtcNow:yyyyMMddHHmmss}";
        
        // Create consistent snapshot using Vitess backup
        var snapshot = await _mysqlRestorer.CreateConsistentSnapshot(
            databaseName, shardName);
        
        // Upload snapshot to cloud storage
        var storagePath = $"backups/{databaseName}/{shardName}/{backupId}";
        await _storageProvider.UploadSnapshot(snapshot, storagePath);

        var manifest = new BackupManifest
        {
            BackupId = backupId,
            DatabaseName = databaseName,
            ShardName = shardName,
            CreatedAt = DateTime.UtcNow,
            StoragePath = storagePath,
            SizeBytes = snapshot.SizeBytes,
            BinlogPosition = snapshot.BinlogPosition
        };

        return manifest;
    }

    public async Task<RecoveryResult> RecoverToPointInTime(
        string databaseName, string shardName, DateTime targetTime)
    {
        // Find the most recent full backup before the target time
        var backups = await _storageProvider.ListBackups(
            databaseName, shardName);
        var suitableBackup = backups
            .Where(b => b.CreatedAt <= targetTime)
            .OrderByDescending(b => b.CreatedAt)
            .FirstOrDefault();

        if (suitableBackup == null)
            throw new RecoveryException(
                "No suitable backup found for the requested recovery time");

        // Restore the full backup
        await _mysqlRestorer.RestoreFromBackup(
            databaseName, shardName, suitableBackup.StoragePath);

        // Replay binary logs from backup time to target time
        var binlogRange = new BinlogRange
        {
            StartPosition = suitableBackup.BinlogPosition,
            EndTime = targetTime
        };
        await _logStreamer.ReplayBinlogRange(
            databaseName, shardName, binlogRange);

        return new RecoveryResult
        {
            RecoveredTo = targetTime,
            BackupUsed = suitableBackup.BackupId,
            BinlogReplayDuration = binlogRange.Duration
        };
    }
}

Recovery Procedures

PlanetScale provides several recovery options depending on the nature of the data loss. For accidental row deletions or data corruption on a specific table, customers can use point-in-time recovery to restore the database to a state just before the damaging operation. For complete database restoration after a catastrophic failure, the system restores from the most recent full backup and applies binary logs up to the point of failure. For cross-region disaster recovery, the system can restore from a replica in a different region.

The recovery process is initiated through the web console or CLI. Customers specify the target recovery time (the point in time to which they want to restore) and the scope of the recovery (the entire database or specific tables). The platform then executes the recovery, which may take minutes to hours depending on the amount of data and the distance between the backup time and the recovery target. During recovery, the database is placed in read-only mode to prevent new writes from conflicting with the recovery process.

Backup Retention and Cost

PlanBackup RetentionRecovery WindowRecovery Time ObjectiveRecovery Time
Hobby7 days7 daysUp to 5 minutesMinutes to hours
Scaler14 days14 daysUp to 1 minuteMinutes to hours
Enterprise30 days30 daysNear-zero (continuous)Minutes to hours

Data Durability Guarantees

PlanetScale's data durability strategy follows the principle of defense in depth. The primary MySQL instance uses InnoDB's double-write buffer and fsync to ensure that committed transactions are durable on disk. The binary log provides a sequential record of all changes that can be used to reconstruct the database state. The replication system provides multiple copies of the data across availability zones. And the backup system provides point-in-time recovery capability independent of the replication system.

Together, these mechanisms provide strong durability guarantees. A committed transaction is simultaneously present on the primary's disk, in the binary log stream, and replicated to at least two replicas. The probability of data loss is vanishingly small — it would require the simultaneous failure of all three availability zones before the binary log could be streamed to cloud storage. PlanetScale's SLA reflects this robust architecture, with committed data durability guarantees that meet or exceed industry standards.

Backup Integrity Verification

PlanetScale periodically verifies backup integrity by performing test restores of randomly selected backups. This practice ensures that backups are not corrupted and can actually be used for recovery when needed. The integrity verification process restores a backup to a temporary MySQL instance, runs a set of validation queries to verify data consistency, and then discards the temporary instance. Results of these verification tests are logged and monitored, with alerts generated if any backup fails verification.

12. Multi-Region Read Replicas

Multi-region read replicas are one of PlanetScale's most powerful features for applications that serve users across geographic regions. By replicating data to servers in multiple cloud regions, PlanetScale enables applications to read data from a geographically nearby replica, dramatically reducing read latency for users who are far from the primary database region. This capability is essential for global applications where sub-100ms read latency is required for a good user experience.

How Multi-Region Replicas Work

PlanetScale's multi-region architecture extends the single-region replication model to a global scale. The primary database and its replicas reside in one cloud region (the write region), while additional read replicas are provisioned in other regions (read regions). These cross-region replicas receive replicated data from the primary through Vitess's VStream mechanism, which captures change data from the primary's binlog and streams it to replicas in other regions.

The replication stream is asynchronous, meaning that cross-region replicas may have slightly higher replication lag than same-region replicas due to network latency. PlanetScale optimizes this by batching changes and compressing the replication stream to minimize the amount of data that needs to cross region boundaries. Typical replication lag for cross-region replicas is in the hundreds of milliseconds range, making them suitable for read-heavy workloads that can tolerate slightly stale data.

graph TB subgraph "US East (Write Region)" PRIMARY[(Primary)] REPLICA_LOCAL[(Local Replica)] end subgraph "EU West (Read Region)" REPlICA_EU[(EU Read Replica)] end subgraph "AP Southeast (Read Region)" REPLICA_AP[(AP Read Replica)] end subgraph "VStream Replication" STREAM[Change Stream] end PRIMARY -->|Binlog| REPLICA_LOCAL PRIMARY -->|Binlog| STREAM STREAM -->|Compressed Stream| REPlICA_EU STREAM -->|Compressed Stream| REPLICA_AP APP_US[US App Servers] --> PRIMARY APP_US --> REPLICA_LOCAL APP_EU[EU App Servers] --> REPlICA_EU APP_AP[AP App Servers] --> REPLICA_AP

Read-after-Write Consistency

One of the challenges with multi-region read replicas is ensuring read-after-write consistency. When a user writes data to the primary in one region and immediately reads it from a replica in another region, the read may not see the write if the replication has not yet propagated the change. PlanetScale addresses this through a consistency mechanism that allows clients to specify a minimum replication lag threshold for read queries. If the replica's lag exceeds this threshold, the query is automatically routed to the primary instead.

This mechanism works by tracking the GTID (Global Transaction Identifier) of the most recent write on the primary and comparing it with the GTID of the most recently applied transaction on the replica. The difference between these two GTIDs represents the replication lag. When a client specifies a consistency requirement, the vtgate layer checks the replica's lag and makes routing decisions accordingly.

C#
// MultiRegionRouter handles query routing across geographic regions
public class MultiRegionRouter
{
    private readonly Dictionary<string, RegionEndpoint> _regionEndpoints;
    private readonly IGtidTracker _gtidTracker;

    public async Task<QueryRoute> RouteQuery(
        string query, string clientRegion, QueryConsistency consistency)
    {
        var endpoint = _regionEndpoints[clientRegion];
        var replicaGtid = await endpoint.GetReplicaGtid();
        var primaryGtid = await _gtidTracker.GetPrimaryGtid(endpoint.DatabaseName);
        var replicationLag = GtidDistance.Calculate(primaryGtid, replicaGtid);

        if (consistency == QueryConsistency.Strong)
        {
            // Always route to primary for strong consistency
            return new QueryRoute
            {
                TargetEndpoint = _regionEndpoints["primary"],
                Reason = "Strong consistency requested"
            };
        }

        if (consistency == QueryConsistency.Eventual || 
            replicationLag <= consistency.MaxLagThreshold)
        {
            return new QueryRoute
            {
                TargetEndpoint = endpoint,
                Reason = $"Replica lag {replicationLag} within threshold"
            };
        }

        // Replication lag exceeds threshold, route to primary
        return new QueryRoute
        {
            TargetEndpoint = _regionEndpoints["primary"],
            Reason = $"Replica lag {replicationLag} exceeds threshold {consistency.MaxLagThreshold}"
        };
    }
}

public enum QueryConsistency
{
    Eventual,
    Bounded,
    Strong
}

public class BoundedConsistency : QueryConsistency
{
    public long MaxLagThreshold { get; set; }
}

Regional Deployment Topology

RegionRoleReplication LagUse Case
US East (Virginia)Primary + Local Replicas0ms (primary)All writes, US reads
EU West (Ireland)Read Replica~100-300msEuropean reads
AP Southeast (Singapore)Read Replica~150-400msAsia-Pacific reads
US West (Oregon)Read Replica~20-50msUS West Coast reads

Cost Considerations

Multi-region read replicas involve additional costs beyond the primary database. Cross-region data transfer is typically more expensive than intra-region transfer, and the additional replica instances consume compute and storage resources in each region. PlanetScale's billing model charges for these resources based on actual usage, allowing customers to balance latency requirements against cost. Many customers choose to deploy read replicas in their two most active regions while relying on the primary region for less active regions.

Fallback Strategies

When a cross-region replica becomes unavailable (due to a region outage or network partition), PlanetScale automatically routes read queries to the nearest available replica or to the primary. This fallback ensures that application read availability is maintained even during regional failures. The fallback behavior is configurable — customers can choose to fail over to the primary, to a replica in an alternative region, or to return an error for reads that cannot be served with the required consistency level.

13. Billing and Usage Metering

PlanetScale's billing system is built on a usage-based model that charges customers for the resources they actually consume. This approach aligns costs with value, allowing startups to start small and scale up as their usage grows. Understanding the billing system is important for customers who need to predict costs, optimize usage, and justify the investment in PlanetScale to their organizations.

Usage Metrics

PlanetScale meters four primary usage metrics: storage, rows read, rows written, and branching compute. Storage is measured in gigabytes and represents the total amount of data stored across all branches. Since branches share underlying data until they diverge, the storage cost of multiple branches is typically less than the sum of their individual sizes. Rows read counts the total number of rows returned by queries across all instances. Rows written counts the total number of rows inserted, updated, or deleted. Branching compute measures the compute resources consumed by branch operations like schema diffs, deploy requests, and online DDL.

The usage metrics are collected continuously by the platform's metering service, which aggregates the data into hourly buckets. The hourly aggregates are then rolled up into daily, weekly, and monthly totals for billing purposes. This granular metering ensures that customers are charged precisely for what they use, with no rounding or estimation involved.

graph TB subgraph "Metering Pipeline" VTGATE[vtgate Query Logs] -->|Query Events| COLLECTOR[Metering Collector] VTTABLET[vttablet Events] -->|Write Events| COLLECTOR SCHEMA[Schema Service] -->|DDL Events| COLLECTOR COLLECTOR -->|Raw Events| AGGREGATOR[Hourly Aggregator] AGGREGATOR -->|Hourly Totals| ROLLUP[Daily Rollup] ROLLUP -->|Daily Totals| BILLING[Billing Engine] BILLING -->|Invoice| CUSTOMER[Customer Dashboard] BILLING -->|Usage Data| ALERTS[Usage Alerts] end

Plan Tiers

PlanetScale offers several plan tiers to accommodate different usage levels and requirements. The Hobby tier is free and provides enough capacity for small projects, prototypes, and learning. It includes limited storage (5 GB), limited rows read and written, and access to a single region. The Scaler tier is designed for production applications and provides higher limits, multiple regions, and additional features like deploy request approvals and branch protection rules. The Enterprise tier provides custom limits, dedicated support, SLA guarantees, and advanced features like SSO integration and audit logging.

Cost Optimization Strategies

There are several strategies that customers can use to optimize their PlanetScale costs. First, clean up unused branches regularly — while branches share underlying data, they do consume metadata storage and increase the complexity of schema management. Second, optimize queries to reduce rows read — a query that examines 10,000 rows to return 10 rows is 1,000 times more expensive than a query that examines 10 rows to return 10 rows. Third, use read replicas judiciously — cross-region replicas provide lower latency but higher cost, so deploy them only where the latency improvement justifies the expense. Fourth, monitor usage trends to identify unexpected spikes that might indicate a bug or inefficiency in the application.

Billing Dashboard Metrics

MetricUnitBilling PeriodOverage Policy
StorageGB-monthMonthlySoft limit with alerts
Rows ReadBillions of rowsMonthlyCharged per unit
Rows WrittenBillions of rowsMonthlyCharged per unit
Branching ComputeCPU-hoursMonthlyCharged per unit
Data TransferGBMonthlyCharged per GB
BackupsGBMonthlyIncluded in plan

Usage Alerts and Budgets

PlanetScale provides configurable usage alerts that notify customers when their usage approaches or exceeds predefined thresholds. Customers can set alerts for each usage metric independently, with configurable thresholds and notification channels (email, Slack, webhook). The platform also supports hard budget limits that prevent usage from exceeding a specified amount, protecting customers from unexpected cost overruns. When a hard limit is reached, the platform may throttle or pause the database to prevent additional charges, depending on the customer's configuration.

The billing system also integrates with popular cost management platforms, allowing customers to track their PlanetScale costs alongside their other cloud expenses. This integration is particularly valuable for organizations that use multi-cloud strategies and need a unified view of their infrastructure costs.

14. MySQL Compatibility and Limitations

PlanetScale is built on MySQL and aims for broad compatibility with the MySQL ecosystem, but the distributed nature of the underlying Vitess architecture introduces certain limitations that customers must understand. This section provides a comprehensive overview of what works, what doesn't, and what requires special consideration when using PlanetScale.

What Works

PlanetScale supports the vast majority of MySQL's SQL syntax, including SELECT, INSERT, UPDATE, DELETE, JOINs, subqueries, window functions, CTEs (Common Table Expressions), stored procedures, and triggers. Standard MySQL clients and drivers work without modification — customers can use mysql CLI, MySQL Workbench, JDBC, mysql2 for Node.js, pymysql for Python, and any other MySQL-compatible client library. ORMs like ActiveRecord, Sequelize, SQLAlchemy, Eloquent, and Entity Framework also work with PlanetScale out of the box.

PlanetScale supports MySQL 5.7 and 8.0 compatible syntax, including JSON column types, spatial data types, and full-text search. InnoDB features like transactions, foreign keys, and row-level locking are fully supported. MySQL's replication mechanism (binlog) is used internally by Vitess but is not exposed directly to customers — customers interact with PlanetScale through the MySQL protocol, not through replication.

Known Limitations

Despite the broad compatibility, there are several limitations that customers should be aware of. The most significant limitation is the absence of FOREIGN KEY constraints enforcement. While PlanetScale supports the syntax for defining foreign keys, it does not enforce them at the database level. This is a deliberate design decision — enforcing foreign keys in a sharded environment requires cross-shard coordination, which would significantly impact performance. Instead, PlanetScale recommends that customers enforce referential integrity at the application level.

Another significant limitation is that ALTER TABLE operations that change the shard key are not supported through the normal deploy request workflow. Changing the shard key requires resharding, which is a complex operation that involves moving data between shards. PlanetScale provides resharding tools for this purpose, but it is not a routine operation and requires careful planning.

C#
// MySQL Compatibility Checker validates queries against PlanetScale constraints
public class CompatibilityChecker
{
    private readonly List<ICompatibilityRule> _rules;

    public CompatibilityChecker()
    {
        _rules = new List<ICompatibilityRule>
        {
            new ForeignKeyRule(),
            new CrossShardJoinRule(),
            new ShardKeyModificationRule(),
            new SystemTableAccessRule(),
            new UdfRule(),
            new XaTransactionRule()
        };
    }

    public async Task<CompatibilityReport> CheckQuery(string query)
    {
        var report = new CompatibilityReport { Query = query };
        var parsedQuery = MySqlParser.Parse(query);

        foreach (var rule in _rules)
        {
            var result = await rule.Evaluate(parsedQuery);
            if (result.HasIssues)
            {
                report.Issues.Add(result);
            }
        }

        report.IsCompatible = !report.Issues.Any(i => i.Severity == IssueSeverity.Error);
        return report;
    }
}

public class CompatibilityIssue
{
    public string RuleName { get; set; }
    public IssueSeverity Severity { get; set; }
    public string Message { get; set; }
    public string Suggestion { get; set; }
}

public enum IssueSeverity
{
    Info,
    Warning,
    Error
}

Compatibility Matrix

FeatureSupported?Notes
SELECT / INSERT / UPDATE / DELETEYesFull support
JOINsYesCross-shard joins require scatter
Foreign Keys (syntax)YesNot enforced at database level
Stored ProceduresYesMust not reference sharded tables
TriggersLimitedSupport varies by trigger type
ALTER TABLE (online)YesVia deploy requests
ReshardingYesComplex operation, requires planning
Cross-database queriesNoEach PlanetScale database is independent
XA TransactionsNoNot supported in distributed environment
LOCK TABLESNoNot supported; use application-level locking

Workarounds for Limitations

PlanetScale provides workarounds for many of its limitations. For foreign key enforcement, customers can implement referential integrity checks in their application code or use database triggers (where supported). For cross-shard queries, customers can redesign their schema to colocate related data on the same shard, or use materialized views that precompute cross-shard aggregations. For cross-database queries, customers can use application-level joining or replicate necessary data between databases.

The platform's documentation provides detailed guidance on how to work within these constraints, and the community forums are active with discussions about best practices. The key insight is that while PlanetScale introduces some constraints compared to single-server MySQL, these constraints often lead to better application design — schemas that work well on PlanetScale are typically also more performant and maintainable on single-server MySQL.

15. Migration from Self-Hosted MySQL

Migrating from a self-hosted MySQL deployment to PlanetScale is a common use case, and the platform provides tools and guidance to make the process as smooth as possible. However, migration is not a simple "lift and shift" — the differences between single-server MySQL and PlanetScale's distributed architecture require careful planning and execution. This section provides a comprehensive migration guide that covers assessment, preparation, migration execution, and post-migration optimization.

Migration Phases

A typical migration follows four phases: Assessment, Preparation, Migration, and Validation. During the Assessment phase, the team evaluates the existing MySQL schema for PlanetScale compatibility, identifies queries that may need modification, and estimates the effort required. The Preparation phase involves modifying the schema to work within PlanetScale's constraints, setting up the PlanetScale database, and testing the application against the new platform. The Migration phase involves moving the data from the existing MySQL instance to PlanetScale and switching traffic. The Validation phase involves monitoring the migrated system, verifying data integrity, and optimizing performance.

graph LR subgraph "Phase 1: Assessment" A1[Schema Analysis] A2[Query Audit] A3[Compatibility Check] end subgraph "Phase 2: Preparation" P1[Schema Modification] P2[PlanetScale Setup] P3[Application Testing] end subgraph "Phase 3: Migration" M1[Data Export] M2[Data Import] M3[Traffic Cutover] end subgraph "Phase 4: Validation" V1[Data Integrity Check] V2[Performance Monitoring] V3[Optimization] end A1 --> P1 A2 --> P1 A3 --> P1 P1 --> M1 P2 --> M1 P3 --> M1 M1 --> V1 M2 --> V1 M3 --> V1

Schema Preparation

The most critical step in migrating to PlanetScale is preparing the schema for the distributed environment. This involves several modifications: adding a shard key to tables that will exceed single-shard capacity, removing or replacing foreign key constraints with application-level enforcement, verifying that stored procedures and triggers are compatible with Vitess, and ensuring that all queries can work within the shard routing model.

The shard key selection is particularly important and should be based on the most common access patterns. For an e-commerce application, the users table might be sharded on user_id, the orders table on user_id (to colocate orders with their user), and the products table on product_id. The goal is to ensure that the most frequent queries include the shard key in their WHERE clause, enabling single-shard routing for the majority of traffic.

C#
// MigrationAnalyzer evaluates a MySQL schema for PlanetScale compatibility
public class MigrationAnalyzer
{
    private readonly ISchemaReader _schemaReader;
    private readonly IQueryAnalyzer _queryAnalyzer;

    public async Task<MigrationReport> AnalyzeMigration(
        string sourceConnectionString, List<string> trackedQueries)
    {
        var report = new MigrationReport();
        
        // Read the existing schema
        var schema = await _schemaReader.ReadSchema(sourceConnectionString);
        
        foreach (var table in schema.Tables)
        {
            var tableAnalysis = new TableAnalysis { TableName = table.Name };
            
            // Check for foreign keys
            tableAnalysis.ForeignKeys = table.ForeignKeys;
            if (table.ForeignKeys.Any())
            {
                tableAnalysis.Warnings.Add(
                    "Foreign keys will not be enforced in PlanetScale. " +
                    "Implement referential integrity in application code.");
            }
            
            // Suggest shard key
            tableAnalysis.SuggestedShardKey = await SuggestShardKey(
                table, trackedQueries);
            if (tableAnalysis.SuggestedShardKey == null)
            {
                tableAnalysis.Warnings.Add(
                    "No shard key suggested. Table may need resharding later.");
            }
            
            // Estimate size and sharding needs
            tableAnalysis.EstimatedRowCount = await EstimateRowCount(
                sourceConnectionString, table.Name);
            tableAnalysis.NeedsSharding = tableAnalysis.EstimatedRowCount > 50_000_000;
            
            report.TableAnalyses.Add(tableAnalysis);
        }

        // Analyze tracked queries for compatibility
        foreach (var query in trackedQueries)
        {
            var queryAnalysis = await _queryAnalyzer.AnalyzeForMigration(query, schema);
            if (queryAnalysis.HasIssues)
                report.QueryIssues.Add(queryAnalysis);
        }

        report.TotalEffort = EstimateMigrationEffort(report);
        return report;
    }

    private async Task<string> SuggestShardKey(
        TableInfo table, List<string> queries)
    {
        var columnFrequency = new Dictionary<string, int>();
        
        foreach (var query in queries.Where(q => 
            q.Contains(table.Name, StringComparison.OrdinalIgnoreCase)))
        {
            var columns = ExtractWhereColumns(query, table.Name);
            foreach (var col in columns)
            {
                columnFrequency.TryGetValue(col, out var count);
                columnFrequency[col] = count + 1;
            }
        }

        return columnFrequency
            .OrderByDescending(kvp => kvp.Value)
            .FirstOrDefault().Key;
    }
}

Data Migration Methods

PlanetScale supports several data migration methods, each suitable for different scenarios. For small databases (under 10 GB), a direct mysqldump export and import may be sufficient. For larger databases, pg_dump-style logical dumps with parallel import provide better performance. For very large databases (hundreds of GB or more), physical backup methods like xtrabackup combined with Vitess's import mechanism provide the best throughput.

For zero-downtime migrations, customers can use a dual-write approach where the application writes to both the existing MySQL instance and PlanetScale during the migration window. Reads are gradually shifted from the old instance to PlanetScale, starting with low-risk read traffic and progressing to critical read traffic. Once all reads are served from PlanetScale, writes are switched over, and the old instance is decommissioned.

Migration Checklist

StepTaskEffortRisk
1Schema compatibility audit1-2 daysMedium
2Add shard keys to tables1-3 daysLow
3Remove/replace foreign keys2-5 daysHigh
4Modify cross-shard queries2-5 daysMedium
5Set up PlanetScale database1 dayLow
6Test application against PlanetScale3-5 daysMedium
7Perform data migration1-3 daysMedium
8Cutover traffic1 dayHigh
9Monitor and optimize1-2 weeksLow

Post-Migration Optimization

After migrating to PlanetScale, teams should take advantage of the platform's insights engine to optimize query performance. The insights engine will quickly identify slow queries, suggest indexes, and highlight opportunities for schema improvement. This optimization phase is particularly valuable because the migration process often reveals long-standing performance issues that were hidden in the self-hosted environment. The combination of PlanetScale's distributed architecture and its built-in monitoring provides a powerful platform for continuous improvement.

16. Security and Compliance

Security is a fundamental concern for any database platform, and PlanetScale implements a comprehensive security program that encompasses data encryption, access control, network security, and compliance certifications. Understanding PlanetScale's security posture is essential for organizations in regulated industries or those handling sensitive data, as it determines what types of data can be stored on the platform and what compliance obligations can be met.

Data Encryption

PlanetScale encrypts all data at rest and in transit. Data at rest is encrypted using AES-256 encryption, with encryption keys managed through cloud provider key management services (AWS KMS or Google Cloud KMS). Data in transit is encrypted using TLS 1.2 or higher, with automatic certificate rotation. The encryption is transparent to applications — no changes to application code or connection strings are required to enable encryption.

For customers who require customer-managed encryption keys (CMEK), PlanetScale provides support through cloud provider key management integration. CMEK allows customers to control the encryption keys used to encrypt their data, providing an additional layer of control over data access. With CMEK, PlanetScale cannot access customer data without the customer's encryption key, even if compelled by a legal request.

Access Control

PlanetScale implements multiple layers of access control. At the platform level, role-based access control (RBAC) determines what actions users can perform. Roles include Organization Admin, Database Admin, Developer, and Read-Only, each with a specific set of permitted operations. At the database level, MySQL-compatible user accounts control access to specific databases and tables. At the network level, IP allowlisting restricts which IP addresses can connect to the database.

The RBAC system supports integration with organizational identity providers through SAML and OIDC, enabling single sign-on (SSO) and centralized access management. When a team member leaves the organization, their access can be revoked immediately through the identity provider, without needing to modify database-level permissions separately.

Security Features Summary

FeatureDescriptionAvailability
Encryption at RestAES-256 encryption for all stored dataAll plans
Encryption in TransitTLS 1.2+ for all connectionsAll plans
Customer-Managed KeysCMEK through cloud KMS integrationEnterprise
IP AllowlistingRestrict connections to specific IP rangesAll plans
VPC PeeringPrivate network connectivityEnterprise
SSO / SAMLSingle sign-on integrationEnterprise
RBACRole-based access controlAll plans
Audit LoggingComprehensive activity audit trailEnterprise
SOC 2 Type IIIndependent security audit certificationAll plans
HIPAAHealth Insurance Portability complianceEnterprise (BAA)

Network Security

PlanetScale's network architecture is designed to minimize the attack surface. Customer databases are isolated in dedicated network segments, with no direct internet access. Connections from the internet are routed through the PlanetScale proxy layer, which performs TLS termination, authentication, and rate limiting. For customers who require private connectivity, PlanetScale supports VPC peering through cloud provider networking, allowing applications to connect to PlanetScale over private network links without traversing the public internet.

The platform also implements defense-in-depth strategies including Web Application Firewall (WAF) rules for the API gateway, DDoS protection at the network edge, and intrusion detection systems that monitor for suspicious activity. Security patches and updates are applied continuously by the PlanetScale operations team, ensuring that the underlying infrastructure is protected against known vulnerabilities.

Compliance and Certifications

PlanetScale maintains SOC 2 Type II certification, which validates the effectiveness of its security controls through independent audit. The platform also supports HIPAA compliance for healthcare customers through Business Associate Agreements (BAA). For customers in the European Union, PlanetScale complies with GDPR requirements, including data processing agreements, right to erasure support, and data portability. The platform's compliance documentation is available upon request for enterprise customers.

Security Best Practices for Customers

PlanetScale recommends several security best practices for customers. First, always use TLS connections and never transmit credentials over unencrypted channels. Second, use IP allowlisting to restrict database access to known application servers. Third, implement the principle of least privilege for database user accounts, granting only the permissions necessary for each application's needs. Fourth, enable audit logging to maintain a record of database operations for security investigations. Fifth, rotate database credentials regularly using PlanetScale's credential rotation feature.

17. Interview Q&A

The following questions and answers cover the key concepts and trade-offs involved in designing a PlanetScale-like serverless MySQL platform. These are designed for senior-level system design interviews where deep technical knowledge and the ability to discuss trade-offs are essential.

Q1: How would you design the schema branching system?

The schema branching system stores schema definitions as versioned snapshots, similar to how Git stores file contents. Each branch records its parent branch and a series of DDL changes. When a branch's schema is needed, the system starts from the parent branch's schema at the time of branching and applies the branch's DDL changes sequentially. The schema store uses a content-addressable storage model where each unique schema state is stored once and referenced by its hash, minimizing storage overhead for branches that share most of their schema.

Q2: How does PlanetScale handle schema changes without downtime?

PlanetScale uses Vitess's online DDL mechanism, which works by creating a shadow table with the new schema, incrementally copying data from the original table while applying ongoing changes from the binlog, and then performing an atomic rename to swap the shadow table into place. This approach ensures that production traffic continues to be served by the original table while the schema change is being applied in the background. The rename operation is brief (milliseconds) and is the only point at which the schema change becomes visible to clients.

Q3: How would you design the connection multiplexing system?

The connection multiplexing system works at two levels. The client-facing proxy maintains persistent connections with clients and tracks session state (current database, session variables, transaction context). The backend connection pool maintains a smaller set of connections to the vtgate layer. When a client sends a query, the proxy selects an available backend connection, applies the session state, executes the query, and returns the connection to the pool. For transactions, the proxy ensures that all queries within the transaction use the same backend connection to maintain consistency.

Q4: How does the deploy request analysis engine work?

The deploy request analysis engine parses the proposed DDL changes, compares the source and target schemas, and evaluates each change against the current production schema and tracked query patterns. For each change, it checks for destructive operations, estimates execution time based on table size, identifies queries that reference affected columns or tables, evaluates index impact, and detects potential breaking changes. The analysis results are presented to reviewers as a structured report with severity levels and actionable recommendations.

Q5: How would you design the billing metering system?

The billing metering system collects events from the data plane (query logs, DDL operations, storage metrics) through a streaming pipeline. Events are aggregated into hourly buckets per customer and per metric. The hourly aggregates are stored in a time-series database for real-time dashboards and rolled up into daily and monthly totals for invoicing. The system uses idempotent event processing to handle duplicates and provides reconciliation tools to verify metering accuracy against actual resource consumption.

Q6: How would you handle a primary MySQL failure in a sharded cluster?

The failover controller continuously monitors primary health through lightweight health checks. When a primary fails, the controller examines all replicas for that shard, selects the one with the least replication lag (verified by comparing GTIDs), and promotes it to primary. The promotion involves updating MySQL replication topology, updating Vitess's topology in etcd, and routing new traffic to the promoted replica. The remaining replicas are reconfigured to replicate from the new primary. The entire process typically completes in 10-30 seconds.

Q7: How would you design the multi-region read replica system?

The multi-region system extends the single-region replication model by streaming the primary's binlog to read replicas in other regions through Vitess's VStream mechanism. The stream is compressed and batched to minimize cross-region data transfer. Client connections in each region are routed to the nearest replica by default. When strong consistency is required, the vtgate layer checks the replica's replication lag and routes to the primary if the lag exceeds the configured threshold. The system monitors replica health and automatically falls back to alternative endpoints if a replica becomes unavailable.

Q8: How would you approach migrating a large MySQL database to PlanetScale?

The migration follows a four-phase approach: assessment (schema compatibility audit, query analysis), preparation (schema modification for PlanetScale compatibility, shard key selection), migration (data transfer using logical or physical backup methods, traffic cutover using dual-write or blue-green approach), and validation (data integrity verification, performance monitoring, optimization). For zero-downtime migrations, the dual-write approach writes to both systems during the transition period, gradually shifts reads to PlanetScale, and then cuts over writes.

Q9: How would you design the query insights pipeline?

The insights pipeline collects query telemetry from vtgate query logs, processes events through a streaming pipeline that normalizes queries (replacing literal values with placeholders), aggregates statistics by query template, and computes metrics like latency percentiles, rows examined, and error rates. The aggregated data is stored in a time-series database optimized for analytical queries. The analysis layer runs slow query detection, index recommendation, and trend analysis algorithms against the stored metrics, producing actionable insights surfaced through the web console and API.

Q10: What are the key trade-offs in PlanetScale's architecture?

The key trade-offs include: (1) Horizontal scalability vs. query flexibility — sharding enables scale but restricts cross-shard queries; (2) Managed complexity vs. control — PlanetScale automates operations but limits low-level customization; (3) MySQL compatibility vs. distributed features — some MySQL features (foreign keys, XA transactions) are not supported in the distributed environment; (4) Cost vs. availability — multi-zone and multi-region replication provide high availability but increase cost; (5) Consistency vs. latency — strong consistency requires routing to the primary, increasing latency for distant clients.

Summary Table

Question TopicKey ConceptDesign Pattern
Schema BranchingVersioned schema storage with DDL replayEvent sourcing / content-addressable storage
Online DDLShadow table with binlog-based syncWrite-ahead shadow copy
Connection MultiplexingSession-aware connection poolingProxy pool pattern
Deploy RequestsAutomated schema analysis and reviewCI/CD pipeline for databases
Billing MeteringEvent-driven usage aggregationStreaming analytics pipeline
FailoverGTID-based replica promotionActive-passive HA
Multi-RegionCompressed binlog streaming with consistencyGeo-distributed read replicas
MigrationPhased approach with dual-write optionStrangler fig / blue-green deployment
Query InsightsNormalized telemetry with analyticsOLAP on OLTP workload
Trade-offsScalability vs. flexibility, managed vs. controlledArchitecture decision records

Ayodhyya - System Design Blog Series | PlanetScale Serverless MySQL Platform - Senior+ Guide

Article #199 | Published June 4, 2024