Communitygithub.com

データベースマイグレーション

PostgreSQL、MySQL、Prisma、Drizzle、Kysely、Django、TypeORM、golang-migrate などに対応した、スキーマ変更、データ移行、ロールバック、ダウンタイムゼロデプロイのためのデータベースマイグレーションのベストプラクティス。スキーマやデータのマイグレーション作成時、ロールバック計画時、ダウンタイムゼロデプロイを目指す際に使用します。

データベースマイグレーション とは?

このスキルは、データベースマイグレーションにおける安全で確実なパターンを提供し、特に本番環境でのスキーマ変更、データ移行、ロールバック、ダウンタイムゼロデプロイに焦点を当てています。PostgreSQL、MySQL、および主要なORM(Prisma、Drizzle、Kysely、Django、golang-migrate)をサポートしており、各ツール固有のワークフローや実装例を詳述しています。コア原則として「すべての変更はマイグレーションとして行う」「マイグレーションは前方のみ進行」「スキーマとデータのマイグレーションは分離する」を掲げ、安全チェックリストやアンチパターンも提示しています。特に、ダウンタイムゼロを実現するためのExpand-Contractパターン(カラム追加→バックフィル→アプリケーションコード変更→旧カラム削除)や、CONCURRENTLYオプションを使ったインデックス作成、大規模テーブルでのバッチ更新など、実践的なテクニックを網羅しています。」

対応~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/ECC/tree/main/skills/database-migrations

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

データベースマイグレーションパターン

本番システム向けの安全で可逆的なデータベーススキーマ変更。

アクティベーションタイミング

  • データベーステーブルの作成または変更
  • カラムやインデックスの追加/削除
  • データマイグレーション(バックフィル、変換)の実行
  • ダウンタイムゼロのスキーマ変更の計画
  • 新規プロジェクトのマイグレーションツール設定

コア原則

  1. すべての変更はマイグレーションである — 本番データベースを手動で変更しない
  2. マイグレーションは本番では前方のみ進行 — ロールバックは新しい前方マイグレーションを使用する
  3. スキーママイグレーションとデータマイグレーションは分離する — DDLとDMLを1つのマイグレーションに混在させない
  4. 本番規模のデータでマイグレーションをテストする — 100行で動作するマイグレーションが1000万行ではロックする可能性がある
  5. 一度デプロイしたマイグレーションは不変 — 本番で実行されたマイグレーションを編集しない

マイグレーション安全チェックリスト

マイグレーションを適用する前に:

  • マイグレーションにUPとDOWNの両方がある(または明示的に不可逆とマークされている)
  • 大規模テーブルに対するフルテーブルロックがない(同時実行操作を使用)
  • 新しいカラムにはデフォルト値があるか、NULL許容である(デフォルトなしでNOT NULLを追加しない)
  • インデックスは同時に作成する(既存テーブルではCREATE TABLEにインライン化しない)
  • データバックフィルはスキーマ変更とは別のマイグレーションである
  • 本番データのコピーに対してテスト済み
  • ロールバック計画が文書化されている

PostgreSQLパターン

カラムを安全に追加する

-- 良い例: NULL許容カラム、ロックなし
ALTER TABLE users ADD COLUMN avatar_url TEXT;

-- 良い例: デフォルト値を持つカラム(Postgres 11+では即時、再書き込みなし)
ALTER TABLE users ADD COLUMN is_active BOOLEAN NOT NULL DEFAULT true;

-- 悪い例: 既存テーブルにデフォルトなしのNOT NULL(フル再書き込みが必要)
ALTER TABLE users ADD COLUMN role TEXT NOT NULL;
-- テーブルをロックし、全行を再書き込みする

ダウンタイムなしでインデックスを追加する

-- 悪い例: 大規模テーブルでは書き込みをブロックする
CREATE INDEX idx_users_email ON users (email);

-- 良い例: ノンブロッキング、同時書き込みを許可する
CREATE INDEX CONCURRENTLY idx_users_email ON users (email);

-- 注意: CONCURRENTLYはトランザクションブロック内では実行できません
-- ほとんどのマイグレーションツールでは特別な処理が必要です

カラム名の変更(ダウンタイムゼロ)

本番では直接リネームしないでください。Expand-Contractパターンを使用します:

-- ステップ1: 新しいカラムを追加(マイグレーション001)
ALTER TABLE users ADD COLUMN display_name TEXT;

-- ステップ2: データをバックフィル(マイグレーション002、データマイグレーション)
UPDATE users SET display_name = username WHERE display_name IS NULL;

-- ステップ3: アプリケーションコードを更新して両方のカラムを読み書きする
-- アプリケーション変更をデプロイ

-- ステップ4: 古いカラムへの書き込みを停止し、削除する(マイグレーション003)
ALTER TABLE users DROP COLUMN username;

カラムを安全に削除する

-- ステップ1: カラムへのすべてのアプリケーション参照を削除
-- ステップ2: カラム参照なしでアプリケーションをデプロイ
-- ステップ3: 次のマイグレーションでカラムを削除
ALTER TABLE orders DROP COLUMN legacy_status;

-- Djangoの場合: SeparateDatabaseAndStateを使用してモデルから削除し、
-- DROP COLUMNを生成せず(次のマイグレーションで削除)

大規模データマイグレーション

-- 悪い例: 1つのトランザクションで全行を更新(テーブルをロック)
UPDATE users SET normalized_email = LOWER(email);

-- 良い例: バッチ更新と進捗表示
DO $$
DECLARE
  batch_size INT := 10000;
  rows_updated INT;
BEGIN
  LOOP
    UPDATE users
    SET normalized_email = LOWER(email)
    WHERE id IN (
      SELECT id FROM users
      WHERE normalized_email IS NULL
      LIMIT batch_size
      FOR UPDATE SKIP LOCKED
    );
    GET DIAGNOSTICS rows_updated = ROW_COUNT;
    RAISE NOTICE 'Updated % rows', rows_updated;
    EXIT WHEN rows_updated = 0;
    COMMIT;
  END LOOP;
END $$;

Prisma(TypeScript/Node.js)

ワークフロー

# スキーマ変更からマイグレーションを作成
npx prisma migrate dev --name add_user_avatar

# 保留中のマイグレーションを本番に適用
npx prisma migrate deploy

# データベースをリセット(開発のみ)
npx prisma migrate reset

# スキーマ変更後にクライアントを生成
npx prisma generate

スキーマ例

model User {
  id        String   @id @default(cuid())
  email     String   @unique
  name      String?
  avatarUrl String?  @map("avatar_url")
  createdAt DateTime @default(now()) @map("created_at")
  updatedAt DateTime @updatedAt @map("updated_at")
  orders    Order[]

  @@map("users")
  @@index([email])
}

カスタムSQLマイグレーション

Prismaでは表現できない操作(同時インデックス、データバックフィルなど)の場合:

# 空のマイグレーションを作成し、手動でSQLを編集
npx prisma migrate dev --create-only --name add_email_index
-- migrations/20240115_add_email_index/migration.sql
-- PrismaはCONCURRENTLYを生成できないため、手動で記述
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_users_email ON users (email);

Drizzle(TypeScript/Node.js)

ワークフロー

# スキーマ変更からマイグレーションを生成
npx drizzle-kit generate

# マイグレーションを適用
npx drizzle-kit migrate

# スキーマを直接プッシュ(開発のみ、マイグレーションファイルなし)
npx drizzle-kit push

スキーマ例

import { pgTable, text, timestamp, uuid, boolean } from "drizzle-orm/pg-core";

export const users = pgTable("users", {
  id: uuid("id").primaryKey().defaultRandom(),
  email: text("email").notNull().unique(),
  name: text("name"),
  isActive: boolean("is_active").notNull().default(true),
  createdAt: timestamp("created_at").notNull().defaultNow(),
  updatedAt: timestamp("updated_at").notNull().defaultNow(),
});

Kysely(TypeScript/Node.js)

ワークフロー(kysely-ctl)

# 設定ファイルを初期化 (kysely.config.ts)
kysely init

# 新しいマイグレーションファイルを作成
kysely migrate make add_user_avatar

# すべての保留中マイグレーションを適用
kysely migrate latest

# 最後のマイグレーションをロールバック
kysely migrate down

# マイグレーション状態を表示
kysely migrate list

マイグレーションファイル

// migrations/2024_01_15_001_create_user_profile.ts
import { type Kysely, sql } from 'kysely'

// 重要: 常にKysely<any>を使用し、型付きDBインターフェースは使わないこと。
// マイグレーションは時間的に固定され、現在のスキーマ型に依存してはなりません。
export async function up(db: Kysely<any>): Promise<void> {
  await db.schema
    .createTable('user_profile')
    .addColumn('id', 'serial', (col) => col.primaryKey())
    .addColumn('email', 'varchar(255)', (col) => col.notNull().unique())
    .addColumn('avatar_url', 'text')
    .addColumn('created_at', 'timestamp', (col) =>
      col.defaultTo(sql`now()`).notNull()
    )
    .execute()

  await db.schema
    .createIndex('idx_user_profile_avatar')
    .on('user_profile')
    .column('avatar_url')
    .execute()
}

export async function down(db: Kysely<any>): Promise<void> {
  await db.schema.dropTable('user_profile').execute()
}

プログラムによるマイグレータ

import { Migrator, FileMigrationProvider } from 'kysely'
import { promises as fs } from 'fs'
import * as path from 'path'
// ESM only — CJS can use __dirname directly
import { fileURLToPath } from 'url'
const migrationFolder = path.join(
  path.dirname(fileURLToPath(import.meta.url)),
  './migrations',
)

// `db` は Kysely<any> データベースインスタンス
const migrator = new Migrator({
  db,
  provider: new FileMigrationProvider({
    fs,
    path,
    migrationFolder,
  }),
  // 警告: 開発環境でのみ有効にしてください。タイムスタンプ順序の
  // 検証を無効にすると、環境間でスキーマにずれが生じる可能性があります。
  // allowUnorderedMigrations: true,
})

const { error, results } = await migrator.migrateToLatest()

results?.forEach((it) => {
  if (it.status === 'Success') {
    console.log(`migration "${it.migrationName}" executed successfully`)
  } else if (it.status === 'Error') {
    console.error(`failed to execute migration "${it.migrationName}"`)
  }
})

if (error) {
  console.error('migration failed', error)
  process.exit(1)
}

Django(Python)

ワークフロー

# モデル変更からマイグレーションを生成
python manage.py makemigrations

# マイグレーションを適用
python manage.py migrate

# マイグレーション状態を表示
python manage.py showmigrations

# カスタムSQL用の空のマイグレーションを生成
python manage.py makemigrations --empty app_name -n description

データマイグレーション

from django.db import migrations

def backfill_display_names(apps, schema_editor):
    User = apps.get_model("accounts", "User")
    batch_size = 5000
    users = User.objects.filter(display_name="")
    while users.exists():
        batch = list(users[:batch_size])
        for user in batch:
            user.display_name = user.username
        User.objects.bulk_update(batch, ["display_name"], batch_size=batch_size)

def reverse_backfill(apps, schema_editor):
    pass  # データマイグレーション、逆操作は不要

class Migration(migrations.Migration):
    dependencies = [("accounts", "0015_add_display_name")]

    operations = [
        migrations.RunPython(backfill_display_names, reverse_backfill),
    ]

SeparateDatabaseAndState

Djangoモデルからカラムを削除するが、データベースからはすぐに削除しない:

class Migration(migrations.Migration):
    operations = [
        migrations.SeparateDatabaseAndState(
            state_operations=[
                migrations.RemoveField(model_name="user", name="legacy_field"),
            ],
            database_operations=[],  # まだデータベースには触れない
        ),
    ]

golang-migrate(Go)

ワークフロー

# マイグレーションペアを作成
migrate create -ext sql -dir migrations -seq add_user_avatar

# すべての保留中マイグレーションを適用
migrate -path migrations -database "$DATABASE_URL" up

# 最後のマイグレーションをロールバック
migrate -path migrations -database "$DATABASE_URL" down 1

# バージョンを強制(ダーティ状態を修正)
migrate -path migrations -database "$DATABASE_URL" force VERSION

マイグレーションファイル

-- migrations/000003_add_user_avatar.up.sql
ALTER TABLE users ADD COLUMN avatar_url TEXT;
CREATE INDEX CONCURRENTLY idx_users_avatar ON users (avatar_url) WHERE avatar_url IS NOT NULL;

-- migrations/000003_add_user_avatar.down.sql
DROP INDEX IF EXISTS idx_users_avatar;
ALTER TABLE users DROP COLUMN IF EXISTS avatar_url;

ダウンタイムゼロマイグレーション戦略

重要な本番変更には、Expand-Contractパターンに従います:

フェーズ1: EXPAND(拡張)
  - 新しいカラム/テーブルを追加(NULL許容またはデフォルト値あり)
  - デプロイ: アプリは新旧両方に書き込む
  - 既存データをバックフィル

フェーズ2: MIGRATE(移行)
  - デプロイ: アプリはNEWから読み取り、両方に書き込む
  - データの一貫性を確認

フェーズ3: CONTRACT(縮小)
  - デプロイ: アプリはNEWのみを使用
  - 別のマイグレーションで古いカラム/テーブルを削除

タイムライン例

1日目: マイグレーションで new_status カラムを追加(NULL許容)
1日目: アプリ v2 をデプロイ — status と new_status の両方に書き込む
2日目: 既存行のバックフィルマイグレーションを実行
3日目: アプリ v3 をデプロイ — new_status からのみ読み取り
7日目: マイグレーションで古い status カラムを削除

アンチパターン

アンチパターン失敗する理由より良いアプローチ
本番での手動SQL監査証跡なし、再現不可常にマイグレーションファイルを使用する
デプロイ済みマイグレーションの編集環境間でずれが生じる代わりに新しいマイグレーションを作成する
デフォルトなしのNOT NULLテーブルをロックし、全行を再書き込みNULL許容で追加、バックフィル、その後制約を追加
大規模テーブルへのインラインインデックスビルド中に書き込みをブロックCREATE INDEX CONCURRENTLY を使用
スキーマとデータを1つのマイグレーションにロールバックが困難、長時間トランザクションマイグレーションを分離する
コード削除前のカラム削除カラム欠落によるアプリケーションエラーまずコードを削除し、次のデプロイでカラムを削除する

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/content-engine

Create platform-native content systems for X, LinkedIn, TikTok, YouTube, newsletters, and repurposed multi-platform campaigns. Use when the user wants social posts, threads, scripts, content calendars, or one source asset adapted cleanly across platforms.

affaan-m/ECC

Multi-platform content distribution across X, LinkedIn, Threads, and Bluesky. Adapts content per platform using content-engine patterns. Never posts identical content cross-platform. Use when the user wants to distribute content across social platforms.

affaan-m/fal-ai-media

Unified media generation via fal.ai MCP — image, video, and audio. Covers text-to-image (Nano Banana), text/image-to-video (Seedance, Kling, Veo 3), text-to-speech (CSM-1B), and video-to-audio (ThinkSound). Use when the user wants to generate images, videos, or audio with AI.

affaan-m/manim-video

日本語翻訳:このファイルは manim-video 用の日本語翻訳が必要です

affaan-m/remotion-video-creation

Remotion のベストプラクティス - React で動画を作成する。3D、アニメーション、音声、字幕、チャート、トランジションなどをカバーするドメイン固有の29のルール。

affaan-m/video-editing

AI-assisted video editing workflows for cutting, structuring, and augmenting real footage. Covers the full pipeline from raw capture through FFmpeg, Remotion, ElevenLabs, fal.ai, and final polish in Descript or CapCut. Use when the user wants to edit video, cut footage, create vlogs, or build video content.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

agent-self-evaluation

Use after completing any non-trivial task. The agent self-rates its output on 5 axes — accuracy, completeness, clarity, actionability, conciseness — with concrete evidence per criterion. Produces a structured 1-5 scorecard with specific improvement suggestions.

agent-sort

Build an evidence-backed ECC install plan for a specific repo by sorting skills, commands, rules, hooks, and extras into DAILY vs LIBRARY buckets using parallel repo-aware review passes. Use when ECC should be trimmed to what a project actually needs instead of loading the full bundle.

ai-first-engineering

Engineering operating model for teams where AI agents generate a large share of implementation output. Use when setting team process, review gates, or ownership rules for a codebase largely written by agents.

ai-regression-testing

Regression testing strategies for AI-assisted development. Sandbox-mode API testing without database dependencies, automated bug-check workflows, and patterns to catch AI blind spots where the same model writes and reviews code. Use when adding regression coverage to AI-assisted code, or when the same model both wrote and reviewed a change.

android-clean-architecture

Clean Architecture patterns for Android and Kotlin Multiplatform projects — module structure, dependency rules, UseCases, Repositories, and data layer patterns. Use when structuring modules, layers, or data flow in an Android or KMP project.

angular-developer

Generates Angular code and provides architectural guidance. Trigger when creating projects, components, or services, or for best practices on reactivity (signals, linkedSignal, resource), forms, dependency injection, routing, SSR, accessibility (ARIA), animations, styling (component styles, Tailwind CSS), testing, or CLI tooling.

関連スキル