프로덕션에서의 Prisma 마이그레이션: 무중단 전략 및 롤백 패턴

발행: (2026년 4월 7일 PM 05:53 GMT+9)
5 분 소요
원문: Dev.to

Source: Dev.to

소개

프로덕션 환경에서 계획 없이 prisma migrate deploy를 실행하면 장애가 발생하는 일반적인 원인입니다.
대부분의 마이그레이션 가이드는 순조로운 경우만 보여주지만, 프로덕션 환경에는 추가적인 제약이 있습니다:

  • 마이그레이션 기간 동안 실시간 트래픽
  • 새로운 배포가 완료될 때까지 구버전 앱이 계속 실행됨
  • 롤백이 필요한 잠재적인 마이그레이션 실패
  • 테이블을 잠그는 장시간 실행 마이그레이션

가장 안전한 접근 방식은 확장 후 축소입니다.

무중단 마이그레이션 전략

단계 1 – 확장 (역호환 변경)

새 컬럼을 nullable(NULL 허용)로 추가하여 기존 코드가 계속 작동하도록 합니다.

-- Add new column as nullable
ALTER TABLE users ADD COLUMN display_name TEXT;

단계 2 – 백필 (백그라운드 작업)

긴 잠금을 방지하기 위해 새 컬럼을 배치 단위로 채웁니다.

// scripts/backfill-display-name.ts
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();

const batchSize = 1000;
let cursor: string | undefined;

while (true) {
  const users = await prisma.user.findMany({
    take: batchSize,
    skip: cursor ? 1 : 0,
    cursor: cursor ? { id: cursor } : undefined,
    where: { displayName: null },
  });

  if (users.length === 0) break;

  await prisma.user.updateMany({
    where: { id: { in: users.map(u => u.id) } },
    data: { displayName: users.map(u => u.name) },
  });

  cursor = users[users.length - 1].id;
  await new Promise(r => setTimeout(r, 100)); // don't hammer the DB
}

단계 3 – 계약 (새 코드를 완전히 배포한 후)

컬럼을 필수로 만들고 필요에 따라 기존 컬럼을 삭제합니다.

-- Make the new column NOT NULL
ALTER TABLE users ALTER COLUMN display_name SET NOT NULL;

-- Old column can be dropped in a later deploy
ALTER TABLE users DROP COLUMN name;

개발 워크플로우

# Create a migration from schema changes
npx prisma migrate dev --name add_display_name

# Preview what will run in production
npx prisma migrate status

프로덕션 워크플로우

# Apply pending migrations
npx prisma migrate deploy

실행하지 않고 마이그레이션 파일 생성

npx prisma migrate dev --name backfill_slugs --create-only

필요에 따라 생성된 migration.sql을 편집하세요 (아래 예시를 참고).

예시: 슬러그 백필링

-- migrations/20240101_backfill_slugs/migration.sql
ALTER TABLE posts ADD COLUMN slug TEXT;

-- Backfill in batches to avoid lock contention
UPDATE posts
SET slug = lower(regexp_replace(title, '[^a-zA-Z0-9]+', '-', 'g'))
WHERE slug IS NULL;

ALTER TABLE posts ALTER COLUMN slug SET NOT NULL;
CREATE UNIQUE INDEX posts_slug_idx ON posts(slug);

잠금 없이 인덱스 생성

-- This locks the table (bad in production)
CREATE INDEX posts_author_idx ON posts(author_id);

-- This doesn't lock (safe in production)
CREATE INDEX CONCURRENTLY posts_author_idx ON posts(author_id);

마이그레이션 SQL에 CONCURRENTLY 절을 직접 추가할 수 있습니다.

배포 스크립트

#!/bin/bash
# deploy.sh

# 1. Check migration status
echo 'Checking migration status...'
npx prisma migrate status

# 2. Backup before destructive migrations
echo 'Creating backup...'
pg_dump $DATABASE_URL > backup_$(date +%Y%m%d_%H%M%S).sql

# 3. Run migrations
echo 'Running migrations...'
npx prisma migrate deploy

# 4. Deploy app
echo 'Deploying app...'
# your deploy command here

# 5. Verify migration outcome
npx prisma migrate status

# 6. Resolve a failed migration after manual fix
# npx prisma migrate resolve --applied 20240101000000_migration_name
# Or mark as rolled back
# npx prisma migrate resolve --rolled-back 20240101000000_migration_name

CI/CD 통합

# .github/workflows/deploy.yml
- name: Run database migrations
  env:
    DATABASE_URL: ${{ secrets.DATABASE_URL }}
  run: |
    npx prisma migrate deploy
    # Verify schema is in sync
    npx prisma db pull --print | diff - prisma/schema.prisma

시드 데이터

// prisma/seed.ts
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();

async function main() {
  await prisma.user.upsert({
    where: { email: 'test@example.com' },
    update: {},
    create: {
      email: 'test@example.com',
      name: 'Test User',
    },
  });
}

main()
  .catch(console.error)
  .finally(() => prisma.$disconnect());

Package Configuration

{
  "prisma": {
    "seed": "npx ts-node prisma/seed.ts"
  }
}

AI SaaS 스타터 키트는 Prisma가 구성된 상태로 제공되며, 마이그레이션 워크플로가 설정되어 있고, 개발을 위한 시드 데이터가 준비되어 있습니다. $99 일회성.

0 조회
Back to Blog

관련 글

더 보기 »