Prisma Migrate가 한 번 꼬이기 시작하면 문제는 보통 한 가지가 아닙니다. 로컬에선 정상인데 배포 DB만 실패해 있거나, 브랜치를 바꿨더니 migrations 폴더와 _prisma_migrations 테이블이 어긋나거나, 운영 DB에 급하게 직접 패치를 한 뒤부터 drift가 생기는 식입니다. 이때 가장 위험한 반응은 “일단 새 migration 하나 더 만들면 어떻게든 맞겠지” 하는 식으로 덮는 것입니다.

Prisma 공식 문서를 기준으로 보면, 마이그레이션 문제가 생겼을 때 가장 먼저 해야 할 일은 명확합니다. 복구 명령을 고르기 전에 어디서, 무슨 종류의 꼬임인지를 먼저 구분해야 합니다. Prisma도 개발 환경용 Troubleshooting과 운영 환경용 Patching & hotfixing을 완전히 다르게 설명합니다. 즉 reset으로 끝낼 수 있는 상황과, 절대 reset을 먼저 누르면 안 되는 상황을 같은 문제처럼 다루면 안 됩니다.

이 글은 2026년 4월 8일 기준 Prisma 공식 문서를 바탕으로, 제가 Prisma 마이그레이션 문제를 정리할 때 따르는 안전한 순서를 정리한 글입니다. “이렇게 하면 무조건 고쳐진다”가 아니라, 어떤 명령을 어떤 상황에서 써야 하는지 구분하는 흐름에 가깝습니다. 앞서 정리한 코드 블록 서식 규칙 글처럼, 이번 글도 긴 명령어보다 먼저 의사결정 순서를 분명히 보여주는 데 초점을 두겠습니다.

경험/운영데이터베이스

Prisma 마이그레이션이 꼬였을 때 복구한 실제 순서

Prisma Migrate가 꼬였을 때 무엇부터 확인하고 어떤 순서로 복구해야 하는지 정리합니다. `migrate status`, `_prisma_migrations`, `migrate resolve`, `migrate diff`, `db execute`를 개발 환경과 운영 환경으로 나눠 설명합니다.

핵심 1

먼저 결론: 복구 명령보다 먼저 “개발 환경인지 운영 환경인지”를 구분한다

핵심 2

어떤 꼬임이든 가장 먼저 migrate status부터 본다

핵심 3

꼬임의 종류를 세 가지로 나눠 보면 판단이 쉬워진다

데이터베이스경험/운영prisma migration recovery
Prisma 마이그레이션 문제를 개발 환경과 운영 환경으로 나눠 status 확인, drift 판별, reset 또는 resolve와 diff로 복구하는 흐름

먼저 결론: 복구 명령보다 먼저 “개발 환경인지 운영 환경인지”를 구분한다

Prisma 공식 문서를 읽어보면 이 구분이 계속 반복됩니다.

  • 개발 환경: migrate reset, db pull, migrate dev
  • 운영 환경: migrate status, migrate resolve, migrate diff, db execute

이 차이가 중요한 이유는 아주 단순합니다.

  • 개발 환경은 데이터 초기화가 허용되는 경우가 많습니다.
  • 운영 환경은 이미 데이터와 트래픽이 있고, migration history까지 함께 맞춰야 합니다.

그래서 제가 가장 먼저 하는 질문은 두 개입니다.

  1. 지금 만지는 DB가 개발용인가, 운영용인가
  2. 현재 증상이 history conflict인가, schema drift인가, failed migration인가

이 두 질문을 건너뛰고 바로 명령부터 고르면, 문제를 더 복잡하게 만들 가능성이 높습니다.

1. 어떤 꼬임이든 가장 먼저 migrate status부터 본다

Prisma CLI 문서에서 가장 먼저 추천하는 확인 명령은 prisma migrate status입니다. 이 명령은 로컬의 prisma/migrations/*와 데이터베이스의 _prisma_migrations 테이블을 비교해서 현재 상태를 알려줍니다. 공식 문서도 migrate resolve가 필요한지 파악하려면 먼저 migrate status를 실행하라고 설명합니다.

npx prisma migrate status

이 명령이 좋은 이유는 “지금 문제를 어느 축에서 봐야 하는지”를 빠르게 보여주기 때문입니다.

  • 아직 적용 안 된 migration이 있음
  • 로컬 history와 DB history가 갈라짐
  • 실패한 migration이 존재함
  • 아예 migrations table이 없음

문서에 있는 example output도 마지막 공통 migration, 로컬에만 있는 migration, DB에만 있는 migration을 구분해 보여줍니다. 즉 복구는 보통 여기서 시작하는 편이 맞습니다.

2. 꼬임의 종류를 세 가지로 나눠 보면 판단이 쉬워진다

Prisma 문서를 기준으로 보면 흔한 문제는 크게 세 가지로 정리할 수 있습니다.

2-1. Migration history conflict

이건 로컬 파일 시스템의 migrations 폴더와 DB의 _prisma_migrations 내용이 안 맞는 경우입니다. 개발 문서는 이미 적용된 migration을 나중에 수정했거나, 브랜치를 바꿨는데 이전 브랜치 migration이 로컬에서 사라지는 상황을 대표 원인으로 듭니다.

2-2. Schema drift

이건 데이터베이스가 migration history가 만드는 상태와 달라진 경우입니다. 대표적으로 운영에서 테이블, 인덱스, 컬럼을 직접 수정했는데 migration history가 그 사실을 모르는 경우가 여기에 해당합니다.

2-3. Failed migration

Prisma 문서가 예로 드는 실패 원인은 익숙합니다.

  • 이미 실행 전 migration을 수정해서 syntax error를 냄
  • 데이터가 있는 테이블에 NOT NULL 컬럼을 추가함
  • 마이그레이션 중 DB가 중단됨
  • 실행이 중간에 끊김

문서도 _prisma_migrations 테이블의 logs 컬럼이 에러를 저장한다고 설명합니다. 즉 운영 DB에서 실패 원인을 볼 때는 status만 보지 말고 logs도 같이 보는 편이 좋습니다.

3. 개발 환경이라면 가장 쉬운 해법은 대개 migrate reset이다

Prisma의 개발 환경 Troubleshooting 문서는 이 부분을 아주 분명하게 말합니다. 개발 환경에서 failed migration이나 drift가 났다면, 가장 쉬운 처리 방법은 원인을 고치고 reset하는 것입니다.

npx prisma migrate reset

이 명령은 destructive합니다. 하지만 로컬 개발 DB라면 보통 이게 제일 빠르고 명확합니다. 특히 아래 같은 경우에는 reset이 거의 정석에 가깝습니다.

  • 브랜치를 바꿨더니 history conflict가 남음
  • 실패한 migration을 고쳤음
  • 데이터가 꼭 보존될 필요가 없음
  • 로컬 테스트용 데이터만 있음

Prisma reset 문서도 기본 사용법을 이 명령으로 설명하고 있습니다. 다만 운영 DB에 이 습관을 그대로 가져가면 안 됩니다. 이게 첫 번째 분기점입니다.

4. 개발 환경에서 수동 변경을 “살려야” 한다면 db pull부터 간다

개발 문서에서 제가 특히 중요하게 보는 부분이 여기입니다. 수동으로 DB를 바꿨고, 그 변경을 버리고 싶지 않다면 migrate reset만 누르는 게 아니라 먼저 현재 DB 상태를 schema로 가져와야 합니다. Prisma 문서는 이 경우 아래 순서를 안내합니다.

npx prisma db pull
npx prisma migrate dev --name introspected_change

의미는 단순합니다.

  1. db pull로 DB의 실제 상태를 schema.prisma에 반영
  2. 그 상태를 migration history에 새 migration으로 기록

문서도 이 흐름이 끝나면 database와 migration history가 다시 sync된다고 설명합니다. 즉 수동 변경을 유지하고 싶다면, reset보다 introspection이 먼저입니다.

이 부분은 실무적으로도 중요합니다. “이미 DB에 있는 현실”을 소스 오브 트루스로 잠깐 받아들인 뒤, 그것을 migration history로 다시 편입시키는 과정이라고 이해하면 됩니다.

5. 운영 환경에서는 reset보다 resolvediff를 먼저 생각한다

운영 환경 문서로 가면 분위기가 완전히 달라집니다. 여기서부터는 migrate reset이 아니라 migrate resolve, migrate diff, db execute가 중심이 됩니다. 이유는 분명합니다. 이미 데이터가 있고, 앞으로의 migration history까지 맞춰야 하기 때문입니다.

Prisma의 migrate resolve 문서는 이 명령이 production에서 migration history 문제를 풀기 위한 것이라고 설명합니다. 할 수 있는 일도 명확합니다.

  • failed migration을 applied로 표시
  • failed migration을 rolled back으로 표시
  • baselining
  • hotfix reconciliation

즉 운영 환경에서 꼬였을 때는 “실제 DB 상태를 어떻게 정리할지”와 “Prisma가 그 상태를 어떻게 기록할지”를 같이 다뤄야 합니다.

6. 운영에서 failed migration이 났다면, 먼저 두 방향 중 하나를 고른다

Prisma의 production troubleshooting 문서는 failed migration 이후 복구 방향을 두 가지로 나눕니다.

  1. 뒤로 가기: failed migration을 rolled back으로 기록하고 다시 배포
  2. 앞으로 가기: 수동으로 나머지 작업을 끝내고 applied로 기록

이 구분이 정말 중요합니다. 예를 들어 migration이 절반만 적용됐고, 지금 설계 자체를 되돌리고 싶다면 “뒤로 가기”가 맞습니다. 반대로 데이터 문제만 정리하면 원래 migration을 계속 가져가고 싶다면 “앞으로 가기”가 맞습니다.

뒤로 가는 흐름

문서 예시는 다음 순서를 보여줍니다.

npx prisma migrate resolve --rolled-back "20201127134938_added_bio_index"
npx prisma migrate deploy

하지만 이 사이에 중요한 조건이 하나 더 있습니다. 만약 migration이 partially run 됐다면, 문서도 두 가지 중 하나를 하라고 말합니다.

  • 이미 실행된 단계가 있으면 수동으로 되돌린다
  • 또는 migration을 idempotent하게 수정해 다시 적용 가능하게 만든다

--rolled-back은 마법이 아니라 history 기록 정리입니다. DB 상태 자체는 필요하면 별도로 되돌려야 합니다.

7. 계속 앞으로 갈 거라면 migrate diffdb execute가 핵심이다

production troubleshooting 문서에서 가장 실무적인 부분이 바로 여기입니다. failed migration이 partially applied된 상태에서 남은 변경만 적용하거나, 반대로 원래 상태로 되돌릴 SQL을 계산할 때 Prisma는 migrate diffdb execute를 쓰라고 안내합니다.

예를 들어 “부분 실패한 운영 DB를 현재 schema.prisma 상태까지 앞으로 밀어붙이겠다”면 문서는 아래 흐름을 예시로 듭니다.

npx prisma migrate diff \
  --from-config-datasource \
  --to-schema schema.prisma \
  --config prisma.config.prod.ts \
  --script > forward.sql

npx prisma db execute --config prisma.config.prod.ts --file forward.sql

npx prisma migrate resolve --applied Unique

의미를 풀면 이렇습니다.

  1. 현재 운영 DB 상태와 목표 schema 차이를 SQL로 만든다
  2. 그 SQL을 운영 DB에 직접 실행한다
  3. 그러고 나서 failed migration을 applied로 기록한다

db execute는 DB 상태를 맞추고, resolve --applied는 migration history를 맞춥니다. 둘은 역할이 다릅니다.

문서에는 반대 방향, 즉 “부분 실패한 운영 DB를 마지막 성공 migration 상태로 되돌리는 backward.sql” 예시도 같이 나옵니다. 이때는 --to-migrations ./prisma/migrations 쪽으로 diff를 만든 뒤 resolve --rolled-back으로 기록합니다.

8. Prisma v7에서는 production config 파일을 따로 두는 편이 안전하다

Prisma production troubleshooting 문서는 v7 note도 함께 적고 있습니다. prisma db execute에서 예전처럼 --url을 쓰는 방식이 없어졌고, production DB에 명령을 보낼 때는 prisma.config.ts 또는 별도 config 파일에서 datasource URL을 설정해 --config로 넘기라고 설명합니다.

그래서 운영용 복구를 자주 다룬다면 이런 식으로 분리해 두는 편이 낫습니다.

prisma.config.prod.ts

import { defineConfig, env } from "prisma/config";

export default defineConfig({
  datasource: {
    url: env("DATABASE_URL_PROD"),
  },
});

그리고 명령도 운영 config를 명시합니다.

npx prisma migrate status --config prisma.config.prod.ts
npx prisma db execute --config prisma.config.prod.ts --file forward.sql

이렇게 해두면 개발용 URL과 운영용 URL을 섞을 위험이 줄어듭니다.

9. 운영에서 직접 hotfix를 했다면 “같은 변경을 migration history로 다시 반영”해야 한다

Prisma의 hotfix 가이드도 자주 놓치는 포인트를 잘 설명합니다. 예를 들어 운영에서 쿼리 성능 때문에 인덱스를 직접 하나 추가했다면, 그 순간 production DB는 이미 schema drift 상태입니다. 그냥 “고쳤으니 끝”이 아니라, 그 변경을 schema와 migrations에도 다시 반영해야 합니다.

문서가 안내하는 흐름은 이렇습니다.

  1. schema.prisma에 같은 변경을 반영
  2. 로컬에서 새 migration 생성
  3. production에는 그 migration을 다시 실행하지 말고 resolve --applied로 표시

예시는 이런 형태입니다.

npx prisma migrate dev --name retroactively-add-index
npx prisma migrate resolve --applied "20210316150542_retroactively_add_index"

이 흐름이 중요한 이유는 아주 단순합니다. 운영에서 이미 들어간 변경을 Prisma가 나중에 “새 작업”으로 오해하지 않게 하기 위해서입니다.

10. 이미 적용된 migration 파일을 가볍게 수정하면 conflict가 더 커진다

개발 문서도 history conflict의 대표 원인으로 “이미 적용된 migration을 나중에 수정한 경우”를 직접 듭니다. 이건 정말 자주 생깁니다. 로컬에서 migration.sql을 조금 고쳐도 아무 일 없는 것처럼 보이지만, 이미 다른 DB에 그 migration이 적용된 순간부터는 그 파일이 사실상 역사 기록이 되어버립니다.

그래서 저는 아래 규칙을 거의 예외 없이 지킵니다.

  • 이미 공유된 migration은 가급적 수정하지 않는다
  • 수정이 필요하면 새 migration으로 덮는다
  • 운영 복구 때문에 수정했다면 source control에도 정확히 반영한다

Prisma production 문서도 partially applied migration을 수정한 경우, 그 상태가 source control에 정확히 반영되도록 하라고 계속 강조합니다. 즉 복구 과정에서 생긴 진실을 repo에도 남겨야 합니다.

11. PgBouncer 환경이면 migration 에러가 SQL 문제만은 아닐 수 있다

Prisma troubleshooting 문서는 PgBouncer 환경에서 migrate 명령 실행 시 prepared statement 관련 에러가 날 수 있다고 따로 경고합니다. 예시 메시지도 나옵니다.

Error querying the database: db error: ERROR: prepared statement "s0" already exists

이 경우 무조건 migration SQL이 잘못됐다고 단정하지 말고, Prisma가 연결된 환경에 connection pooling 계층이 있는지도 같이 보는 편이 좋습니다. 문서도 별도 workaround를 보라고 안내합니다.

즉 복구 순서에서 “왜 실패했는지”를 볼 때는 SQL, 데이터, 연결 환경을 같이 봐야 합니다.

제가 실제로 따르는 복구 순서

여기까지를 한 줄로 정리하면, 저는 보통 아래 순서로 움직입니다.

  1. 지금 DB가 개발용인지 운영용인지 먼저 확정
  2. npx prisma migrate status로 상태 확인
  3. _prisma_migrations.logs 또는 오류 메시지로 실패 원인 확인
  4. 개발 환경이면 reset 가능한지 먼저 판단
  5. 개발 환경인데 수동 변경을 살려야 하면 db pull -> migrate dev
  6. 운영 환경이면 뒤로 갈지, 앞으로 갈지 먼저 결정
  7. hotfix라면 schema와 migration history에 다시 반영
  8. failed migration이라면 diffdb execute로 실제 상태를 맞춘 뒤 resolve
  9. 마지막에 migrate deploy나 후속 migration으로 흐름 재개

핵심은 이겁니다. Prisma 복구는 대개 “하나의 명령”이 아니라 “DB 상태를 맞추는 단계”와 “migration history를 맞추는 단계”가 따로 있습니다.

실무 체크리스트

  • 가장 먼저 prisma migrate status를 실행했는가
  • 지금 DB가 개발용인지 운영용인지 확실히 구분했는가
  • 문제 종류가 history conflict, drift, failed migration 중 무엇인지 파악했는가
  • 개발 환경에서 데이터가 중요하지 않다면 reset이 더 빠른지 검토했는가
  • 수동 변경을 유지해야 한다면 db pull -> migrate dev 흐름을 고려했는가
  • 운영 환경에서 reset을 습관처럼 실행하지 않았는가
  • failed migration이면 뒤로 갈지 앞으로 갈지 먼저 결정했는가
  • db execute는 DB 상태를 맞추고, resolve는 history를 맞춘다는 점을 구분했는가
  • 운영에서 직접 hotfix한 변경을 schema와 migration history에도 반영했는가
  • 이미 적용된 migration 파일을 뒤늦게 조용히 수정하고 있지 않은가

마무리

Prisma 마이그레이션이 꼬였을 때 가장 큰 실수는, 개발 환경 복구 습관을 운영 환경에 그대로 가져가는 것입니다. 로컬에선 migrate reset이 가장 좋은 답일 수 있지만, 운영에선 보통 status, resolve, diff, db execute를 더 신중하게 조합해야 합니다.

복구가 어렵게 느껴지는 이유는 명령이 많아서가 아니라, 데이터베이스의 실제 상태와 Prisma의 history를 동시에 맞춰야 하기 때문입니다. 그래서 먼저 환경을 나누고, 상태를 읽고, 그다음에 DB 상태 복구와 history 정리를 분리해서 생각하면 훨씬 덜 헷갈립니다.

참고 자료: