TechFeedTechFeed
Backend

Prisma P3009, 실패 마이그레이션, migrate resolve | 배포만 옛 실패 기록에 막히면?

P3009는 _prisma_migrations에 실패로 남은 기록이 있어 migrate deploy가 새 SQL을 적용하지 않을 때 나는 코드입니다. status로 이름을 보고, SQL이 안 들어갔으면 resolve --rolled-back, 손으로 맞춘 뒤라면 --applied입니다. 표 없음이나 칸 없음과는 다른 줄입니다. Prisma, PostgreSQL, 프리뷰, 한국 1인 개발. 2026년 10월 문서.

by

Prisma P3009는 표가 없어서가 아니라, _prisma_migrations에 실패로 남은 기록이 있어 migrate deploy가 새 SQL을 적용하지 않을 때 나는 코드입니다. 실패한 마이그레이션 이름을 확인한 뒤 migrate resolve로 롤백 표시를 하거나, 손으로 맞춘 경우에만 적용 표시를 하세요.




로컬 migrate dev는 지우고 다시 만들어서 초록인데, 버셀 프리뷰나 슈퍼베이스에 migrate deploy만 P3009로 멈추는 장면이 이 줄입니다. 메시지에는 실패한 폴더 이름이 나옵니다.




표 없음 P2021, 칸 없음 P2022와는 순서가 다릅니다. 근거는 Prisma P3009와 migrate resolve 문서에 뒀습니다.


P3009는 실패 기록이 배포를 막는다

프리즈마 마이그레이트는 prisma/migrations 폴더와 데이터베이스의 _prisma_migrations 표를 비교합니다. 표에 실패로 남은 행이 있으면, 그 다음 배포는 새 파일을 적용하지 않고 P3009로 멈춥니다.


공식 문장 그대로면, 대상 데이터베이스에서 실패한 마이그레이션을 찾았고 새 마이그레이션은 적용하지 않겠다는 뜻입니다. 뒤에 폴더 이름이 붙습니다. 그 이름은 20240101120000_add_nickname처럼 타임스탬프와 슬러그입니다. resolve에 넘기는 값도 그 전체 이름입니다.


개발 중 실패한 기록은 migrate dev가 자주 지우고 다시 씁니다. 프리뷰와 운영은 migrate deploy만 쓰므로, 한 번 실패한 행이 남아 있으면 다음 배포마다 같은 P3009가 납니다. generate가 초록인 것과는 별개입니다. 클라이언트 생성은 표의 실패 행을 지우지 않습니다.


이름을 복사하는 곳 | 오류에 적힌 마이그레이션 이름, 또는 prisma migrate status의 실패 목록입니다. 폴더를 새로 만들어 이름을 바꾸면 옛 실패 행은 그대로 남습니다.


프리뷰 데이터베이스의 실패 마이그레이션 기록이 배포를 막는 개념
P3009는 SQL을 새로 쓰기 전에, 이미 실패로 적힌 행을 먼저 처리하라는 신호다

실패 행은 finished_at이 비어 있다

포스트그레스에서 표를 직접 보면 오류 메시지보다 상태가 분명합니다. 적용이 끝난 행은 finished_at이 있고, 실패로 남은 행은 finished_at이 비어 있으며 rolled_back_at도 비어 있습니다. logs 칸에 그때의 데이터베이스 오류가 남습니다.


행 상태칸다음 동작
적용 완료finished_at 있음resolve 대상 아님
실패로 남음finished_at 없음, rolled_back_at 없음logs를 읽고 resolve
롤백으로 표시rolled_back_at 있음고친 뒤 deploy 가능
폴더는 있는데 행이 없음미적용P3009가 아니라 대기 중

포스트그레스에서 프리즈마 마이그레이션은 한 트랜잭션으로 도는 경우가 많아, DDL이 중간에 죽으면 표 변경은 되돌아갑니다. 그런데 _prisma_migrations의 실패 표시는 남을 수 있습니다. 스키마를 보면 닉네임 칸이 없는데 배포만 P3009인 이유가 이것입니다. 칸이 없다는 런타임 P2022와 순서가 다릅니다. P3009를 치우기 전에는 그 다음 SQL이 실행되지 않습니다.


연결은 풀러 6543이 아니라 직접 5432를 쓰세요. 트랜잭션 풀러는 마이그레이션의 준비된 문장과 세션 설정을 중간에 끊는 일이 있습니다. 앱 런타임의 풀러 주소와 배포 잡의 직접 주소를 환경 변수로 나누세요.


실패로 남은 마이그레이션만 조회
SELECT migration_name, started_at, finished_at, rolled_back_at, logs FROM _prisma_migrations WHERE finished_at IS NULL ORDER BY started_at;

로컬은 reset, 프리뷰는 resolve

개발 데이터베이스에 버릴 수 있는 데이터만 있으면 prisma migrate reset이 짧습니다. 이 명령은 데이터를 지우고 마이그레이션을 처음부터 다시 적용합니다. 프리뷰에 가입 테스트 계정만 있어도, 팀이 그 데이터를 쓰면 reset은 과합니다.


프리뷰와 운영은 prisma migrate resolve로 기록만 고칩니다. 문서상 이 명령은 실패한 마이그레이션에만 씁니다. 이미 성공한 이름에 쓰면 오류가 납니다. 적용 표시와 롤백 표시 중 하나를 반드시 골라야 합니다.


로컬에서 migration.sql을 고친 뒤 그 파일을 이미 실패한 프리뷰에 다시 넣으려면, 체크섬이 기록과 어긋납니다. 실패한 배포의 SQL과 저장소의 SQL이 같은지 logs와 파일을 비교하세요. 파일을 조용히 고친 뒤 resolve만 하면, 다음 환경에서는 다른 SQL이 적용됩니다.


상태를 보고 실패 이름을 처리
npx prisma migrate status # SQL이 들어가지 않았을 때 npx prisma migrate resolve --rolled-back 20240101120000_add_nickname npx prisma migrate deploy
마이그레이션 상태 명령으로 실패 기록을 확인하는 작업 화면
reset은 로컬 데이터용이고, 프리뷰의 실패 행은 resolve로 기록을 고친 뒤 deploy한다

applied와 rolled-back을 거꾸로 쓰지 않는다

두 플래그는 기록을 반대로 적습니다. 데이터베이스에 SQL이 실제로 반영됐는지가 기준입니다.


데이터베이스명령그 다음
SQL이 안 들어갔다resolve --rolled-back 이름원인을 고치고 migrate deploy
손으로 SQL을 끝까지 실행했다resolve --applied 이름파일과 결과가 같은지 확인
일부 문장만 남았다남은 객체를 정리한 뒤 rolled-back그대로 applied 하면 다음 배포가 꼬임
로컬이고 데이터를 버려도 된다migrate reset프리뷰에는 쓰지 않음

--applied는 그 폴더의 SQL을 다시 실행하지 않습니다. 이미 적용된 것으로 장부만 적습니다. 칸이 없는데 applied로 표시하면, 프리즈마는 다음부터 그 파일을 건너뛰고 클라이언트는 없는 칸을 읽습니다. 그때 나오는 코드가 P2022입니다.


NOT NULL 칸을 데이터가 있는 표에 기본값 없이 넣는 SQL은 자주 실패합니다. 롤백 표시 뒤에 스키마에 기본값을 넣고 새 마이그레이션을 만드는 편이, 실패한 파일의 SQL을 운영에서 수동으로 이어 붙이는 것보다 장부가 깨끗합니다. 이미 운영에 수동으로 넣었다면, 파일 내용과 결과가 같을 때만 applied입니다.


몽고DB는 이 resolve 흐름을 쓰지 않습니다. 문서도 몽고DB에서는 db push를 쓰라고 적습니다. 포스트그레스와 명령을 섞지 마세요.


표 없음, 연결 실패와 다른 줄

P2021은 쿼리가 찾는 표가 없을 때입니다. 마이그레이션을 한 번도 안 넣은 빈 프리뷰에서 납니다. P3009는 표가 있을 수도 있고, 없을 수도 있지만, 배포 명령이 실패 기록 때문에 멈추는 자리입니다. status에 failed가 있으면 P3009 쪽을 먼저 치우세요.


P2022는 표는 있는데 칸이 없을 때입니다. applied를 잘못 눌러 장부만 맞춘 뒤에 런타임에서 자주 만납니다. P1001은 호스트까지 TCP가 안 닿을 때라, 마이그레이션 표까지 읽기 전에 끝납니다. P3009 메시지가 보였다면 연결은 된 것입니다.


체크섬이 어긋나 배포가 거절되는 경우도 있습니다. 그 문장은 실패 기록이 아니라 로컬 파일과 기록된 합이 다르다는 쪽입니다. 실패 행의 로그가 비어 있고 합이 다르다는 문장만 있으면 기록 명령보다 파일 이력을 보세요. 관련 글은 표가 없을 때, 칸이 없을 때, 데이터베이스에 닿지 않을 때입니다.


한국에서 슈퍼베이스 프리뷰를 버셀에 붙여 쓰는 경우에는 배포 잡의 주소가 앱 런타임 주소와 달라야 합니다. 앱은 풀러로 붙여도, 마이그레이션 잡은 직접 포트로 붙어야 준비된 문장이 중간에 끊기지 않습니다. 끊기면 표 변경은 되돌아가고 실패 기록만 남아, 다음 배포마다 같은 코드가 반복됩니다. 집 맥의 로컬 데이터베이스는 지워도 되면 처음부터 다시 적용하는 명령이 짧습니다. 프리뷰에 시험 가입 데이터가 있으면 그 명령은 쓰지 마세요.


실패 이름을 복사할 때는 폴더의 앞 숫자까지 포함하세요. 슬러그만 넘기면 명령을 찾지 못합니다. 같은 이름이 로컬 폴더와 원격 기록에 모두 있는지도 상태 명령으로 확인하세요. 폴더를 저장소에서 지운 뒤 기록만 남기면, 다음 배포는 없는 파일을 가리키며 또 멈춥니다. 기록 처리와 파일 보존을 한 세트로 보세요.


  • [ ] 오류에 적힌 마이그레이션 전체 이름을 복사했다
  • [ ] migrate status 와 _prisma_migrations.logs 를 읽었다
  • [ ] SQL이 반영됐는지 정보 스키마로 확인했다
  • [ ] 반영이 없으면 --rolled-back, 손으로 맞췄으면 --applied
  • [ ] 프리뷰에 migrate reset 을 쓰지 않았다
  • [ ] deploy 연결이 직접 5432 인지 확인했다

실패 마이그레이션을 롤백 표시한 뒤 배포를 다시 실행하는 흐름
applied는 장부만 맞추므로, 칸이 실제로 있을 때만 쓴다

참고 자료


내부 연계: 표가 없어 P2021이 날 때, 칸이 없어 P2022가 날 때, 데이터베이스에 연결이 안 될 때.


P3009 문장과 resolve 플래그는 2026년 10월 Prisma 공개 문서 기준입니다. 프리즈마 8의 마이그레이션 명령 이름은 다를 수 있으니, 지금 쓰는 CLI의 migrate resolve --help와 맞추세요.


자주 묻는 질문

migrate dev를 프리뷰에서 돌리면 되나요?

프리뷰와 운영에는 deploy를 씁니다. dev는 대화형으로 파일을 만들고 reset을 제안할 수 있습니다. 실패 기록은 status로 확인하고 resolve로 장부를 고친 뒤 deploy를 다시 실행하세요.


폴더를 지우면 P3009가 사라지나요?

저장소 폴더를 지워도 데이터베이스의 실패 행은 남습니다. 다음 배포는 그 이름이 로컬에 없다고 하거나, 같은 실패를 계속 말합니다. 행을 resolve로 처리하세요.


--applied가 안전한 기본값인가요?

아닙니다. applied는 SQL을 실행하지 않고 적용된 것으로만 적습니다. 정보 스키마에 그 칸이나 표가 있을 때만 쓰세요. 없으면 rolled-back 뒤에 deploy가 맞습니다.


generate는 성공했는데 배포만 실패합니다.

generate는 클라이언트 코드를 만들 뿐입니다. 프리뷰의 _prisma_migrations는 건드리지 않습니다. 배포 로그의 P3009와 status 출력이 기준입니다.


풀러 주소로 deploy 해도 되나요?

트랜잭션 모드 풀러는 마이그레이션과 맞지 않는 경우가 많습니다. 배포 잡만 직접 5432를 쓰고, 앱 런타임은 풀러를 쓰도록 주소를 나누세요. 연결 자체가 안 되면 P1001입니다.


실패한 SQL의 NOT NULL은 어떻게 다시 넣나요?

rolled-back으로 기록을 연 다음, 스키마에 기본값을 추가해 새 마이그레이션을 만듭니다. 실패한 파일의 SQL을 운영 콘솔에 반쯤 붙여 넣고 applied로 막지 마세요.


P3009는 실패한 마이그레이션 기록이 migrate deploy를 막는 줄입니다. logs와 정보 스키마로 SQL이 들어갔는지 확인한 뒤, 없으면 --rolled-back, 손으로 끝까지 맞췄으면 --applied를 쓰세요. 표가 통째로 없는 경우는 P2021 글을 보시면 됩니다.


P3009Prismamigrate resolve마이그레이션PostgreSQL프리뷰배포백엔드

함께 보면 좋은 문제 해결

EXPLORE / Backend

이어서 읽어보기

전체 토픽 둘러보기 →