PlanetScale을 Prisma로 더 잘 사용하기

유원근·2023년 5월 29일
1
post-thumbnail

최근들어 기존 AWS, Azure, GCP와 같은 클라우드에서 제공하고 있는 RDS와 같은 PaaS서비스들 이외에 조금더 SaaS에 가까워진 데이터베이스 서비스들이 많이 등장하고 있습니다.

redis, kafka 클러스터를 지원해주는 Upstash, 위와 같은 기존 클라우드에서 제공하고 있는 Azure Cosmos, AWS Aurora와 같은 서비스등 개발자 입장에서는 개발과 유지보수에 있어서 더 나은 개발자 경험을 제공하고 있는 서비스들이 점차 많아지고 있는데, 이와 같은 데이터베이스 서비스들을 가리켜 DBaaS - Database as a Service 라고 부르고 있습니다.

이번 주제에서 다루게 될 PlanetScale이라는 서비스도 Mysql을 DBaaS화 한 서비스중 하나입니다.

PlanetScale은 개발경험 이외에도 많은 장점이 많은 서비스여서 현재 팀에서도 도입하여 프로덕션으로 사용하게된 서비스입니다.

그렇다면 PlanetScale은 어떤 서비스이고 어떤 장점을 가지고 있을까요?

Planetscale 이란?

플래닛 스케일 공식 사이트

PlanetScale은 MySQL과 호환되는 서버리스 데이터베이스이자, 개발자 경험을 저하시키지 않으면서 확장성, 성능 및 안정성을 제공하는 서비스입니다.

Vitess개발에 참여한 인원들이 만든 PlanetScale은 비교적 최근인 2021년도에 서비스를 런칭했지만, 매우 빠르게 개발생태계에 침투하고 있는 서비스인것 같습니다.

Vitess는 2010년 Youtube에서 너무 거대해진 데이터베이스를 관리하기 위해 개발된 데이터베이스입니다.

CLI, SQL캐싱을 통해 이론상 1000x의 속도를 내준다는 Boost 등 많은 기능을 가지고 있지만 핵심이 되는 장점을 뽑아보자면 다음과 같을 것 같습니다.

Scale to Edge!

먼저 플래닛 스케일이라는 이름의 뜻을 이해해 본다면 이 서비스가 추구하는 목적에 대해 쉽게 이해할 수 있을 것 같습니다.

플래닛 스케일은 글로벌 리전에 대해 읽기전용 디비를 복제하여 지구 행성 어디에서든 가장 가까운 위치에 데이터를 보관하고 빠르게 읽어올 수 있습니다.
그래서 서비스명도 지구행성 전체를 커버한다는 의미에서 PlanetScale이라고 지어진 것 같다는 개인적 추축?! 입니다.

번외로 Azure CosmosDB도 같은 의미를 가지고 있습니다. ( 초기명이 PlanetScale 이었다는 이야기도..? )

읽기 전용 지역 | 플래닛 스케일 Docs

Branch system

플래닛 스케일은 흔히 Git에서 사용하는 브렌치 시스템을 가지고 있습니다.

개발할때에는 dev브랜치 데이터베이스에 개발을 진행을 하고, 이후 콘솔에서 쉽게 main브랜치로 병합을 시킬 수 있기 때문에, 개발자 입장에서는 더 쉽고 안전하게 데이터베이스 마이그레이션을 진행할 수 있게 됩니다.

또한 각 브랜치의 데이터는 별도로 관리되기 때문에 따로 개발전용 데이터베이스를 만들지 않아도 된다는 장점이 있으며, 개발용 브랜치 뿐만 아니라, 프로덕션용 브랜치도 여러개를 만들어 관리할 수 있습니다.

데이터 브랜치 | 플래닛 스케일 Docs

Schema history

브랜치기능과 함께 Git을 통한 버전관리와 유사한 스키마의 버전관리를 자동으로 진행해 줍니다.

이를 기반으로 데이터베이스 스키마 롤백, 브랜치간의 병합요청을 진행할 때에 리뷰와 같은 기능을 이용할 수 있습니다.

각 구성원들의 동의를 얻어 병합을 승인하면 스키마 history에 따라 데이터베이스를 마이그레이션을 자동으로 진행해 줍니다.

논 블로킹 스키마 | 플래닛 스케일 Docs

Development Experience

위 장점들을 이용해 아래와 같은 플로우로 개발을 진행할 수 있습니다.

이러한 기능들도 물론 DX적으로 큰 도움을 줄 수 있는것은 분명하지만, 실제로 가장 크게 경험 향상에 기여하는 부분은 수평적 샤딩, 비차단 스키마 변경과 같은 기능을 매우 쉽게, 혹은 자동으로 이용할 수 있다는 것입니다.

플래닛 스케일이 MySQL의 호환되며 수평적 확장에 중점을 두고 있는 Vitess의 개발자가 만든 서비스인 만큼 플래닛 스케일도 Vitess를 이용해 확장성을 만들어 내고 있습니다.

쉽게 말한다면 MySQL과 같은 RDBMS를 NoSQL처럼 확장 가능하게 만들어주는 데이터베이스입니다.


외래키 제약조건의 아쉬움

이렇게 장점이 많은 플래닛 스케일!

하지만, 이에 따른 단점도 가지고 있습니다.

Vitess를 이용해 수평적 확장을 진행하기 때문에 기본적으로 PlanetScale은 외래키 제약조건을 지원하지 않습니다.

하지만, 납득이 가능한 이유가 있습니다.

MySQL을 단일 서버로 운영하게 되면 데이터가 증가할때 하나의 서버에 데이터를 저장하게 되는데, 이런 경우에는 제약조건을 발동하는데 어려움이 없습니다.
하지만, 데이터베이스를 기능적으로 분할 또는 수평적으로 분할되면 데이터가 하나의 DB가 아닌 여러개의 DB에 위치하기 때문에 제약 조건을 유지하기가 매우매우 어려워지게 됩니다.

또한 스키마 마이그레이션 측면에서 보아도 외래키가 없을때 장점이 더 많을 수 도 있다는 생각이 듭니다.

Vitess가 외래키를 지원하지 않는이유 | Vitess Docs

이로 인해 발생하는 여러가지 문제점들 가운데 가장 빠르게 채감할 수 있는 부분이 onDelete: Cascade와 같은 제약조건 입니다.

만약 유저와 상품간의 장바구니와 같은 기능을 구현해 놓고, 상품을 DB에서 제거해야 하는 상황이 발생한다고 생각해보면,
상품을 장바구니에 추가한 유저가 1000명이라고 가정했을 때에 1000개의 쓰래기데이터가 남게됩니다.

onDelete: CASCADE는 연결된 자식 태이블의 데이터도 삭제하지만,
planetScale은 제약조건을 지원하지 않기때문에 가상의 외래키 포함된 데이터가 그대로 남게 됩니다.


기존의 해결방법

그렇다면 이와 같은 아쉬움을 해결할 방법은 없는 것일까요?

우선 해결방법은 몇가지가 존재할 것 같습니다.

외래키 제약조건 관리법 | 플래넷 스케일 Docs

위 공식문서에서는 3가지 방법들에 대해 설명을 하고 있습니다.

인라인으로 작업 실행

먼저 가장 쉬운 방법입니다.

연결되어 있는 데이터가 있는 행을 삭제할때에 인라인으로 함께 삭제되어야 하는데이터를 아래와 같이 순차적으로 삭제하는 방법입니다.

-- Main action
DELETE FROM recipes WHERE id = 123

-- Secondary actions
DELETE FROM ingredients WHERE recipe_id = 123
DELETE FROM steps WHERE recipe_id = 123

큐를 사용한 비동기 정리

AWS SQS 또는 RabbitMQ와 같은 메시지큐를 활용해 삭제할 데이터를 대기열에 넣어놓고 워커나 람다를 통해 큐에서 하나씩 꺼내 삭제하는 방법도 있습니다.

// 아래와 같은 데이터를 대기열에 넣어두고 순차적으로 삭제
{
  "id": 123,
  "action": "deleted"
}

// 대기열 데이터를 이용한 로직 실행
DELETE FROM ingredients WHERE recipe_id = 123
DELETE FROM steps WHERE recipe_id = 123
DELETE FROM recipes WHERE id = 123

예약된 작업

마지막으로 CRON과 같은 기능을 이용해 예약작업을 걸어놓을 수 있습니다.

문서에서는 다음과 같이 soft delete를 이용해 삭제된 데이터들을 주기적으로 삭제하라고 설명하고 있습니다.

-- Flag the record as requiring deletion.
UPDATE recipes SET is_deleted = true WHERE id = 123

저는 개인적으로 soft delete를 이용하지 않기 때문에 아래와 같은 방법을 이용해 남겨진 데이터들을 삭제해 주고 있습니다.

DELETE FROM child_table LEFT JOIN parent_table ON (child_table.parent_id=parent_table.id) WHERE parent_table.id IS NULL

연결태이블과 같은 자식태이블에서 부모태이블이 null인 데이터들을 찾아내 삭제하는 방법인데, 실행당 100개 정도의 limit을 걸어두고 사용자가 몰리지 않는 시간에 주기적으로 삭제를 하게되는 현재 프로덕트에서도 사용하고 있는 방법입니다.

프리즈마와 함께 사용하기

하지만, 우리 Nodejs개발자들에게는 또 다른 좋은 선택지가 존재하고 있습니다.

바로 Prisma를 이용하는 방법인데요

Prisma는 두가지 RelationMode를 지원하고 있기 때문에 위 해결책들과 같이 두단계의 코드실행 없이, 프리즈마 엔진 내부에서 우리가 필요한 기능을 자체적으로 처리해 줄 수 있습니다.

프리즈마가 지원하는 RelationMode는 아래와 같습니다.

  • forignkey
    일반적으로 RDBMS에서 사용하는 외래키를 이용한 방식.
  • prisma
    플래닛 스케일과 같은 DB에서 외래키 제약조건을 생성하지 않고, 외래키 index를 생성해 가상의 외래키로 사용합니다.

prisma.schema 파일에서 datasource부분에서 설정해 줄 수 있습니다.

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

프리즈마 with Planetscale | Prisma Docs

위 링크에서 설명하고 있는 방법대로 간단하게 @index를 통한 가상 외래키 부분만 설정하고 나면 @relation에 지정한 onDeleteonUpdate와 같은 제약조건들을 Prisma내부 엔진을 통해 아래와 같이 간단하게 추가적인 작업을 위한 코드없이 처리할 수 있게 됩니다.

마이그레이션에서의 장점

추가적으로 Prisma와 함께 PlanetScale을 사용했을때의 강력한 이점이 있습니다.

Prisma는 다른 ORM들과 비교해서 더 나은 마이그레이션 경험이 장점인 ORM인데, 이 부분에서 PlanetScale과 함께 이용하게 된다면, 더 나은 경험을 얻을 수 있습니다.

대표적으로 prisma cli를 이용해 db migration을 워크스페이스의 코드를 통해 관리했던 부분을 db push만을 이용해 PlanetScale 콘솔에서 쉽고 효율적으로 처리할 수 있게 됩니다.

npx prisma db migrate dev //<- 사용하지 말고
npx prisma db push //<- 사용해 주세요

기존의 prisma db migration 을 이용하면 prisma폴더 내에 migration관련 파일과 데이터베이스에 migration데이터를 저장했지만, 이와는 다르게 Planetscale과 함께 prisma를 사용할때에는 prisma db push를 이용해 현재 prisma.schema파일에 명시되어 있는 데이터베이스 스키마를 연결되어 있는 DB에 푸시를 하게 되며 prisma 내부적으로 migration관련 데이터를 관리하지 않을 수 있습니다.

이 과정을 통해 데이터베이스의 스키마가 변경되면, PlanetScale의 콘솔에서 아래와 같이 윤택한 DB 마이그레이션과 병합을 이용할 수 있게 됩니다.


PlanetScale + Prisma = 최선의 조합일까?

요즘 꾸준히 주목받고 있던 Prisma가 최근에 TypeORM을 npmTrends에서 앞지른 것을 보고 사실 다양한 생각이 드는 것 같습니다.
아직 갈길이 먼 ORM이라고 생각되지만 다른 ORM들에 비해 상당히 꾸준하게 상승하고 있는 모습을 Prisma가 보여주고 있는 것 같습니다.

사실 최근 Prisma팀 내부적으로 대대적인 구조조정이 있었다는 이야기를 들어서, 유지보수나 생태계 확장에 있어서 어려움이 있지 않을까? 하는 생각도 있지만, PlanetScale과 유사한 기능을 제공하는 Prisma Accelerate도 베타테스트 중에 있고 열심히 일하는 팀이라는 생각이 드는 것 같습니다. (수익모델만 만들어낸다면..)

Prisma의 가장 큰 단점은 쿼리 부분에서 아직 더 개선되어야 할 부분이 매우 많이 있다고 생각하고 있습니다.

특히 SUM, AVG와 같은 DB 함수나 비교적 고급 SQL문을 ORM을 통해 만들어 낼 수 없다는 부분은 실제 프로덕트를 개발하다 보면 매번 느껴지는 아쉬움이 되었습니다.

다음 글의 주제로는 플래닛 스케일과 Prisma의 효율적인 마이그레이션 시스템을 유지함과 동시에 강력한 쿼리빌더를 사용할 수 있는 방법에 대하여 포스팅 해보도록 하겠습니다

0개의 댓글