mcpField
D
database-migration

database-migration

github.com/sickn33データ・データベース48.8k405 回インストール今日更新
リポジトリ

database-migration について

データベース移行に取り組むためのガイダンススキルです。Sequelize、TypeORM、Prisma間のORM移行に加え、スキーマやデータの変換、ロールバック、ゼロダウンタイム展開を扱います。例と検証の指針を含みますが、一部の参照資料やスクリプトはカタログ版に含まれていません。

主な機能

  1. 01Sequelize、TypeORM、Prismaの移行
  2. 02データベーススキーマ変更の指針
  3. 03データ変換とデータベース間移行
  4. 04ロールバックと復旧戦略
  5. 05ゼロダウンタイム移行の指針
  6. 06移行の検証とベストプラクティス

活用シーン

  1. 01ORM間の移行
  2. 02スキーマやデータモデルの再設計
  3. 03データ移行とロールバックの計画
  4. 04停止時間を抑えたデータベース更新

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/sickn33/agentic-awesome-skills --skill database-migration

インストール後、Claude Code で/database-migrationを入力して読み込まれたか確認します。

詳細

サポート資料の提供状況

このカタログの記載には以下のインラインガイダンスが含まれていますが、次のサポート用パスのファイルは含まれていません。これらは外部の前提条件であり、同梱ファイルではありません。

  • <support-root>/references/orm-switching.md
  • <support-root>/references/schema-migration.md
  • <support-root>/references/data-transformation.md
  • <support-root>/references/rollback-strategies.md
  • <support-root>/assets/schema-migration-template.sql
  • <support-root>/assets/data-migration-script.py
  • <support-root>/scripts/test-migration.sh

これらのパスに依存する手順を実行する前に、元のサポート資料または既存のインストール環境を求めてください。このカタログでは、検証済みのダウンロード元は特定されていません。各パスをそのインストール環境からの相対パスとして解決し、内容を確認したうえで、該当するライセンスと実行の前提条件を検証してください。必要なファイルが存在することを確認するまでは、コマンドを実行したり、参照先の手順に従ったと主張したりしないでください。不足しているスクリプトを捏造したり、同じ名前の無関係なファイルを同等のものとして扱ったりしないでください。ここで手順が完全に説明されている独立した作業は、ユーザーが許可した範囲内で進められます。依存する手順は利用できないことを報告してください。

データベース移行

Sequelize、TypeORM、Prisma などの ORM 間で、データベースのスキーマとデータの移行、ロールバック戦略、ゼロダウンタイムデプロイを扱います。

このスキルを使用しない場合

  • データベース移行に関係のないタスクの場合
  • この範囲外の別の分野やツールが必要な場合

手順

  • 目標、制約、必要な入力を明確にします。
  • 関連するベストプラクティスを適用し、結果を検証します。
  • 実行可能な手順と検証方法を提示します。

このスキルを使用する場合

  • 異なる ORM 間を移行する場合
  • スキーマ変換を行う場合
  • データベース間でデータを移動する場合
  • ロールバック手順を実装する場合
  • ゼロダウンタイムデプロイを行う場合
  • データベースのバージョンをアップグレードする場合
  • データモデルをリファクタリングする場合

ORM の移行

Sequelize の移行
// migrations/20231201-create-users.js
module.exports = {
  up: async (queryInterface, Sequelize) => {
    await queryInterface.createTable('users', {
      id: {
        type: Sequelize.INTEGER,
        primaryKey: true,
        autoIncrement: true
      },
      email: {
        type: Sequelize.STRING,
        unique: true,
        allowNull: false
      },
      createdAt: Sequelize.DATE,
      updatedAt: Sequelize.DATE
    });
  },

  down: async (queryInterface, Sequelize) => {
    await queryInterface.dropTable('users');
  }
};

// Run: npx sequelize-cli db:migrate
// Rollback: npx sequelize-cli db:migrate:undo
TypeORM の移行
// migrations/1701234567-CreateUsers.ts
import { MigrationInterface, QueryRunner, Table } from 'typeorm';

export class CreateUsers1701234567 implements MigrationInterface {
  public async up(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.createTable(
      new Table({
        name: 'users',
        columns: [
          {
            name: 'id',
            type: 'int',
            isPrimary: true,
            isGenerated: true,
            generationStrategy: 'increment'
          },
          {
            name: 'email',
            type: 'varchar',
            isUnique: true
          },
          {
            name: 'created_at',
            type: 'timestamp',
            default: 'CURRENT_TIMESTAMP'
          }
        ]
      })
    );
  }

  public async down(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.dropTable('users');
  }
}

// Run: npm run typeorm migration:run
// Rollback: npm run typeorm migration:revert
Prisma の移行
// schema.prisma
model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  createdAt DateTime @default(now())
}

// Generate migration: npx prisma migrate dev --name create_users
// Apply: npx prisma migrate deploy

スキーマ変換

デフォルト値を指定して列を追加する
// Illustrative migration: lock/rewrite behavior depends on engine/version/table size
module.exports = {
  up: async (queryInterface, Sequelize) => {
    await queryInterface.addColumn('users', 'status', {
      type: Sequelize.STRING,
      defaultValue: 'active',
      allowNull: false
    });
  },

  down: async (queryInterface) => {
    await queryInterface.removeColumn('users', 'status');
  }
};
列名の変更(Expand/Contract の概要)

この概要は、同時書き込みに対応するには不十分です。互換性のある二重書き込みを先にデプロイしてから、上限を設けたバックフィルを行い、バックフィル中に変更を調整してください。検証が完了してから読み取りを切り替え、すべての読み取り側と書き込み側の移行後にのみ古い列を削除してください。一度だけの UPDATE では、更新が失われる可能性があります。

// Step 1: Add new column
module.exports = {
  up: async (queryInterface, Sequelize) => {
    await queryInterface.addColumn('users', 'full_name', {
      type: Sequelize.STRING
    });

    // Copy data from old column
    await queryInterface.sequelize.query(
      'UPDATE users SET full_name = name'
    );
  },

  down: async (queryInterface) => {
    await queryInterface.removeColumn('users', 'full_name');
  }
};

// Step 2: Update application to use new column

// Step 3: Remove old column
module.exports = {
  up: async (queryInterface) => {
    await queryInterface.removeColumn('users', 'name');
  },

  down: async (queryInterface, Sequelize) => {
    await queryInterface.addColumn('users', 'name', {
      type: Sequelize.STRING
    });
  }
};
列の型を変更する

この例は、互換性を損なう可能性があり、データ損失も起こり得る移行であり、ゼロダウンタイムを実現する手順ではありません。まずキャスト、範囲、NULL の扱いを検証してください。別途承認されたコントラクト工程まで元の値を保持してください。キャストで元に戻しても、破棄されたデータは復元できません。

module.exports = {
  up: async (queryInterface, Sequelize) => {
    // For large tables, use multi-step approach

    // 1. Add new column
    await queryInterface.addColumn('users', 'age_new', {
      type: Sequelize.INTEGER
    });

    // 2. Copy and transform data
    await queryInterface.sequelize.query(`
      UPDATE users
      SET age_new = CAST(age AS INTEGER)
      WHERE age IS NOT NULL
    `);

    // 3. Drop old column
    await queryInterface.removeColumn('users', 'age');

    // 4. Rename new column
    await queryInterface.renameColumn('users', 'age_new', 'age');
  },

  down: async (queryInterface, Sequelize) => {
    await queryInterface.changeColumn('users', 'age', {
      type: Sequelize.STRING
    });
  }
};

データ変換

複雑なデータ移行

これは解析方法の例にすぎません。まず住所のスキーマを定義して検証してください。カンマで分割すると情報が失われます。上限を設けたバッチとチェックポイントを使用し、照合とテスト済みの復旧計画が完了するまで、元の列を保持してください。以下の down() による再構築では、元の形式は復元されません。

module.exports = {
  up: async (queryInterface, Sequelize) => {
    // Get all records
    const [users] = await queryInterface.sequelize.query(
      'SELECT id, address_string FROM users'
    );

    // Transform each record
    for (const user of users) {
      const addressParts = user.address_string.split(',');

      await queryInterface.sequelize.query(
        `UPDATE users
         SET street = :street,
             city = :city,
             state = :state
         WHERE id = :id`,
        {
          replacements: {
            id: user.id,
            street: addressParts[0]?.trim(),
            city: addressParts[1]?.trim(),
            state: addressParts[2]?.trim()
          }
        }
      );
    }

    // Drop old column
    await queryInterface.removeColumn('users', 'address_string');
  },

  down: async (queryInterface, Sequelize) => {
    // Reconstruct original column
    await queryInterface.addColumn('users', 'address_string', {
      type: Sequelize.STRING
    });

    await queryInterface.sequelize.query(`
      UPDATE users
      SET address_string = CONCAT(street, ', ', city, ', ', state)
    `);

    await queryInterface.removeColumn('users', 'street');
    await queryInterface.removeColumn('users', 'city');
    await queryInterface.removeColumn('users', 'state');
  }
};

ロールバック戦略

トランザクションベースの移行
module.exports = {
  up: async (queryInterface, Sequelize) => {
    const transaction = await queryInterface.sequelize.transaction();

    try {
      await queryInterface.addColumn(
        'users',
        'verified',
        { type: Sequelize.BOOLEAN, defaultValue: false },
        { transaction }
      );

      await queryInterface.sequelize.query(
        'UPDATE users SET verified = true WHERE email_verified_at IS NOT NULL',
        { transaction }
      );

      await transaction.commit();
    } catch (error) {
      await transaction.rollback();
      throw error;
    }
  },

  down: async (queryInterface) => {
    await queryInterface.removeColumn('users', 'verified');
  }
};
チェックポイントベースの復旧

データベースエンジンでテスト済みのバックアップ/スナップショットおよびポイントインタイムリカバリ手順を使用し、使い捨ての復元環境で検証してください。移行前に、復旧ポイント、スキーマ、制約、インデックス、シーケンス、権限、アプリケーションとの互換性を記録してください。CREATE TABLE AS SELECT によるコピーは完全なバックアップではありません。自動エラー復旧中に、稼働中のテーブルを DROP して再作成するために使用してはなりません。重要なスキーマプロパティが欠落し、同時書き込みが失われる可能性があります。部分的な状態に不確実な点がある場合は、処理を停止し、再試行する前に整合性を確認してください。

ゼロダウンタイム移行

Blue-Green デプロイ戦略
// Phase 1: Make changes backward compatible
module.exports = {
  up: async (queryInterface, Sequelize) => {
    // Add new column (both old and new code can work)
    await queryInterface.addColumn('users', 'email_new', {
      type: Sequelize.STRING
    });
  }
};

// Phase 2: Deploy code that writes to both columns

// Phase 3: Backfill data
module.exports = {
  up: async (queryInterface) => {
    await queryInterface.sequelize.query(`
      UPDATE users
      SET email_new = email
      WHERE email_new IS NULL
    `);
  }
};

// Phase 4: Deploy code that reads from new column

// Phase 5: Remove old column
module.exports = {
  up: async (queryInterface) => {
    await queryInterface.removeColumn('users', 'email');
  }
};

データベース間の移行

PostgreSQL から MySQL への移行
// Handle differences
module.exports = {
  up: async (queryInterface, Sequelize) => {
    const dialectName = queryInterface.sequelize.getDialect();

    if (dialectName === 'mysql') {
      await queryInterface.createTable('users', {
        id: {
          type: Sequelize.INTEGER,
          primaryKey: true,
          autoIncrement: true
        },
        data: {
          type: Sequelize.JSON  // MySQL JSON type
        }
      });
    } else if (dialectName === 'postgres') {
      await queryInterface.createTable('users', {
        id: {
          type: Sequelize.INTEGER,
          primaryKey: true,
          autoIncrement: true
        },
        data: {
          type: Sequelize.JSONB  // PostgreSQL JSONB type
        }
      });
    }
  }
};

リソース

  • references/orm-switching.md:ORM の移行ガイド
  • references/schema-migration.md:スキーマ変換のパターン
  • references/data-transformation.md:データ移行スクリプト
  • references/rollback-strategies.md:ロールバック手順
  • assets/schema-migration-template.sql:SQL 移行テンプレート
  • assets/data-migration-script.py:データ移行ユーティリティ
  • scripts/test-migration.sh:移行テストスクリプト

ベストプラクティス

  1. 復旧を計画する:本当に元に戻せる場合に限り、テスト済みの down() を用意します。それ以外の場合は、前進修正またはテスト済みの復元手順と、データ損失の境界を文書化します
  2. 移行をテストする:まずステージング環境でテストします
  3. トランザクションを使用する:可能な場合はアトミックな移行にします
  4. 事前にバックアップする:移行前には必ずバックアップします
  5. 変更を小さくする:小さく段階的な手順に分割します
  6. 監視する:デプロイ中にエラーが発生していないか確認します
  7. 文書化する:理由と方法を説明します
  8. 冪等性を保つ:移行を再実行できるようにします

よくある落とし穴

  • ロールバック手順をテストしない
  • ダウンタイム対策なしに互換性を損なう変更を行う
  • NULL 値の処理を忘れる
  • インデックスのパフォーマンスを考慮しない
  • 外部キー制約を無視する
  • 一度に大量のデータを移行する

制限事項

  • このスキルは、タスクが上記の説明に明確に該当する場合にのみ使用してください。
  • 出力を、環境固有の検証、テスト、専門家によるレビューの代わりとして扱わないでください。
  • 必要な入力、権限、安全上の境界、または成功基準が不足している場合は、作業を中断して確認を求めてください。

コメント