Communitygithub.com

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

PostgreSQL、MySQL、および主要なORM(Prisma、Drizzle、Kysely、Django、TypeORM、golang-migrate)におけるスキーマ変更、データマイグレーション、ロールバック、ダウンタイムゼロデプロイメントのベストプラクティス。スキーマやデータのマイグレーションを記述する際、ロールバックを計画する際、またはダウンタイムゼロのデプロイメントを目指す際に使用します。

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

このデータベースマイグレーションパターンは、プロダクションシステムにおいて安全で可逆的なデータベーススキーマ変更を行うための包括的なガイドです。SQLの生クエリからPrisma、Drizzle、Kysely、Django、golang-migrateといった主要なORMまで、幅広いツールをカバーしています。コアとなる原則は「すべての変更はマイグレーションとして扱う」「マイグレーションは前方のみで進行する」「スキーマとデータのマイグレーションは分離する」など、プロダクション運用の現場で重要なルールを提示します。各ツール固有のワークフローやコード例に加え、ダウンタイムゼロデプロイメントのためのExpand-Contractパターンや、よくあるアンチパターンとその回避策も詳しく解説しています。このスキルは、AIエージェントがデータベーススキーマ変更の安全な手順を理解し、適切なマイグレーションスクリプトを生成するために不可欠です。

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

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

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

ドキュメント

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

プロダクションシステムのための安全で可逆的なデータベーススキーマ変更。

アクティベーション条件

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

コア原則

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

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

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

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

PostgreSQLパターン

カラムの安全な追加

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

-- 良い例:デフォルト値ありのカラム(PostgreSQL 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パターンを使用します:

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

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

-- Step 3: アプリケーションコードを両方のカラムを読み書きするように更新
-- アプリケーション変更をデプロイ

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

カラムの安全な削除

-- Step 1: アプリケーション内のカラムへの参照をすべて削除
-- Step 2: カラム参照なしでアプリケーションをデプロイ
-- Step 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のみ — CJSは__dirnameを直接使用可能
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=[],  # まだDBに触らない
        ),
    ]

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のみを使用
  - 別のマイグレーションで古いカラム/テーブルを削除

タイムライン例

Day 1: 新しいステータスカラムを追加するマイグレーション(NULL許容)
Day 1: アプリv2をデプロイ — statusとnew_statusの両方に書き込む
Day 2: 既存行のバックフィルマイグレーションを実行
Day 3: アプリv3をデプロイ — new_statusからのみ読み取る
Day 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/claude-api

Anthropic Claude API patterns for Python and TypeScript. Covers Messages API, streaming, tool use, vision, extended thinking, batches, prompt caching, and Claude Agent SDK. Use when building applications with the Claude API or Anthropic SDKs.

affaan-m/everything-claude-code

End-to-end marketing campaign planning and execution. Covers audience research, positioning, campaign angle definition, landing page copy, email sequences, social posts, ad copy, short-form video scripts, and content calendars. Use as the orchestration layer for multi-channel product launches. Use when planning or executing a multi-channel product launch, or producing landing page, email, social, or ad copy.

affaan-m/everything-claude-code

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/everything-claude-code-conventions

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/frontend-design

Create distinctive, production-grade frontend interfaces with high design quality. Use when the user asks to build web components, pages, or applications and the visual direction matters as much as the code quality.

affaan-m/gget

gget CLI and Python workflow for quick genomic database queries, sequence lookup, BLAST-style searches, enrichment checks, and reproducible bioinformatics evidence logs.

affaan-m/literature-review

Systematic literature-review workflow for academic, biomedical, technical, and scientific topics, including search planning, source screening, synthesis, citation checks, and evidence logging.

affaan-m/motion-ui

Production-ready UI motion system for React/Next.js. Use when implementing animations, transitions, or motion patterns.

affaan-m/project-guidelines-example

Example project-specific skill template based on a real production application.

affaan-m/pubmed-database

Direct PubMed and NCBI E-utilities search workflows for biomedical literature, MeSH queries, PMID lookup, citation retrieval, and API-backed literature monitoring.

affaan-m/scholar-evaluation

Structured scholarly-work evaluation for papers, proposals, literature reviews, methods sections, evidence quality, citation support, and research-writing feedback.

affaan-m/uspto-database

USPTO patent and trademark data workflow for official record lookup, PatentSearch queries, TSDR checks, assignment data, and reproducible IP research logs.

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.

関連スキル