migration-planner について
本番環境の移行を担当するチーム向けに、停止時間を抑えた移行を計画するスキルです。段階的な展開、検証、リスク対策、ロールバックを整理します。
主な機能
- 01フィーチャーフラグとブルーグリーン方式
- 02デュアルライトとバックフィルの計画
- 03データ検証戦略
- 04フェーズ別ロールバック計画
- 05リスクと連絡計画
活用シーン
- 01データベース移行の計画
- 02読み取りトラフィックの段階的切り替え
- 03検証とロールバック手順の準備
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ patricio0312rev/ skills --skill migration-plannerインストール後、Claude Code で/migration-plannerを入力して読み込まれたか確認します。
詳細
移行プランナー
検証とロールバック計画を備えた、安全でダウンタイムゼロの移行を実行します。
移行パターン
1. フィーチャーフラグによる移行(最も安全)
Phase 1: Deploy new code (disabled)
Phase 2: Enable for 1% traffic
Phase 3: Ramp to 10%, 50%, 100%
Phase 4: Remove old code
2. デュアルライトによる移行
Phase 1: Write to both old and new
Phase 2: Backfill old → new
Phase 3: Read from new (write both)
Phase 4: Stop writing to old
Phase 5: Decommission old
3. ブルーグリーンデプロイ
Blue (current) → Green (new)
Switch traffic: Blue → Green
Rollback available: Green → Blue
完全な移行計画テンプレート
# Migration Plan: MySQL → PostgreSQL
## Overview
**What:** Migrate user database from MySQL to PostgreSQL
**Why:** Better JSON support, improved performance
**When:** Q1 2024
**Owner:** Database Team
**Risk Level:** HIGH
## Current State
- MySQL 8.0
- 500GB data
- 100K users
- 1000 writes/min
- 10,000 reads/min
## Target State
- PostgreSQL 15
- Same data model
- No downtime
- Data validation 100% match
## Phases
### Phase 1: Dual Write (Week 1-2)
**Goal:** Write to both databases
**Steps:**
1. Deploy PostgreSQL cluster
2. Create schema in PostgreSQL
3. Deploy dual-write code
4. Enable dual writes (MySQL primary, PostgreSQL secondary)
**Code:**
```typescript
async function createUser(data: CreateUserDto) {
// Write to MySQL (primary)
const mysqlUser = await mysql.users.create(data);
// Write to PostgreSQL (secondary, fire and forget)
postgres.users.create(data).catch((err) => {
logger.error("PostgreSQL write failed", err);
});
return mysqlUser; // Still trust MySQL
}
```
検証:
- PostgreSQL の書き込み成功率を監視する
- 行数を毎日比較する
- 差異が 0.1% を超えた場合にアラートを出す
ロールバック: PostgreSQL への書き込みを無効にする
フェーズ 2:バックフィル(第 3~4 週)
目標: 過去のデータをコピーする
手順:
- MySQL のスナップショットを取得する
- バックフィルスクリプトをバッチで実行する
- データの整合性を検証する
- 失敗した場合は自動的に再開する
スクリプト:
def backfill():
last_id = get_last_migrated_id()
batch_size = 1000
while True:
users = mysql.query(
"SELECT * FROM users WHERE id > %s LIMIT %s",
[last_id, batch_size]
)
if not users:
break
postgres.bulk_insert(users)
last_id = users[-1]['id']
save_checkpoint(last_id)
time.sleep(0.1) # Rate limit
検証:
- 行数が一致すること
- ランダムサンプルを比較する(1000 行)
- チェックサムを比較する
ロールバック: PostgreSQL のデータを削除する
フェーズ 3:デュアルリード(第 5 週)
目標: PostgreSQL の読み取りを検証する
手順:
- シャドーリードのコードをデプロイする
- 両方から読み取る(MySQL をプライマリにする)
- 結果を比較する
- 不一致をログに記録する
コード:
async function getUser(id: string) {
const mysqlUser = await mysql.users.findById(id);
// Shadow read from PostgreSQL
postgres.users.findById(id).then((pgUser) => {
if (!deepEqual(mysqlUser, pgUser)) {
logger.warn("Data mismatch", { id, mysqlUser, pgUser });
metrics.increment("migration.mismatch");
}
});
return mysqlUser; // Still trust MySQL
}
検証:
- 不一致率が 0.01% 未満であること
- PostgreSQL のクエリパフォーマンスが許容範囲であること
ロールバック: シャドーリードを削除する
フェーズ 4:読み取りトラフィックの切り替え(第 6 週)
目標: PostgreSQL から読み取る
手順:
- フィーチャーフラグを設定し、PostgreSQL から読み取る(トラフィックの 1%)
- エラーとレイテンシを監視する
- 段階的に増やす:1% → 10% → 50% → 100%
- 両方への書き込みは継続する
コード:
async function getUser(id: string) {
if (featureFlags.readFromPostgres) {
return postgres.users.findById(id);
}
return mysql.users.findById(id);
}
検証:
- エラー率に変化がないこと
- レイテンシの p95 が 500ms 未満であること
- ユーザーからの苦情がないこと
ロールバック: フィーチャーフラグをオフにする
フェーズ 5:MySQL への書き込みを停止(第 7 週)
目標: PostgreSQL をプライマリにする
手順:
- MySQL への書き込みを停止する
- MySQL を読み取り専用で稼働させ続ける
- 問題がないか監視する
コード:
async function createUser(data: CreateUserDto) {
return postgres.users.create(data);
// No longer writing to MySQL
}
検証:
- すべての操作が正常に動作すること
- MySQL が書き込みを受信していないこと
ロールバック: MySQL への書き込みを再び有効にする
フェーズ 6:廃止(第 8 週)
目標: MySQL を削除する
手順:
- MySQL のデータをアーカイブする
- MySQL クラスターをシャットダウンする
- MySQL クライアントコードを削除する
ロールバック: 不可(後戻りできない時点)
検証戦略
データ整合性チェック
def validate_migration():
# Row counts
mysql_count = mysql.query("SELECT COUNT(*) FROM users")[0]
pg_count = postgres.query("SELECT COUNT(*) FROM users")[0]
assert mysql_count == pg_count
# Random sampling
sample = mysql.query("SELECT * FROM users ORDER BY RAND() LIMIT 1000")
for row in sample:
pg_row = postgres.query("SELECT * FROM users WHERE id = %s", [row['id']])
assert row == pg_row
# Checksums
mysql_checksum = mysql.query("SELECT MD5(GROUP_CONCAT(id, email)) FROM users")
pg_checksum = postgres.query("SELECT MD5(STRING_AGG(id::text || email, '')) FROM users")
assert mysql_checksum == pg_checksum
ロールバック計画
フェーズ 1~3 のロールバック(容易)
- PostgreSQL への書き込みを無効にする
- ユーザーへの影響なし
- MySQL のデータは引き続き有効
フェーズ 4 のロールバック(中程度)
- フィーチャーフラグを切り替える
- 読み取り先を MySQL に戻す
- ユーザーへの軽微な影響(数秒)
フェーズ 5 以降のロールバック(困難)
- MySQL への書き込みを再び有効にする必要がある
- データが失われる可能性がある(フェーズ 5 以降の書き込み)
- デュアルライトの再開が必要
リスク軽減
リスク 1:データ損失
軽減策:
- 検証が完了するまでデュアルライトを続ける
- トランザクションログを取得する
- 継続的にバックアップする
リスク 2:パフォーマンスの低下
軽減策:
- PostgreSQL の負荷テストを行う
- クエリを最適化する
- コネクションプーリングを使用する
リスク 3:スキーマの違い
軽減策:
- スキーマ検証スクリプトを用意する
- ステージング環境で移行をテストする
- データ型の違いを文書化する
コミュニケーション計画
ステークホルダーへの進捗共有
**Week 0:** Migration announced
**Week 2:** Phase 1 complete (dual writes)
**Week 4:** Backfill complete
**Week 6:** Traffic shifted to PostgreSQL
**Week 8:** Migration complete
ステータスダッシュボード
- 現在のフェーズ
- データ同期の状況(%)
- 検証結果
- エラー率
テスト計画
移行前のテスト
- 開発環境でテストする
- ステージング環境で移行を一通り実施する
- PostgreSQL の負荷テストを行う
- ロールバック手順を検証する
移行中
- 継続的に監視する
- 検証を自動化する
- 手動で抜き取りチェックを行う
- ユーザー受け入れテストを実施する
ベストプラクティス
- 小さなバッチ: 段階的に移行する
- デュアルライト: 両方のシステムの同期を維持する
- フィーチャーフラグ: 段階的な展開を制御する
- 継続的に検証する: 信頼するだけでなく、確認する
- ロールバックに備える: 最悪のケースに備えて計画する
- 注意深く監視する: メトリクスを追跡する
- こまめに共有する: ステークホルダーに常に情報を伝える
出力チェックリスト
- 移行フェーズを定義する(5~7 フェーズ)
- デュアルライトを実装する
- バックフィルスクリプトを準備する
- 検証戦略を策定する
- フィーチャーフラグを設定する
- フェーズごとのロールバック計画を立てる
- リスク軽減策を策定する
- コミュニケーション計画を立てる
- 監視ダッシュボードを用意する
- テストチェックリストを用意する
コメント
Markdown 対応