Communitygithub.com

데이터베이스 마이그레이션

PostgreSQL, MySQL 및 주요 ORM(Prisma, Drizzle, Kysely, Django, TypeORM, golang-migrate)에서 스키마 변경, 데이터 마이그레이션, 롤백, 무중단 배포를 위한 데이터베이스 마이그레이션 모범 사례입니다. 스키마 또는 데이터 마이그레이션을 작성하거나, 롤백을 계획하거나, 무중단 배포를 목표로 할 때 사용하세요.

데이터베이스 마이그레이션란 무엇인가요?

이 스킬은 프로덕션 시스템에서 안전하고 되돌릴 수 있는 데이터베이스 스키마 변경을 위한 포괄적인 패턴과 모범 사례를 제공합니다. PostgreSQL, Prisma, Drizzle, Kysely, Django, golang-migrate와 같은 주요 데이터베이스 및 ORM을 사용하는 개발자를 위해 설계되었습니다. 핵심 원칙으로는 모든 변경 사항을 마이그레이션으로 취급하고, 프로덕션에서는 마이그레이션을 전진 전용으로 유지하며, 스키마와 데이터 마이그레이션을 분리하고, 프로덕션 규모의 데이터로 마이그레이션을 테스트하며, 한번 배포된 마이그레이션은 불변으로 유지하는 것을 강조합니다. 이 스킬은 각 도구에 대한 구체적인 워크플로우, 안전한 컬럼 추가, 무중단 인덱스 생성, 확장-축소 패턴을 사용한 컬럼 이름 변경, 대규모 데이터 마이그레이션을 위한 배치 업데이트 등의 패턴을 다룹니다. 또한 마이그레이션 안전 체크리스트와 프로덕션 중단을 방지하기 위한 안티 패턴(예: 배포된 마이그레이션 편집, 기본값 없는 NOT NULL 컬럼 추가, 대규모 테이블에서 인라인 인덱스 생성)을 포함합니다. 목표는 개발자가 다운타임이나 데이터 손실 없이 자신 있게 데이터베이스 변경을 수행할 수 있도록 돕는 것입니다.

지원 대상~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. 스키마 마이그레이션과 데이터 마이그레이션은 분리한다 — 하나의 마이그레이션에 DDL과 DML을 절대 혼용하지 마십시오.
  4. 프로덕션 규모의 데이터로 마이그레이션을 테스트하라 — 100개 행에서 잘 작동하는 마이그레이션이 1000만 개 행에서는 락을 걸 수 있습니다.
  5. 마이그레이션은 일단 배포되면 불변이다 — 프로덕션에서 실행된 마이그레이션을 절대 편집하지 마십시오.

마이그레이션 안전 체크리스트

마이그레이션을 적용하기 전에:

  • UP과 DOWN이 모두 존재하거나 (또는 명시적으로 되돌릴 수 없음으로 표시됨)
  • 대규모 테이블에 전체 테이블 락이 발생하지 않음 (동시 작업 사용)
  • 새 컬럼에 기본값이 있거나 nullable임 (기본값 없이 NOT NULL을 추가하지 마십시오)
  • 인덱스가 동시에 생성됨 (기존 테이블에서는 CREATE TABLE과 인라인으로 사용하지 않음)
  • 데이터 백필은 스키마 변경과 별도의 마이그레이션임
  • 프로덕션 데이터의 복사본으로 테스트됨
  • 롤백 계획이 문서화됨

PostgreSQL 패턴

컬럼 안전하게 추가하기

-- 좋음: Nullable 컬럼, 락 없음
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는 트랜잭션 블록 내에서 실행할 수 없습니다.
-- 대부분의 마이그레이션 도구는 이를 위해 특별한 처리가 필요합니다.

컬럼 이름 변경하기 (무중단)

프로덕션에서 직접 이름을 변경하지 마십시오. 확장-축소 패턴을 사용하세요:

-- 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을 생성하지 않고 (다음 마이그레이션에서 삭제)

대규모 데이터 마이그레이션

-- 나쁨: 하나의 트랜잭션에서 모든 행 업데이트 (테이블 락)
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 '업데이트된 행: %', 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(`마이그레이션 "${it.migrationName}"이(가) 성공적으로 실행되었습니다.`)
  } else if (it.status === 'Error') {
    console.error(`마이그레이션 "${it.migrationName}" 실행 실패`)
  }
})

if (error) {
  console.error('마이그레이션 실패', 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;

무중단 마이그레이션 전략

중요한 프로덕션 변경의 경우 확장-축소 패턴을 따르십시오:

1단계: 확장 (EXPAND)
  - 새 컬럼/테이블 추가 (nullable 또는 기본값 포함)
  - 배포: 앱이 기존 컬럼과 새 컬럼 모두에 씁니다.
  - 기존 데이터 백필

2단계: 마이그레이션 (MIGRATE)
  - 배포: 앱이 새 컬럼에서 읽고, 두 컬럼 모두에 씁니다.
  - 데이터 일관성 확인

3단계: 축소 (CONTRACT)
  - 배포: 앱이 새 컬럼만 사용합니다.
  - 별도의 마이그레이션에서 이전 컬럼/테이블 삭제

타임라인 예시

1일차: 마이그레이션이 new_status 컬럼 추가 (nullable)
1일차: 앱 v2 배포 — status와 new_status 모두에 씁니다.
2일차: 기존 행에 대한 백필 마이그레이션 실행
3일차: 앱 v3 배포 — new_status에서만 읽습니다.
7일차: 마이그레이션이 기존 status 컬럼 삭제

안티 패턴

안티 패턴실패 이유더 나은 접근 방식
프로덕션에서 수동 SQL감사 추적 없음, 반복 불가능항상 마이그레이션 파일 사용
배포된 마이그레이션 편집환경 간 드리프트 발생대신 새 마이그레이션 생성
기본값 없는 NOT NULL테이블 락, 모든 행 재작성nullable 추가 후 백필, 그 다음 제약 조건 추가
대규모 테이블에서 인라인 인덱스빌드 중 쓰기 차단CREATE INDEX CONCURRENTLY
하나의 마이그레이션에 스키마 + 데이터롤백 어려움, 긴 트랜잭션별도의 마이그레이션
코드 제거 전 컬럼 삭제누락된 컬럼으로 인한 애플리케이션 오류먼저 코드 제거 후 다음 배포에서 컬럼 삭제

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.

관련 스킬