PostgresSQL + Prisma ORM 사용법 설정

Yunhye Park·2025년 11월 27일

지식 습득

목록 보기
11/11

프런트엔드 업무만 하다가 처음으로 백엔드 작업을 맡게 되었다. 부트캠프 때 Node에 관해서 조금 배운 이후로는 거의 공부를 이어가지 않았다. 오랜만에 DB 작업을 하려니 뭐가 뭔지 헷갈려서 겸사겸사 정리하고자 한다.

DB 선택

이번 작업은 NCP SENS API를 활용한 카카오톡 알림톡을 발송이다. 메시징 서비스는 별도 팀으로 구분해서 관리할 정도로 장애나 이슈 발생 시 비즈니스에 가해지는 타격이 크리티컬하고(ex. 개인정보 유출, 오발송, 중복 발송 등), 그만큼 심도있는 설계가 필요하다고 들었다.

하지만 그럴 여건이 안 되는 환경이기도 하고, 작은 범위에서 적용하는 것부터 시작하므로 설계 핵심은 잡되 너무 많은 핸들링을 포함하진 않기로 했다.

이 상황을 고려해서 팀장님께서 두 가지 RDBMS 옵션을 제시해 주셨다:

  • PostgreSQL
  • SQLite3

PostgreSQL is a powerful, open source object-relational database system with over 35 years of active development that has earned it a strong reputation for reliability, feature robustness, and performance.

PostgresSQL 공식 문서 가장 처음에 보이듯 어느덧 35년이 넘은 안정적인 시스템이다. 안정성, 기능성, 확장성에서 강점을 보인다. 여러 사용자가 동시에 접근했을 때의 동시성 제어나 복잡한 쿼리 처리 등 기본 옵션으로 제공되는 기능이 다양하다.

SQLite is a C-language library that implements a small, fast, self-contained, high-reliability, full-featured, SQL database engine. SQLite is the most used database engine in the world. SQLite is built into all mobile phones and most computers and comes bundled inside countless other applications that people use every day.

서버 없이 파일 하나를 관리하기 때문에 진입장벽이 거의 없다시피 하다. 파일에 직접 접근하므로 속도도 빠르고 말이다. 빠르고 간편하다. 단순성이 주요 특징을 포괄하는 키워드이기 때문에 PostgresSQL에 비해 자체적인 기능 지원이나 많은 사용자의 동시 접근은 부족할 수 있다.

하지만 그러한 단점이 모바일 앱이나 스마트 워치 등 단일 사용자 환경에서는 무력화된다.

대략적인 아키텍처 차이는 알았고, 확신을 위해선 결국 써보는 수밖에 없었다.

SQLite 맛보기

SQLite부터 시작했는데 역시나 간단했다. 다만 서버리스 형식은 처음이라서 이게 맞나를 끝없이 되뇌었던 것 같다.

Mac은 sqlite3가 내장되어있다. 하지만 데이터베이스에서만 관리할 게 아니고, Node+express 환경에서 사용이 필요한지라 추가로 설치했다:

npm install sqlite3

SQLite3 Desktop이 있지만 기존에 사용 중인 Dbeaver를 활용하기로 했다. 서버 연결이 필요 없으니 경로만 설정해주면 된다. 그럼 해당 위치에 .db 파일(데이터와 스키마 저장)이 생성된다.

대략적인 스키마를 만들어서 사용해 본 결과, 쓰면 쓸수록 SQLite는 너무 가볍다는 인상을 받았다. timestamp 타입이 없어서 변환작업이 필요하다는 점이 한 몫했다. 가벼운 만큼 빠르게 시작이 가능하나, 사소한 것도 만들어야 한다는 번거로움이 있다고 느꼈다.

게다가 현재 운영 중인 서비스에 연결할 부가적인 개념도 있어서, 기존 서비스와 같은 RDBMS를 사용하는 게 장기적으로 나을 것 같았다. 그렇게 PostgresSQL로 사용을 결정하고 PrismaORM와 설정을 시작했다.

기본 셋업

1. PostgresSQL DB 및 유저 생성

CREATE DATABASE [dbname];
CREATE USER [username] WITH CREATEDB PASSWORD ['password'];
GRANT ALL PRIVILEGES ON DATABASE mydb TO [username];

About the shadow database를 참조하면, Prisma는 DB 마이그레이션 시 임시로 DB를 생성했다가 삭제한다. 즉 데이터베이스에 접근할 유저는 데이터베이스 생성 권한(CREATEDB)이 필요하다.

cf. GRANT ALL PRIVILEGES
: 데이터베이스 "내"에서 테이블이나 스키마 등을 변경하는 권한

CREATE 권한이 없는 유저로 접근 시 에러가 발생한다:

Error: P3014
Prisma Migrate could not create the shadow database.
Please make sure the database user has permission to create databases.
Read more about the shadow database (and workarounds) at https://pris.ly/d/migrate-shadow
Original error: 
ERROR: permission denied to create database

2. 설치

npm install prisma --save-dev # 설치
npm install @prisma/client

Ref. Prisma 커맨드

           init    Set up Prisma for your app
        generate   Generate artifacts (e.g. Prisma Client)
              db   Manage your database schema and lifecycle
         migrate   Migrate your database
          studio   Browse your data with Prisma Studio
        validate   Validate your Prisma schema
          format   Format your Prisma schema

3. init

npx prisma init

위 명령어 실행 시 자동으로 prisma.config.ts 파일을 만들어준다. 여기엔 primsa가 생성(generated)된 폴더 경로를 내부적으로 되어있는데, 이 파일이 xxx.schema를 오버라이딩하므로 아래처럼 경로 문제가 발생할 수도 있다:

Failed to load config file "/프로젝트 경로" as a TypeScript/JavaScript module.
Error: PrismaConfigEnvError: Missing required environment variable:
DATABASE_URL

스키마 파일(xxx.schema)로 마이그레이션 파일의 경로를 설정할 수 있으므로 오히려 헷갈릴 가능성이 있는 config 파일은 제거해줬다.

다만 환경변수로 DB 연결해주는 건 필수적이다. 앞서 만든 DB를 프리즈마와 연동하기 위해 username, host(로컬 환경은 별도 수정할 없다), password, 데이터베이스명 등을 수정해 경로를 작성한다. 이때 쌍따옴표는 반드시 있어야 한다. 프리즈마의 규칙.

# .env
DATABASE_URL="postgresql://[username]:[password]@[localhost]:[port]/[dbname]"

이제 xxx.schema 파일에 스키마를 모두 작성한다:

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model MessageRequest {
  ...

그리고 이 파일로 마이그레이션을 진행한다.

4. DB migrate

npx prisma migrate dev --name init

첫 마이그레이션이니까 init으로 명명했다. 그럼 지정한 위치에 migrations 폴더가 생성되고, 그 안에 설정한 이름으로 SQL로 작성된 파일이 있을 것이다.

마무리: Prisma로 DB에 접근할 때

Node에서 Prisma Client를 사용할 때 요청 시마다 새로운 PrismaClient 인스턴스를 생성하면 문제가 발생할 수 있다. 특히 Next.js나 서버리스 환경에서는 인스턴스가 과하게 누적되면 DB 커넥션이 폭주할 수 있으니 말이다.

그래서 공식 문서에서 Prisma client 권장하는 방식을 참고하는 게 좋다:

  • 개발환경에서는 globalThis에 Prisma 인스턴스를 보관해 Hot Reload 시에도 중복 생성되지 않도록 한다.
  • 프로덕션에서는 한 번만 생성해서 그대로 사용한다.
// lib/prisma.ts
import { PrismaClient } from '@prisma/client';

const globalForPrisma = globalThis as unknown as { prisma: PrismaClient };

export const prisma =
  globalForPrisma.prisma ||
  new PrismaClient({
    log: ['query', 'info', 'warn', 'error'],
  });

if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;

매번 인스턴스를 생성하지 않고, 싱글톤 패턴처럼 생성된 하나를 끌어다가 import해서 사용하면 된다:

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

const result = await prisma.messageRequest.findMany();

짧은 회고

나온 지 오래된 ORM인 만큼 생태계도 잘 조성되어있고, 특히 PostgresSQL 유저들을 가장 타겟 삼았는지 가이드 페이지까지 제공해준다. Drizzle은 요새 자주 사용하는 ORM이라는데 웹사이트부터 자유분방하고 유쾌한 느낌이 든다. 처음 해보는 작업이라 안정성을 고려해서 PrismaORM을 사용했는데 기회가 되면 다른 것도 써보고 싶다.

이번에 백엔드 작업을 뼈대부터 만들어보면서 설계, 안정성, 구조화에 대해 많은 고민을 해볼 수 있었다. 자세한 이야기는 현재 작업 중인 서비스가 어느 정도 마무리 되면 정리해봐야겠다.

profile
일단 해보는 편

0개의 댓글