Prisma 스키마 동기화 중 Schema Drift 발생 시 데이터베이스 리셋 없이 해결하기

jamie-lee 2024. 2. 27. 18:05

개요

내가 일하는 곳은 Prisma + PostgreSQL을 스택으로 한다. 프리즈마 스키마와 데이터베이스 스키마 동기화 과정 중에 발생한 스키마 드리프트(schema drift)를 어떻게 리셋 없이 해결했는지 작성해보고자 글을 쓴다.

발단

어제 오늘 서비스 성능 문제 때문에 데이터베이스 인덱스 구성을 손 보면서 조금 의아한 점이 있었다. 데이터베이스 스키마에는 인덱스가 생성되어 있는데 Prisma 스키마에는 인덱스 표기가 안 되어 있었던 것이다. 한 마디로 데이터베이스 스키마와 Prisma 스키마 간에 일치하지 않는 부분이 있었다.

문제는 이 상태에서 데이터베이스에 새로운 인덱스를 생성하거나 삭제한 뒤 스키마 동기화를 시도할 때다. 프리즈마가 DB와 ORM 스키마 간 차이가 발생했다는 것을 경고하며 계속 진행하기 위해서는 모든 데이터를 리셋하겠다는 아래와 같은 무서운 으름장을 놓는다…

Drift detected: Your database schema is not in sync with your migration history.

The following is a summary of the differences between the expected database schema given your migrations files, and the actual schema of the database.

It should be understood as the set of changes to get from the expected schema to the actual schema.

[*] Changed the `Child` table
  [-] Removed unique index on columns (childPoolId)

? We need to reset the "public" schema at <DATABASE_URL>
Do you want to continue? All data will be lost.

원인

실제 데이터베이스의 내부 스키마와 Prisma의 스키마를 일치시키기 위해 프리즈마는 쉐도우 데이터베이스(Shadow Database)라는 것을 이용한다. Prisma 공식문서에는 아래처럼 쉐도우 데이터베이스의 역할을 설명하는 카툰도 있다.

데이터베이스 스키마에 변경을 가할 때, ORM 스키마를 먼저 변경하고 이를 데이터베이스에 동기화하도록 권장된다. 그렇게 하지 않은 나와 같은 경우에(실제 데이터베이스 스키마에 변경을 가한 경우) 쉐도우 데이터베이스가 이를 감지하고 "스키마 드리프트"가 발생했음을 알린다.

사실 스키마 드리프트가 문제인 경우는 개발용 데이터베이스에 대해 npx prisma migrate dev 명령어를 썼을 때다. 프로덕션 데이터베이스 동기화의 경우 npx prisma migrate deploy 명령어를 사용하도록 권장되므로 문제가 없다.

프로덕션용 명령어는 적용되지 않았거나 변경 된 프리즈마 마이그레이션에 대해서만 검사하고 스키마 드리프트는 눈 감아주는거 같다. (이 경우는 이 충돌을 해결하지 않으면 스키마 동기화가 불가능하며, 공식문서에서 해결하는 방법을 잘 알려주기도 한다. 아무튼 프로덕션 데이터베이스에 npx prisma migrate dev를 사용하지 맙시다!)

해결

일회성의 개발용 DB고 리셋해도 괜찮다면 그래도 되겠지만 나는 그렇게 하고 싶지 않았다.(다시 세팅하는 것도 일이고… 그동안 다른 팀원들 사용에 지장이 가니.)

아무튼 그래서 Prisma 공식 문서를 따라 리셋하지 않고 스키마 드리프트를 해결하는 방법을 찾았다.

다음 링크의 내용을 참고했다. 👉 Prisma Docs : resolve schema drift

1. 해결 방법을 요약하면 아래 도식과 같다.

실제 데이터베이스에 가한 변경 사항이 프리즈마 마이그레이션 히스토리랑 일치하지 않는 것이 문제다.

나처럼 실제 데이터베이스에 변경 사항을 가한 경우는, 데이터베이스 스키마와 마이그레이션 간의 변경 사항 차이를 SQL 스크립트로 생성하고 해당 스크립트를 실행하여 데이터베이스에 가한 변경 사항을 revert 시킨다.

2. 실제 데이터베이스 스키마와 프리즈마 스키마 간의 변경 사항 차이를 SQL 스크립트로 생성한다.

prisma migrate diff 명령어를 이용한다. 해당 명령어에 관한 공식 문서 링크는 여기에 있다. 👉 Prisma CLI | Prisma Docs

이 명령어의 핵심은 두 개의 데이터베이스 스키마 소스를 비교하여 첫 번째 소스의 스키마를 두 번째 소스의 스키마 상태로 바꾸기 위해 필요한 마이그레이션을 산출한다는 데에 있다.

비교하는 명령어이니 두 개의 데이터베이스 스키마 소스를 정의하는 옵션이 필수적으로 들어가야 한다. 각각의 소스를 정의하는 옵션은 --from-..., --to-...이다.

소스 스키마를 정의하는 방법도 데이터베이스 URL, 마이그레이션 파일, Prisma 스키마 파일 등 몇 가지가 있다. 구체적으로 --from-url, --from-schema-datamodel 같은 것들이 있다. --to- 옵션도 비슷하다. 이는 위에 링크한 공식 문서의 Options 부분에서 잘 나와 있다.

현재 프리즈마 스키마 파일(“schema.prisma”)이 참조하는 데이터베이스 소스의 상태를 프리즈마 스키마 상태로 바꾸길 원한다면 아래 명령어처럼 입력할 수 있다.

npx prisma migrate diff \
	--from-schema-datasource schema.prisma \
	--to-schema-datamodel schema.prisma \
	--script > rollback.sql

--script 옵션 값을 사용하면 상태를 일치시키기 위해 필요한 SQL 스크립트 파일(이 경우 “rollback.sql” )을 결과물로 생성해준다.

3. SQL 스크립트를 실행한다.

생성된 SQL 스크립트를 열어보면 본인이 데이터베이스에 가한 변경 사항을 revert하는 SQL 명령어들이 있을 것이다. 

이를 prisma db execute 명령어를 통해 실행한다. (공식 문서 👉 Prisma CLI | Prisma Docs)이 명령어는 SQL 스크립트를 Prisma 마이그레이션 테이블을 거치지 않고 바로 데이터베이스에 적용시킨다.

사용법은 간단하다. 적용시킬 SQL 파일의 위치와 데이터베이스의 소스를 정의하고 있는 프리즈마 스키마 파일의 경로를 입력하면 된다.

npx prisma db execute \
	--file ./rollback.sql \
	--schema schema.prisma

“Script executed successfully.” 이렇게 문구가 뜨면 성공이다.

다시 npx prisma migrate dev 명령어를 실행해보면 더 이상 스키마 드리프트가 발생했으니 리셋하라는 메시지가 뜨지 않는다는 걸 확인할 수 있다.