誰も語らないスキーママイグレーションの問題
本番環境のMySQLテーブルで1000万件のレコードに対して ALTER TABLE を実行するのは、バックエンドエンジニアを夜も眠れなくさせる類のものです。私も経験があります。マイグレーションがテーブルを40分間ロックし続ける中、サポートチャンネルが苦情で溢れかえるのを眺めていました。MySQL、PostgreSQL、MongoDBをさまざまなプロジェクトで使ってきましたが、それぞれに強みがあります。ただし、MySQLのスキーママイグレーションの歴史は、この3つの中で最も苦痛が多かったと言えます。
根本的な問題は、MySQLのDDL操作がメタデータロックを取得し、読み書きの両方をブロックする可能性があることです。pt-online-schema-change や gh-ost といったツールを使っても、シャドウテーブル、トリガー、レースコンディションを扱わなければなりません。小規模なチームやスタートアップにとって、この複雑さはデータベース変更を自信を持ってリリースする上での大きな障壁となります。
PlanetScaleは根本的に異なるアプローチを取っています。GitがコードをブランチとマージリクエストI管理するのと同じように、データベースのスキーマ変更を扱います。その結果、スキーママイグレーションはノンブロッキングで、レビュー可能で、ロールバックも可能になります。
まず理解すべき基本概念
PlanetScaleとは何か
PlanetScaleは、YouTubeが2011年から使用しているシャーディングおよびコネクション管理システムである Vitess の上に構築された、サーバーレスのMySQL互換データベースプラットフォームです。MySQLワイヤープロトコルの互換性(既存のORMやドライバがそのまま動作)に加え、内部でVitessのノンブロッキングDDLエンジンが使われています。
「サーバーレス」というのは、インスタンス、レプリカ、コネクション数を手動で管理しなくてよいということです。スケーリングは自動で処理され、料金はストレージと行の読み書き数に基づいて課金されます。時間単位での課金ではありません。
データベースブランチング
すべてのPlanetScaleデータベースには、保護された本番ブランチ(直接DDLは不可)と、任意の数の開発ブランチがあります。ブランチはスキーマの完全なコピーであり、データのコピーではありません。そのため、本番環境に一切触れることなく、独立した環境でマイグレーションを自由に実行できます。
次のようにイメージしてください:
- main — 本番ブランチ(スキーマ変更は読み取り専用)
- add-user-avatar — カラムを追加する開発ブランチ
- refactor-indexes — 並行して作業する別の開発ブランチ
ブランチの作成は安価で高速です。本番環境のパフォーマンスに影響を与えることなく、数十のブランチを同時に実行できます。
デプロイリクエスト
スキーマ変更の準備ができたら、デプロイリクエストを作成します。これはデータベースマイグレーション向けの、PlanetScaleにおけるプルリクエストに相当します。実行されるDDLの差分が表示され、チームメンバーがレビューでき、その後VitessのOnline DDLエンジンを使って本番環境に適用されます。テーブルロックもダウンタイムも発生しません。
ノンブロッキングなスキーマ変更
VitessのDDL戦略(vitess または online)は、gh-ost に似たシャドウテーブルアプローチを使用しますが、プラットフォームに組み込まれています。スキーマ変更をデプロイすると、Vitessは:
- 目的のスキーマで新しいシャドウテーブルを作成
- 読み書きをブロックせずにバッチで行をコピー
- バイナリログの追跡によって継続的な変更を適用
- 追いついたら原子的な切り替えを実行
プロセス全体は透過的に行われます。アプリケーションは終始トラフィックを提供し続けます。
実践:初めてのスキーママイグレーションをデプロイする
ステップ1 — PlanetScale CLIをインストールする
pscale CLIはローカルですべてを管理するためのツールです。パッケージマネージャーでインストールしてください:
# macOS
brew install planetscale/tap/pscale
# Linux (Debian/Ubuntu)
curl -fsSL https://cli.planetscale.com/install.sh | bash
# 確認
pscale version
ステップ2 — 認証とデータベースの作成
# OAuthログインのためブラウザが開く
pscale auth login
# 新しいデータベースを作成(無料ティアあり)
pscale database create my-app-db --region us-east
# ステータスを確認
pscale database show my-app-db
データベースにはすぐに使える main ブランチが付いています。無料ティアでは5GBのストレージと月10億行の読み取りが使えます。ほとんどの個人プロジェクトやステージング環境には十分すぎるほどです。
ステップ3 — プロキシ経由でローカル接続する
PlanetScaleはすべての接続にmTLSを使用します。CLIプロキシがこれを代わりに処理し、アプリが通常通り接続できるローカルMySQLソケットを提供します:
# 本番ブランチに接続
pscale connect my-app-db main --port 3309
# 任意のMySQLクライアントで接続
mysql -h 127.0.0.1 -P 3309 -u root
アプリケーションの場合は、プロキシではなくPlanetScaleダッシュボードの接続文字列を使用してください。適切なホスト名とクレデンシャルが得られます。
ステップ4 — スキーマ変更用の開発ブランチを作成する
main に直接DDLを実行しないでください。必ずブランチを先に作成してください:
# mainからブランチを作成
pscale branch create my-app-db add-user-avatar --from main
# 開発ブランチに接続
pscale connect my-app-db add-user-avatar --port 3310
次に、開発ブランチに対してマイグレーションを実行します:
-- ポート3310に接続してDDLを自由に実行
ALTER TABLE users ADD COLUMN avatar_url VARCHAR(500) DEFAULT NULL;
CREATE INDEX idx_users_created_at ON users (created_at);
ALTER TABLE posts ADD COLUMN reading_time_minutes TINYINT UNSIGNED DEFAULT 0;
これらのDDL文は、本番データが存在しないため、開発ブランチで即座に実行されます。スキーマ定義を変更しているだけです。
ステップ5 — デプロイリクエストを作成する
# デプロイリクエストを作成(ブランチ → main)
pscale deploy-request create my-app-db add-user-avatar
WebUIからも操作でき、スキーマ変更のきれいな差分が表示されます。デプロイリクエストのリンクをチームと共有してレビューしてもらいましょう。承認されたら:
# デプロイリクエスト番号を取得
pscale deploy-request list my-app-db
# デプロイを実行
pscale deploy-request deploy my-app-db 1
PlanetScaleはマイグレーションをキューに入れ、Online DDLで実行します。数百万行のテーブルの場合、バックグラウンドで数分かかることがありますが、アプリケーションは一切の中断を経験しません。
ステップ6 — 監視と必要に応じたロールバック
# マイグレーションの進捗を確認
pscale deploy-request show my-app-db 1
# 問題が発生した場合はリバート
pscale deploy-request revert my-app-db 1
リバート操作は、同じノンブロッキングプロセスで追加したカラムやインデックスを削除します。私は本番環境でこれを2回使いました。1回目は新しいインデックスが予期しないクエリプランの後退を引き起こしたとき、2回目はカラムのデフォルト値が間違っていたときです。どちらのリバートもダウンタイムなしで完了しました。
アプリケーションからの接続
Prismaを使用するNode.js/TypeScriptアプリの場合、接続は標準的なMySQLです:
# .env — これらの値はPlanetScaleダッシュボード → Connect → Prismaから取得
DATABASE_URL="mysql://user:[email protected]/my-app-db?sslaccept=strict"
# PythonとSQLAlchemyの場合
import os
from sqlalchemy import create_engine
engine = create_engine(
os.environ["DATABASE_URL"],
connect_args={"ssl": {"ca": "/etc/ssl/certs/ca-certificates.crt"}}
)
実際の使用からのヒント
ブランチ名は日付ではなく機能で付ける
migration-2024-07 ではなく、add-payment-table や drop-legacy-columns のような説明的なブランチ名を使用してください。6ヶ月後にデプロイリクエストの履歴を確認する際、きっと自分に感謝するでしょう。
ブランチは短命に保つ
開発ブランチは main からのスキーマ変更を自動同期しません。main に他のマイグレーションがデプロイされている間、ブランチが数週間存在すると、デプロイリクエストでマージコンフリクトが発生します。作成からレビュー、デプロイまで1〜3日以内を目指しましょう。
カラムのリネームには「Expand/Contract」パターンを使う
PlanetScaleでは、既存のクエリを壊すようなカラムのリネームを1つのデプロイリクエストで行うことはできません。代わりにExpand/Contractパターンを使用してください:
- デプロイ:古いカラム
usernameの横に新しいカラムuser_nameを追加 - 両方のカラムに書き込むようにアプリケーションを更新
- バックフィル:古いカラムから新しいカラムへデータをコピー
- デプロイ:新しいカラムのみから読み込むようにアプリを更新
- デプロイ:古いカラムを削除
ステップは増えますが、各ステップは安全でロールバック可能です。
コールド接続にはBoostを活用する
PlanetScaleのサーバーレスな性質上、コールドスタートにより接続レイテンシのスパイクが発生することがあります。読み取りが多いワークロードにはPlanetScale Boost(クエリキャッシュ)を有効にし、アプリケーション層でコネクションプーリングを使用してください(PgBouncerに相当するものは不要です。PlanetScaleが内部で処理します)。
外部キーはサポートされていない
これが最大の落とし穴です。PlanetScaleはVitess(水平シャーディングをサポート)上に構築されているため、外部キー制約が無効になっています。参照整合性はアプリケーション層で強制する必要があります。スキーマが外部キーに大きく依存している場合は、移行を決める際にこの点を考慮してください。
PlanetScaleが適している場面
いくつかのプロジェクトで使った経験から、PlanetScaleを選ぶべき場面について率直な意見を述べます:
- 適している:スキーマ変更を頻繁にリリースするチーム、マイグレーション中のダウンタイムが許容できないアプリケーション、運用負荷なしでマネージドMySQLを求めるスタートアップ
- 再考が必要:外部キー制約が必要なワークロード、JOINを多用する重い分析処理(代わりにデータウェアハウスを検討)、または自己管理MySQLで
gh-ostを使い慣れているチーム
ブランチングモデルは、データベース変更に対する考え方を根本的に変えます。「メンテナンスウィンドウを計画しよう」という会話が、「ブランチを作って、変更して、レビューしてもらおう」に変わります。この作業フローの転換は、どんな特定の機能よりもはるかに価値があります。

