ストーリー
「本番環境でALTER TABLEを実行したら、テーブルロックで30分間サービスが止まった…」
高橋アーキテクトが報告を受けて言う。
「スキーマ変更を本番で直接実行するのは危険だ。マイグレーションは計画的に、再現可能な手順で、安全に行う必要がある。今日はその戦略を学ぼう」
マイグレーションとは
データベーススキーマを安全に、再現可能な形で変更する仕組み。
なぜマイグレーションツールが必要か
| 問題 | マイグレーションの解決 |
|---|---|
| 手動SQLの実行忘れ | バージョン管理で漏れ防止 |
| 環境ごとのスキーマの差異 | 全環境で同じ手順を適用 |
| ロールバック不能 | down関数で逆操作を定義 |
| 変更履歴が不明 | マイグレーションファイルが履歴 |
| チーム間の同期 | gitでマイグレーションを共有 |
マイグレーションの基本構造
Up(適用)と Down(ロールバック)
// Prisma Migrate の例
// prisma/migrations/20240315_add_user_profile/migration.sql
-- Up: 適用
CREATE TABLE user_profiles (
id SERIAL PRIMARY KEY,
user_id INT NOT NULL UNIQUE REFERENCES users(id),
bio TEXT,
avatar_url VARCHAR(500),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_user_profiles_user_id ON user_profiles(user_id);
// Knex.js の例(Up/Downが明確)
export async function up(knex: Knex): Promise<void> {
await knex.schema.createTable('user_profiles', (table) => {
table.increments('id').primary();
table.integer('user_id').notNullable().unique().references('id').inTable('users');
table.text('bio');
table.string('avatar_url', 500);
table.timestamp('created_at').notNullable().defaultTo(knex.fn.now());
});
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTable('user_profiles');
}
マイグレーションの種類
1. スキーマ変更
-- テーブル追加
CREATE TABLE reviews (...);
-- カラム追加
ALTER TABLE users ADD COLUMN phone VARCHAR(20);
-- カラム型変更
ALTER TABLE products ALTER COLUMN price TYPE DECIMAL(12, 2);
-- インデックス追加
CREATE INDEX CONCURRENTLY idx_orders_status ON orders(status);
2. データマイグレーション
// データの変換・移行
export async function up(knex: Knex): Promise<void> {
// 新カラム追加
await knex.schema.alterTable('users', (table) => {
table.string('full_name', 200);
});
// 既存データの変換
await knex.raw(`
UPDATE users SET full_name = first_name || ' ' || last_name
WHERE full_name IS NULL
`);
// NOT NULL制約を追加
await knex.schema.alterTable('users', (table) => {
table.string('full_name', 200).notNullable().alter();
});
}
3. 複合マイグレーション
// スキーマ変更 + データ移行 + 旧スキーマ削除
// Phase 1: 新カラム追加(デプロイ1)
export async function up_phase1(knex: Knex): Promise<void> {
await knex.schema.alterTable('users', (table) => {
table.string('email_normalized'); // NULLable で追加
});
}
// Phase 2: データ移行(デプロイ2)
export async function up_phase2(knex: Knex): Promise<void> {
await knex.raw(`
UPDATE users SET email_normalized = LOWER(email)
`);
await knex.schema.alterTable('users', (table) => {
table.string('email_normalized').notNullable().alter();
table.unique(['email_normalized']);
});
}
// Phase 3: 旧カラム削除(デプロイ3、十分な検証後)
export async function up_phase3(knex: Knex): Promise<void> {
await knex.schema.alterTable('users', (table) => {
table.dropColumn('email');
});
await knex.schema.alterTable('users', (table) => {
table.renameColumn('email_normalized', 'email');
});
}
マイグレーションのベストプラクティス
1. マイグレーションは小さく
NG: 1つのマイグレーションで10テーブル変更
OK: テーブルごとに個別のマイグレーション
2. 冪等性を意識
-- 冪等なマイグレーション: 何度実行しても同じ結果
CREATE TABLE IF NOT EXISTS users (...);
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
3. ロールバック可能に
// downが書けないマイグレーションは注意
export async function down(knex: Knex): Promise<void> {
// データの復元が不可能な場合は明示する
throw new Error('This migration cannot be rolled back. Data loss would occur.');
}
4. 命名規則
migrations/
├── 20240301_001_create_users_table.sql
├── 20240301_002_add_email_index.sql
├── 20240315_001_create_orders_table.sql
├── 20240315_002_add_user_profile.sql
└── 20240320_001_add_phone_to_users.sql
ORMごとのマイグレーションツール
| ORM | マイグレーションツール | 特徴 |
|---|---|---|
| Prisma | Prisma Migrate | スキーマファーストで自動生成 |
| TypeORM | TypeORM Migration | エンティティから自動生成 |
| Knex.js | Knex Migrate | SQL + JS の柔軟性 |
| SQLAlchemy | Alembic | Python 標準 |
| Django | Django Migrations | モデルから自動生成 |
Prismaの例
# スキーマ変更後にマイグレーション生成
npx prisma migrate dev --name add_user_profile
# 本番適用
npx prisma migrate deploy
# マイグレーション状態の確認
npx prisma migrate status
マイグレーション実行のフロー
1. 開発環境でマイグレーション作成
2. ローカルDBで動作確認
3. コードレビュー(マイグレーションもレビュー対象)
4. ステージング環境で検証
5. 本番環境で適用(メンテナンスウィンドウ or ゼロダウンタイム)
6. 適用後の動作確認
7. 問題発生時はロールバック
まとめ
| ポイント | 内容 |
|---|---|
| マイグレーションの目的 | 再現可能で安全なスキーマ変更 |
| Up/Down | 適用と逆操作をセットで定義 |
| 種類 | スキーマ変更、データ移行、複合 |
| ベストプラクティス | 小さく、冪等に、ロールバック可能に |
| 実行フロー | 開発 → レビュー → ステージング → 本番 |
理解度チェックリスト
- マイグレーションの Up/Down を設計できる
- データマイグレーションを含む複合マイグレーションを計画できる
- 冪等なマイグレーションの書き方を理解している
- マイグレーション実行のフローを説明できる
次のステップ
次のレッスンではゼロダウンタイムマイグレーションを学ぶ。サービスを止めずにスキーマ変更を行う高度な技法を身につけよう。
推定読了時間: 30分