우리 팀에서는 DB 스키마 정의 파일(schema.prisma)을 기반으로 타입을 생성하고 쿼리를 실행해주는 Prisma 를 ORM 으로 사용하고 있다.
개발을 하면서 경험했던 불편한 점과 Prisma 가 제공하는 기본 cli의 한계를 정리하며 느낀 점을 토대로 보조 툴을 개발하여 생산성을 높여보고자 한다.
전제조건
DB 스키마 변경사항이 있는 경우 schema.prisma 를 수정한 다음 로컬 DB에 연결한 상태에서 prisma migrate dev [--create-only]를 커맨드를 사용하여 마이그레이션 파일을 생성한다.
migrate dev 의 동작
공식 문서에 따르면 migrate dev 실행 시 아래의 작업이 수행된다.
- 스키마 드리프트를 감지하기 위해 쉐도우 데이터베이스에서 마이그레이션 히스토리, 즉 _prisma_migration 테이블에서 applied로 마킹된 항목을 로컬의 prisma/migrations 에서 찾아 순서대로 실행함
- 실행 완료 후 쉐도우 데이터베이스와 연결된 데이터베이스의 스키마가 다르면(Drift) 오류가 발생함
- (적용되었다고 마킹된) 마이그레이션 파일을 수정(*)하거나 삭제(**)한 경우, 스키마를 수동으로 변경(***)한 경우 발생 가능함
- 마이그레이션 파일이 비정상적이라면 실행 중에 오류가 발생할 수도 있음(****)
- 실행 완료 후 쉐도우 데이터베이스와 연결된 데이터베이스의 스키마가 다르면(Drift) 오류가 발생함
- 보류 중인 마이그레이션을 쉐도우 데이터베이스에 적용
- schema.prisma의 모든 변경 사항으로부터 새로운 마이그레이션을 생성
- 적용되지 않은 모든 마이그레이션을 개발 데이터베이스에 적용하고 _prisma_migrations 테이블을 업데이트함

직면한 문제와 해결 시도
migrate dev를 실행하면 (경고 / 확인 메시지에 yes를 선택할 경우) database reset이 필요하여 로컬 데이터가 모두 삭제된다.
Case 1: 여러 작업을 병렬로 진행해야 함
이 상황은 다른 프로젝트에도 보편적으로 발생 가능한 조건이다. main 브랜치에서 딴 브랜치 A에서 Post 테이블을 추가하는 스키마 변경을 로컬 DB에 적용하여 기능을 테스트 하다가 (상용배포 X), 우선 순위가 높은 다른 작업을 요청받아 동일한 상태의 main 브랜치에서 생성한 B 브랜치에서 스키마 변경을 하면서 migrate dev를 실행하면 쉐도우 DB와 로컬 DB 간 차이, 즉 drift가 감지된다. migrate dev 동작 문단의 (**)에 해당하는 케이스이다.

에러메시지 해석
- 예상 스키마(쉐도우데이터베이스의 스키마)가 실제 스키마가 되기 위해 변화가 필요하다
- [+] Added tables: Post 테이블이 실제 DB에(만) 추가되어 있다
- _prisma_migrations에 applied로 마킹되어있는 마이그레이션이 로컬 prisma/migrations 폴더에 존재하지 않는다
Solution 1: db push (제한이 있음) 또는 migrate diff 를 활용
- db push
위의 문제 상황에서, main브랜치에서 checkout한 직후의 브랜치 B에서 npx prisma db push 를 실행하면 현재 schema.prisma 와 로컬 DB가 동기화되어 A 브랜치에서 적용된 변경사항과 연관 데이터가 제거된다. 그러나 _prisma_migration 테이블의 기록은 그대로기에 오류가 발생한다.

이 오류는 브랜치 A에서 migrate dev를 실행할 때 --create-only를 설정하여 새 마이그레이션 파일을 생성하되 _prisma_migrations 테이블에는 적용하지 않고 db push 를 통해 스키마를 반영하고 테스트 한다면 해결 가능하다. 다만 직접 작성한 DML이 포함된 마이그레이션이라면 db push 를 사용할 수 없다는 예외가 있다.
참고로, db push 에 --force-reset 옵션을 활성화하면 _prisma_migrations 테이블의 모든 로우가 삭제되므로 적절하지 않다.
- migrate diff
두 스키마 파일만을 비교하여 마이그레이션 파일을 생성하는 migrate diff 커맨드도 있다(이외에도 from 과 to 에 다양한 옵션이 가능하다). 이 기능을 사용하면 운영환경에 배포 후 롤백할 때 실행할 Down 마이그레이션 파일도 자동생성 가능하다.
npx prisma migrate diff \
--from-schema-datamodel ./prisma/schema-old.prisma \
--to-schema-datamodel ./prisma/schema.prisma \
--script > migration_name.sql
Case 2: 부적절한 마이그레이션 파일이 기존에 운영 환경에 배포된 상태
이는 우리 프로젝트에서 발생하는 특수한 상황이다. 한 테이블 C 에 데이터를 생성하는 DML이 포함된 마이그레이션 파일이 있는데, 다른 테이블 D 의 컬럼을 외래키로 참조하는 컬럼을 포함하여 생성하므로 테이블 D에 해당 로우가 존재하지 않으면 DB 엔진에서 외래키 제약 오류가 발생한다. 이 때, 쉐도우 DB에는 기본적으로 데이터가 없으므로 오류가 발생한다. 운영환경에서는 데이터가 존재하기에 문제가 발생하지 않았지만 스키마와 무관한, 데이터의 유무에 따라 오류가 발생하는 SQL은 마이그레이션에 포함하기에 부적절하다고 판단하고 있다.

Solution 2: 마이그레이션 파일을 임의로 수정
테이블 D에 데이터를 생성하는 Insert문을 추가하거나, 테이블 C에 데이터를 생성하는 Insert문을 주석처리하는 임시방편이 있다. _prisma_migration 테이블의 로우가 비어있어야 가능하며, 운영환경의 데이터를 덤프했다면 체크섬 불일치로 오류가 발생할 수 있다. 이는 migrate dev동작 문단의 (*) 에 해당하는 케이스이다.
로컬 DB 관리에 대한 고민
문제를 경험하면서, 처음에는 어떻게 하면 스키마 drift를 피할 수 있을지 위주로 고민했다. 그러던 중 다른 팀에서는 로컬 DB를 어떻게 관리하는지가 궁금해졌고, 관련해서 조사하던 중 12 factors app 의 운영-개발 간 동등성 을 읽어보았다 주 내용은 운영환경에서의 예상치 못한 치명적인 버그를 방지하기 위해서는 로컬 개발의 편의성보다 운영환경과 동일한 환경을 선택해야 한다는 것인데, main 브랜치의 마이그레이션 파일과의 차이가 있다면, 이것을 베이스로 하여 로컬 DB를 reset 하는 prisma migrate dev 커맨드의 동작이 이 원칙을 지키기 위한 목적으로 설계된 것일 수도 있겠다는 생각이 들었다.
오픈소스 활동
이러한 고민을 바탕으로 개발 생산성을 높일 수 있는 도구를 만들어보고자 한다.
prisma-migrator (개발 중단)
prisma migrate dev로 자동 실행된 마이그레이션 파일이 아니라 수동으로 생성한 파일은 실행 시 SQL 오류가 발생할 수도 있다. 이 때, 파일에 여러 SQL문이 있다면, 이 오류를 수정하여 다시 마이그레이션을 실행하려면 오류가 발생하기 전까지 정상 실행된 SQL 문을 수동으로 되돌려야 하는 점이 불편하여 롤백 파일이 있으면 마이그레이션이 실패했을 때 롤백 파일이 실행되도록 하는 라이브러리를 만들어 npm 에 배포해보았다. migrate dev는 스크립트로 실행되지 않도록 하는 제한이 있어 deploy를 대체로 사용하는 등 한계가 있고 롤백 파일도 직접 만들어야 하는 등 완성도가 떨어진다고 판단하여, 개발을 중단하고 새 프로젝트를 시작할 예정이다
lazy prisma 기여
lazy prisma는 lazy git, lazy docker 와 유사한 터미널 ui로 prisma의 커맨드를 편리하게 사용하고 마이그레이션 파일을 확인하기 좋은 오픈소스 프로젝트이다. 이 곳에 down 마이그레이션 파일 생성 기능을 요청하고 가능하다면 개발해보려고 한다.
checkout db (가칭) - 개발 브랜치마다 별도의 데이터베이스를 관리
일련의 조사와 고민을 통해 prisma를 사용하면서 로컬(개발) 데이터베이스를 관리하는 이상적인 방법은 작업 브랜치마다 별도의 데이터베이스를 관리하는 것이라는 결론을 내렸다. 이 아이디어를 토대로 git checkout -b {새 브랜치} 를 실행했을때 아래의 작업을 자동화하는 도구를 개발해보려고 한다.
- 데이터베이스를 생성
- 현재 코드를 기반으로 스키마 반영과 시딩
- prisma seed 파일이 있다면 반영
- 아니라면 스냅랫으로 생성
- env 파일의 데이터베이스 연결 파라미터에 생성된 데이터베이스 반영
- 브랜치가 삭제되면 데이터베이스도 삭제
'기술 조사 > 기타' 카테고리의 다른 글
| Elasticsearch 에 인덱싱할 때 CPU 이슈 (2) | 2026.02.24 |
|---|---|
| Nestjs(TypeScript) GraphQL 을 사용할 때 Enum 타입 아규먼트의 기본값 설정 이슈 (2) | 2025.04.12 |
| reflect-metadata 와 Nest.js (4) | 2025.02.15 |
| DataLoader 는 어떻게 GraphQL의 1+N 문제를 해결하는지 (7) | 2024.11.07 |