LESSON 30分

ストーリー

「本番環境で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マイグレーションツール特徴
PrismaPrisma Migrateスキーマファーストで自動生成
TypeORMTypeORM Migrationエンティティから自動生成
Knex.jsKnex MigrateSQL + JS の柔軟性
SQLAlchemyAlembicPython 標準
DjangoDjango 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分