PlanetScale + Prisma + Next.js

4분

1/21/2023

Thumbnail

들어가면서

현재 블로그 사이트는 마크다운 기반으로 포스트를 작성하고 정적으로 생성하는 방식을 사용합니다. 그리고 동적인 데이터를 따로 관리할 필요가 없어 DB를 사용하지 않았습니다.

하지만 몇 명이 포스트를 보고 있는지 등의 동적인 데이터를 관리하고 싶어졌습니다. 그래서 DB를 사용하기로 결정했고, 이번엔 PlanetScale과 Prisma를 사용해보았습니다.

docker

PlanetScale을 사용하기 전 개발을 위해서 docker를 사용했습니다.

간단하게 사용하기 위해 docker-compose를 통해 가장 기본적인 설정만을 하고 사용했습니다.

PlanetScale 적용 후에도 임시 용도나 테스트를 위해 사용할 예정으로 적용해두었습니다.

# Use root/example as user/password credentials
version: '3.1'

services:
  mysql:
    image: mysql
    container_name: seonest_mysql
    restart: always
    environment:
      MYSQL_DATABASE: seonest
      MYSQL_ROOT_PASSWORD: root
    volumes:
      - ./db_data:/var/lib/mysql
    ports:
      - 3306:3306

PlanetScale

간단히 말하자면 PlanetScale은 MySQL(Percona, MariaDB 포함)를 클라우드에서 관리해주는 서비스입니다.

PlanetScale은 MySQL 데이터베이스 클러스터링 시스템인 Vitess를 사용하여 만들어졌습니다. 덕분에 손쉬운 구성, 스케일링, 백업, 복구 등을 제공합니다.

Vitess는 Go로 작성된 MySQL 데이터베이스 클러스터링 오픈 소스 시스템입니다.
Youtube, Slack, Github 등에서 사용되고 있습니다.
Vitess에 대해 더 자세한 내용을 알고 싶다면 여기를 확인하세요.

PlanetScale을 선택한 이유는 여러가지가 있습니다.

  • MySQL 설치, 관리, 백업, 복구 등의 작업을 하지 않아도 되는 것
  • 간편한 설정과 직관적인 App UI
  • 잘 정리된 문서
  • 무료로 사용할 수 있는 것(정해진 트래픽을 초과하거나 추가적인 기능이 필요하면 업그레이드가 필요합니다.)
  • 기존 사용하는 배포 환경과 잘 맞는 것

기존에 PlanetScale 서비스를 알고는 있었지만 이번에 사용하면서 좋은 서비스라는 것을 느꼈습니다. (다만 플랜이 올라갈 수록 비용이 생각보다 많이 들어가서 따로 구성하는 것이 더 좋을 수도 있습니다.)

PlanetScale은 git 처럼 브랜치 기반으로 동작하며 브랜치별로 데이터베이스를 생성합니다. 처음 프로젝트를 생성하면 main 브랜치가 자동으로 생성됩니다.

브랜치별로 Connection 정보가 달라서 개발 환경과 배포 환경을 분리할 수 있습니다.(배포 환경 뿐만 아니라 다양하게 활용할 수 있습니다.)

만약 main, dev 브랜치가 있다면 main 브랜치에는 실제 서비스에 사용되는 데이터베이스가 있고 dev 브랜치에는 개발 환경에서 사용되는 데이터베이스가 있습니다. dev 스키마 정보를 main 브랜치로 가져오고 싶다면 dev 브랜치에서 main 브랜치로 pull request를 진행하고, 승인을 득한 뒤 merge하면 반영됩니다. (Github 브랜치 머지와 비슷하다고 생각했습니다.)

무료 플랜에서는 2개의 브랜치(production branch, development branch)만 사용할 수 있습니다.
더 자세한 플랜은 여기를 참고해주세요.

주의해야 될 것이 있는데 PlanetScale은 외래키 제약사항을 지원하지 않습니다.

Prisma

최근에 Node.js에서 ORM을 사용할 일이 있으면 대부분 Prisma를 사용했습니다. 그만큼 BigFan입니다. (여러가지 장단점이 있지만 ORM을 사용할 일이 있으면 큰 제약이 없는 한 Prisma을 사용합니다.)

cli 명령어를 통해 client 생성, type generation, migration 등을 쉽게 할 수 있는 장점도 있습니다. (migration은 flyway migration 썼을 때 느낌이 비슷했습니다.)

prisma init 명령어를 통해 prisma를 초기 설정할 수 있습니다.

npx prisma init --datasource-provider mysql

.env 파일에 DB Connection 정보가 예시정보로 설정되고, prisma/schema.prisma 파일에 기본 DB 스키마가 설정됩니다.

연결할 DB Connection Info를 입력하고, Schema를 schema.prisma 파일에 작성하고 migrate 명령어를 통해 DB에 반영하고 prisma client를 생성합니다.

npx prisma migrate dev --name init

더 자세한 내용은 여기를 확인하세요.

PlanetScale + Prisma + Next.js

이번에 이 프로젝트에 사용하기 위해했던 설정들을 간단히 공유하고자 합니다.

planetscale 셋팅

https://app.planetscale.com에 접속하여 계정을 생성하고 프로젝트를 생성합니다.

Github처럼 branch를 가진 프로젝트가 생성되는데 기본적으로 main branch가 생성됩니다.

저는 preview, dev에서 사용할 preview branch를 하나 더 생성합니다.

planetscale branch는 Github과 마찬가지로 pr을 통해 승인을 획득한 뒤 merge가 되는 구조입니다.

생성이 확인되면 connect 버튼을 클릭하여 연결 정보를 가져옵니다.

2개 branch 연결 정보를 가져옵니다. (2개 DB 인스턴스 라고 생각하면 됩니다.)

# main branch 연결 정보
mysql://....

# preview branch 연결 정보
mysql://....

위 2개 정보를 로컬 사용을 위해 .env에 저장해두고 사용합니다. 로컬에서는 preview 브랜치 db를 사용할 것이기 때문에 preview branch 연결 정보를 저장합니다.

앱 배포 후에는 main 브랜치 db를 연결해야 하기 때문에 vercel env setting에서 main branch 연결 정보를 저장합니다.

  • production에는 vercel env setting에서 main branch 연결 정보를 저장합니다.
  • preview, dev에는 vercel env setting에서 preview branch 연결 정보를 저장합니다.

next.js 앱을 배포하기 위해 vercel 사용한다는 것을 전제로 작성되었습니다. 그 외 배포 방법은 비슷하게 env 설정하면 될 것 같습니다.

schema migrate

PlanetScale은 외래키 제약조건을 지원하지 않고 Prisma는 기본적으로 관계 표현 시 외래키를 사용하기 때문에 PlanetScale과 Prisma를 함께 사용할 때 'relationMode' 속성에 대해 설정을 해야 합니다. 또한 외래키 제약조건을 지원하지 않기 때문에 인덱스도 같이 추가해줍니다.

datasource db {
  provider     = "mysql"
  url          = env("DATABASE_URL")
  relationMode = "prisma"
}

model PostView {
  id        Int      @id @default(autoincrement())
  slug      String
  viewer    User     @relation(fields: [viewerId], references: [id])
  viewerId  Int
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  @@index([slug])
  @@index([viewerId])
}

preview에 prisma를 통해 npx prisma db push를 하면 preview branch에 table schema가 적용됩니다. 적용이 잘 되었다는 로그가 찍히고 결과를 확인하기 위해 prisma studio를 실행합니다.

npx prisma studio

https://github.com/prisma/prisma/issues/7292
위 issue를 확인해보면 planetscale에서 migrate 기능을 사용에 제한이 있다는 것과 해결할 수 있는 방법이 있습니다.
그러나 planetscale에서는 free tier에서는 최대 2개 브랜치만 제공하는데, 위 migrate 기능을 위해 shadow database를 사용하기 때문에 free tier에서는 브랜치를 더 생성할 수 없습니다.
그래서 migrate 기능을 사용하지 않고 prisma db push를 사용하였습니다.

schema deploy

CD 프로세스에 planetscale preview branch 스키마를 main branch로 merge하는 프로세스를 추가할 방법을 알지 못해 수동으로 진행하였습니다. 이 경우 스키마가 크게 변경되는 경우 downtime이 발생할 수 밖에 없는 구조입니다.

Vercel 배포를 할 시점에 main branch로 pr을 통해 merge를 합니다.

Next.js에서 사용

prisma client를 가져와서 사용합니다.

import { db } from '@/lib/prisma';

import type * as PostTypes from './types';

export function getViews({ slug }: PostTypes.GetViewsParams) {
  return db.postView.count({
    where: {
      slug,
    },
  });
}

마무리하며

이번 글에서는 PlanetScale + Prisma를 사용하는 방법을 알아보았습니다.

PlanetScale과 같은 as a service DB를 사용하면 DB에 대한 관리를 하지 않아도 되고, DB 설정에 대해 고민을 하지 않아도 되기 때문에 개발에 집중할 수 있었던 것 같습니다. 기존 처럼 DB 서버를 직접관리하고 스케일링을 하는 비용과 비교해보지 않아 얼마나 비싸거나 저렴한지는 모르겠지만, 개발자 입장에서는 편리하게 사용할 수 있었습니다.

PlanetScale은 MySQL을 사용하기 때문에 MySQL에 대한 지식이 있다면 쉽게 사용할 수 있습니다. 문서도 잘 되어 있고 UI도 직관적이어서 PlanetScale의 기능을 사용하기 위해 추가적으로 공부할 것이 많지 않아서 좋았습니다.

개인적으로 Next.js middleware(edge 환경)에서 prisma client를 사용하는 방법을 사용하고 싶었는데, 이번 기회에는 적용하지 못했습니다. (이슈에도 등록해놔서 곧 사용해 보려고 합니다.) 그리고 PlanetScale 사용할 때 Prisma의 migreate deploy를 사용하지 못했습니다. 제가 알아본 방법은 PlanetScale 브랜치가 하나 더 필요(migration용 Shadow DB)해서 이번에는 적용하진 못했습니다.

제 개인적인 경험은 매우 만족스럽습니다. 버튼 몇 개만 누르면 DB 환경이 구성되고 앱에서는 Connection String만 설정하면 바로 사용이 가능하기 때문에 개발자 입장에서는 편리했습니다. 모니터링 기능도 잘 되어 있어서 바로 확인하기 편했습니다. Prisma와도 잘 연동되어 사용하는데 불편함은 느끼지 못했습니다. (사실 가장 컸던 migration 기능을 빼버려서 다른 부분은 크게 연동할 부분이 없었습니다.)

여유가 된다면 이번에 적용하지 못했던 기능을 적용해보고 싶습니다.

적용한 설정, 코드 등을 확인하려면 제 프로젝트 코드를 참고하시면 됩니다.

마지막 업데이트

1/21/2023


Avatar

JHSeo

배우는 것을 좋아하고 관심이 많은 웹 엔지니어 입니다. 느리더라도 꾸준하게 성장하려고 노력하는 개발자입니다.