Prisma 6 vs Drizzle v1: TypeScript ORM 선택 기준
타입 추론·마이그레이션 UX·번들 크기 세 축으로 비교하는 실전 가이드
Prisma와 Drizzle을 나란히 놓고 몇 주 써보면 두 도구가 단순히 API 문법만 다른 게 아니라는 사실이 분명해집니다. Prisma는 prisma generate가 만들어낸 .d.ts 파일을 TypeScript가 참조하는 코드 생성 모델이고, Drizzle은 스키마 파일 자체가 TypeScript이므로 컴파일러가 그 자리에서 타입을 즉시 추론합니다. 이 근본 차이가 마이그레이션, CI/CD, 번들 크기, IDE 응답 속도까지 연쇄적으로 결정합니다.
이 글은 타입 추론 방식, 마이그레이션 UX, 번들 크기 세 축으로 두 ORM을 비교합니다. 어느 쪽이 절대적으로 우월하다는 결론은 없으며, 팀 규모·배포 환경·SQL 친숙도에 따라 답이 달라집니다. 2026년 7월 현재 기준으로, Prisma 6과 7 전환 경로, Drizzle v1(2025년 GA 기준, 성능 관련 상세 수치는 v1 beta 시점 벤더 측정치 사용)이 모두 프로덕션에서 검증된 선택지입니다.
한 가지 유의사항: 아래에서 인용하는 "타입 체크 2.9배 빠름", "콜드 스타트 최대 9배 개선", "타입 인스턴시에이션 40,000→5,000회" 같은 수치는 대부분 Prisma 공식 블로그가 자사 조건에서 측정한 값입니다. 독립 검증 자료가 아니라는 점을 감안하고 참고하시기 바랍니다.
축 1 — 타입 추론 방식
스키마가 타입이 되는 두 가지 경로
Prisma 스키마는 .prisma 파일에 PSL(Prisma Schema Language)로 작성합니다.
// schema.prisma
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[]
createdAt DateTime @default(now())
}
model Post {
id Int @id @default(autoincrement())
title String
author User @relation(fields: [authorId], references: [id])
authorId Int
}prisma generate 명령이 node_modules/.prisma/client 안에 대응하는 .d.ts를 생성하고, TypeScript는 이 사전 생성된 선언 파일을 참조합니다. 대형 스키마에서도 타입 조회가 빠른 배경입니다 — 런타임에 추론을 다시 계산할 필요가 없으니까요.
Drizzle 스키마는 순수 TypeScript로 작성합니다.
// schema.ts
import { pgTable, serial, text, varchar, integer, timestamp } from 'drizzle-orm/pg-core';
export const users = pgTable('users', {
id: serial('id').primaryKey(),
email: varchar('email', { length: 256 }).notNull().unique(),
name: text('name'),
createdAt: timestamp('created_at').defaultNow().notNull(),
});
export const posts = pgTable('posts', {
id: serial('id').primaryKey(),
title: text('title').notNull(),
authorId: integer('author_id').notNull().references(() => users.id),
});
export type User = typeof users.$inferSelect;
export type NewUser = typeof users.$inferInsert;여기서 짚고 넘어갈 두 가지 실무 포인트가 있습니다.
첫째, 각 컬럼 정의의 첫 번째 인자('created_at')가 실제 DB 컬럼명이고, 객체 키(createdAt)가 TypeScript에서 참조하는 이름입니다. Drizzle은 이 매핑을 명시적으로 선언하도록 강제하고, Prisma는 @map("created_at")으로 표기합니다. Drizzle에서 매핑을 빼먹으면 SQL 오류로 바로 드러나므로 초기 실수를 잡기 쉽지만, 반대로 컬럼 하나마다 두 번 쓰는 수고가 있습니다.
둘째, $inferSelect와 $inferInsert가 왜 나뉘어 있는지입니다. SELECT 결과 행 타입에서는 모든 컬럼이 확정되지만, INSERT 입력에서는 DEFAULT, serial, nullable 컬럼들이 선택 가능(optional)이어야 합니다. $inferInsert는 이 규칙을 반영해 필수 컬럼만 required로, 나머지는 optional로 좁혀줍니다. API 요청 바디 검증 스키마와 매핑할 때 이 구분이 실질적인 편리함을 만들어냅니다.
타입 라이프사이클: 언제 타입이 만들어지는가
두 방식이 실무에서 갈리는 지점은 "스키마 수정 후 편집기가 새 타입을 보기까지 얼마나 걸리는가"입니다.
Drizzle은 스키마 저장 즉시 새 타입이 편집기에 반영되지만, Prisma는 prisma generate를 다시 돌려야 반영됩니다. 로컬 개발에서는 사소한 차이지만, CI/CD 파이프라인에서 generate 단계를 누락하면 배포 아티팩트의 타입과 런타임 스키마가 어긋나 오류가 프로덕션까지 넘어옵니다. package.json의 postinstall이나 CI의 빌드 이전 단계에 명시적으로 포함시켜두면 예방됩니다.
타입 체크 성능
Prisma가 코드 생성을 택한 이유는 성능입니다. Prisma 공식 블로그가 측정한 복잡한 쿼리 기준 타입 인스턴시에이션 횟수는 다음과 같습니다.
| ORM | 타입 인스턴시에이션 횟수 | 출처 |
|---|---|---|
| Prisma 6 | 수백 회 수준 | Prisma 공식 블로그 |
| Drizzle v1 이전 | 약 40,000회 | Prisma 공식 블로그 |
| Drizzle v1 beta | 약 5,000회 | Prisma 공식 블로그 |
동일 문서에서 Prisma는 타입 체크 속도가 Drizzle 대비 약 2.9배 빠르다고 밝힙니다. 벤더 측정치이므로 그대로 받아들이기보다는, 자신의 스키마 크기와 IDE 조합에서 tsc --extendedDiagnostics나 tsc --generateTrace로 직접 확인하는 편이 안전합니다. 수백 개 테이블 이상 규모라면 특히 그렇습니다.
부분 선택에서 드러나는 접근 방식의 차이
일부 컬럼만 선택할 때 반환 타입이 어떻게 좁혀지는지 보면 두 접근 방식의 차이가 직관적으로 드러납니다.
Prisma — 내부 제네릭 유틸이 반환 타입을 좁힙니다.
const userEmailOnly = await prisma.user.findMany({
select: { id: true, email: true },
});
// 타입: { id: number; email: string }[]Drizzle — SQL 컬럼 선택이 그대로 타입에 매핑됩니다.
const userEmailOnly = await db
.select({ id: users.id, email: users.email })
.from(users);
// 타입: { id: number; email: string }[]단순 쿼리에서는 두 방식이 비슷해 보이지만, include/select 중첩이 깊어지면 Prisma는 Prisma.UserGetPayload<{ include: { posts: { select: { title: true } } } }>처럼 제네릭 표현이 길어지는 반면, Drizzle은 선택한 객체 리터럴 형태가 곧 반환 타입이 되므로 IDE에서 hover했을 때 더 짧게 표시됩니다.
relations()는 JOIN을 만들지 않는다
Drizzle을 처음 도입한 팀이 가장 자주 오해하는 지점입니다. relations() 헬퍼는 Drizzle 내부의 TypeScript 관계 선언일 뿐, 실제 SQL JOIN을 자동으로 생성하지 않습니다. 관계 데이터를 가져오려면 .leftJoin() 같은 SQL 메서드로 직접 조인을 작성하거나, 별도의 Relational Queries API(db.query.users.findMany({ with: { posts: true } }))를 사용해야 합니다.
이는 실수가 아니라 Drizzle의 설계 결정입니다. ORM이 관계를 자동으로 로드하면서 발생하는 N+1이나 예측 불가능한 JOIN을 방지하려는 의도이고, 대신 개발자에게 SQL 통제권을 넘깁니다. Prisma의 include는 반대 방향 — ORM이 관계 로딩까지 관리해 개발자가 SQL을 몰라도 대부분의 케이스를 처리할 수 있도록 합니다.
축 2 — 마이그레이션 UX
마이그레이션 워크플로는 실무에서 가장 피부에 와닿는 차이 중 하나입니다.
Prisma는 prisma migrate dev 하나로 스키마 변경 감지, SQL 파일 생성, DB 적용까지 자동으로 처리합니다. 팀 협업에서 마이그레이션 이력을 Git으로 관리하기 편하고, Prisma Studio라는 성숙한 GUI가 함께 제공됩니다.
Drizzle은 목적에 따라 두 경로로 나뉩니다. 정식 워크플로에서는 drizzle-kit generate가 마이그레이션 SQL을 생성하고, 팀원이 검토한 뒤 drizzle-kit migrate로 적용합니다. 프로토타이핑 단계에서는 drizzle-kit push로 파일 없이 DB에 직접 반영해 빠르게 실험할 수 있습니다. Drizzle v1부터는 각 마이그레이션을 타임스탬프 폴더로 격리하는 구조가 표준화되어 이력 관리 안정성이 개선됐습니다.
마이그레이션 SQL 비교
Prisma 마이그레이션
prisma migrate dev --name add_avatar_url
prisma migrate deploy-- migrations/20260124120000_add_avatar_url/migration.sql
ALTER TABLE "User" ADD COLUMN "avatarUrl" TEXT;Drizzle 마이그레이션
npx drizzle-kit generate
npx drizzle-kit migrate
# 또는 프로토타이핑에서:
npx drizzle-kit push-- drizzle/migrations/0001_add_avatar_url/migration.sql
ALTER TABLE "users" ADD COLUMN "avatar_url" text;여기서 식별자 인용 스타일이 다른 점을 눈여겨보시기 바랍니다. Prisma는 기본으로 모델명·필드명을 PostgreSQL의 큰따옴표 인용으로 감싸 대소문자 그대로("User", "avatarUrl") 사용합니다. Drizzle은 스키마에서 지정한 실제 컬럼명(users, avatar_url)을 그대로 씁니다. 오타가 아니라 각 도구의 인용·네이밍 정책의 결과입니다. Prisma에서 snake_case DB 컬럼 규약을 쓰려면 @map / @@map을 명시적으로 붙여야 합니다.
Drizzle에서 생성 SQL이 항상 사람이 읽을 수 있는 형태로 노출된다는 점은 DBA 검토가 필요한 조직에서 유리합니다. 다만 CI/CD에 drizzle-kit generate 단계를 명시적으로 포함시키지 않으면 스키마 변경이 배포에서 누락됩니다. Prisma의 migrate dev처럼 개발 중 자동으로 감지·생성해주지 않기 때문입니다.
축 3 — 번들 크기와 엣지 런타임
번들 크기는 서버리스·엣지 환경에서 ORM 선택을 실질적으로 좌우합니다.
| ORM | 번들 크기 | 비고 |
|---|---|---|
| Drizzle ORM v1 | 약 7.4KB (gzip) | 의존성 0 |
| Prisma 7 | 약 1.6MB | Rust 엔진 제거, 순수 TS/WASM |
| Prisma 6 (Rust 포함) | 약 14MB | Rust 쿼리 엔진 포함 |
Prisma 7은 Rust 쿼리 엔진을 완전히 걷어내고 순수 TypeScript/WASM 구현으로 전환하면서 번들 크기를 약 14MB에서 약 1.6MB로 줄였습니다. Prisma 공식 블로그는 서버리스 콜드 스타트가 최대 9배 개선됐다고 밝혔습니다(자사 조건 측정치).
두 ORM 간 배율은 기준에 따라 크게 달라집니다.
- Prisma 6 대비 Drizzle: 약 1,900배 차이
- Prisma 7 대비 Drizzle: 약 200배 차이
기존 Prisma 6 코드베이스라면 우선 Prisma 7 전환으로 번들 크기와 콜드 스타트가 얼마나 회복되는지 확인하는 편이 합리적입니다. Cloudflare Workers의 번들 제한이나 Lambda 콜드 스타트가 SLA에 직결되는 환경에서는 Prisma 7 전환 후에도 Drizzle과 여전히 200배 차이가 있으므로, 두 옵션을 모두 실측해볼 가치가 있습니다.
엣지 런타임 예시: Cloudflare Workers + Neon
// worker.ts
import { drizzle } from 'drizzle-orm/neon-http';
import { neon } from '@neondatabase/serverless';
import { users } from './schema';
interface Env {
DATABASE_URL: string;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
if (!env.DATABASE_URL) {
return new Response('DATABASE_URL 미설정', { status: 500 });
}
const sql = neon(env.DATABASE_URL);
const db = drizzle(sql);
const allUsers = await db.select().from(users);
return Response.json(allUsers);
},
};Cloudflare Workers는 Env 인터페이스로 바인딩을 명시적으로 선언해야 타입이 잡힙니다. 그리고 프로덕션 예시라면 process.env.X! 같은 비-null 단언 대신 위처럼 초기 진입점에서 명시적으로 검증하는 편이 안전합니다. 단언은 타입만 속일 뿐 런타임에서는 검증되지 않습니다.
실전 코드 비교
기본 CRUD와 관계 쿼리
두 ORM으로 같은 요구사항을 어떻게 표현하는지 살펴봅니다.
Prisma 방식
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
const newUser = await prisma.user.create({
data: { email: 'hello@example.com', name: 'Alice' },
});
const userWithPosts = await prisma.user.findUnique({
where: { id: 1 },
include: { posts: true },
});
// 타입: (User & { posts: Post[] }) | null
const emailList = await prisma.user.findMany({
select: { id: true, email: true },
});Drizzle 방식
import { drizzle } from 'drizzle-orm/node-postgres';
import { eq } from 'drizzle-orm';
import { users, posts } from './schema';
const databaseUrl = process.env.DATABASE_URL;
if (!databaseUrl) throw new Error('DATABASE_URL 환경변수가 필요합니다');
const db = drizzle(databaseUrl);
const [newUser] = await db
.insert(users)
.values({ email: 'hello@example.com', name: 'Alice' })
.returning();
const result = await db
.select({ user: users, post: posts })
.from(users)
.leftJoin(posts, eq(posts.authorId, users.id))
.where(eq(users.id, 1));
const emailList = await db
.select({ id: users.id, email: users.email })
.from(users);같은 요구사항이지만 표현이 정반대 방향입니다. Prisma의 include는 관계 로딩을 선언적으로 처리하고, Drizzle의 .leftJoin()은 실제 SQL JOIN을 코드에 그대로 드러냅니다. 어느 쪽이 낫다기보다는, 팀이 어느 추상화 층에서 사고하고 싶은지에 따라 결정됩니다.
프로덕션 참고 사례
- Prisma 진영: Cal.com은 오픈소스 캘린더 서비스 전 영역에 Prisma를 사용하며, GitHub 공개 리포지토리에서
include중심의 관계 조회 패턴을 확인할 수 있습니다. - Drizzle 진영: ECOSIRE 팀은 자사 블로그에서 NestJS 기반 API 서버에 Drizzle을 도입해 스키마 파일 66개·약 100개 테이블 규모를 운영하며 피크 시 약 5,000 QPS를 처리한다고 밝혔습니다(자체 측정치).
두 사례는 규모·도메인·주장 근거가 다르므로 "같은 조건에서의 비교"가 아닙니다. 각각 "대규모 오픈소스에서 Prisma가 작동한다", "중규모 SaaS에서 Drizzle이 작동한다"는 존재 증명 정도로 참고하시면 됩니다.
장단점 정리
비교 요약
| 항목 | Prisma 6/7 | Drizzle v1 |
|---|---|---|
| 타입 생성 방식 | 코드 생성 (prisma generate) |
TypeScript 즉시 추론 |
| 스키마 언어 | PSL(자체 DSL) | 순수 TypeScript |
| 타입 체크 속도 | 빠름(사전 생성) | v1에서 큰 폭 개선 |
| 마이그레이션 자동화 | 높음 (migrate dev) |
낮음(명시적 단계 필요) |
| 마이그레이션 가시성 | 생성 후 확인 가능 | SQL 파일 항상 노출 |
| 번들 크기 | 약 1.6MB (Prisma 7) / 약 14MB (Prisma 6) | 약 7.4KB |
| 엣지 런타임 | Prisma 7 이후 개선 | 네이티브 지원 |
| GUI 도구 | Prisma Studio(성숙) | Drizzle Studio(발전 중) |
| 생태계 성숙도 | 높음 | v1 GA 이후 빠르게 성장 |
| SQL 투명성 | 추상화 수준 높음 | SQL과 1:1 대응 |
상황별 권장 선택
| 상황 | 권장 |
|---|---|
| Cloudflare Workers·Vercel Edge, 번들 제한이 엄격 | Drizzle |
| 서버리스 콜드 스타트가 SLA에 직결 | Drizzle |
| SQL 직접 제어·복잡한 커스텀 쿼리가 다수 | Drizzle |
| 팀 전체 마이그레이션 자동화·거버넌스가 우선 | Prisma |
| 비개발자가 GUI로 데이터를 탐색해야 함 | Prisma (Studio) |
| 기존 Prisma 코드베이스, 리라이팅 비용 우려 | Prisma 6 유지 또는 7 전환 |
| 요구사항이 자주 바뀌는 초기 프로토타입 (스키마·DB 자주 리셋) | Drizzle (push) |
| 초기 단계라도 마이그레이션 이력을 Git에 남겨야 함 | Prisma (migrate dev) |
실무에서 자주 만나는 실수
Prisma 관련
prisma generate를 CI/CD에서 누락하는 사례. 로컬에서 정상이던 코드가 배포 후 타입 불일치로 런타임 오류를 냅니다.package.json의postinstall스크립트나 CI 빌드 단계에 명시적으로 포함시키면 예방됩니다.prisma migrate dev없이 스키마만 수정하고prisma generate만 실행하는 혼동. 클라이언트 타입과 실제 DB 구조가 어긋납니다.- 서버리스에서 기본 Prisma 클라이언트를 그대로 사용하다 연결 풀 고갈이 발생하는 사례. Prisma 7 이후 개선됐지만, 필요에 따라 Prisma Accelerate(유료 관리형 서비스), PgBouncer, RDS Proxy 같은 외부 풀링을 검토합니다.
Drizzle 관련
relations()가 SQL JOIN을 자동 생성하지 않는다는 점을 모른 채 도입하는 사례. 관계 로딩은.leftJoin()으로 명시하거나 Relational Queries API를 사용해야 합니다.drizzle-kit generate를 배포 전에 빠뜨려 마이그레이션 파일이 갱신되지 않는 상황. Prisma처럼 자동 감지가 없으므로 배포 체크리스트에 명시하는 편이 안전합니다.- 대형 스키마에서 v1 이전 Drizzle을 계속 쓸 때 TypeScript 언어 서버가 느려지는 증상. v1로 업그레이드하면 타입 인스턴시에이션 수가 크게 줄어드는 것이 Prisma 공식 측정에서 확인됐고, 실제 편집 환경에서 체감 여부는 자체 검증을 권합니다.
의사결정 흐름
마치며
두 ORM의 선택은 결국 철학의 차이에서 시작됩니다. Prisma는 마이그레이션 자동화와 팀 거버넌스를 우선시하고, Drizzle은 번들 크기·SQL 투명성·즉시 추론을 우선시합니다. 2026년 현재 두 도구 모두 프로덕션에서 충분히 검증됐고, 각자의 강점이 뚜렷합니다.
엣지·서버리스가 배포의 핵심이고 번들 크기가 SLA에 직결된다면 Drizzle이 자연스러운 첫 후보입니다. 팀 전체가 마이그레이션 이력을 공유하고 GUI로 데이터를 탐색해야 하는 환경, 또는 이미 대규모 Prisma 코드베이스를 운영 중인 팀이라면 Prisma를 유지하거나 Prisma 7 전환을 먼저 검토하는 편이 비용 대비 합리적입니다.
지금 결정을 내려야 한다면 다음 순서로 접근해보시길 권합니다.
- 배포 환경 확인 — Cloudflare Workers·Lambda·Vercel Edge처럼 번들 크기와 콜드 스타트가 SLA에 직결되는지 먼저 판단합니다.
- 팀 SQL 친숙도 파악 — SQL JOIN과 인덱스를 직접 다루는 것이 편한지, ORM이 관계를 알아서 처리해주길 원하는지 확인합니다.
- 마이그레이션 거버넌스 요건 점검 — DBA 검토가 필수인지, 자동화된 마이그레이션이 팀 문화에 맞는지 확인합니다.
- 실측 — 어느 쪽이든 자기 스키마 규모로 타입 체크 시간과 번들 크기를 실제로 재본 뒤 최종 결정합니다.
참고 자료
- Prisma 6: Better Performance, More Flexibility & Type-Safe SQL
- Prisma ORM Changelog
- Prisma ORM 6.13.0 — CI/CD Workflows and pgvector
- Why Prisma ORM Checks Types Faster Than Drizzle
- Prisma ORM vs Drizzle 공식 비교 문서
- Drizzle ORM 공식 문서 — Latest Releases
- Drizzle ORM v1.0.0-beta.2 릴리스 노트
- Drizzle vs Prisma ORM in 2026 — MakerKit
- Drizzle vs Prisma in 2026 — Encore.dev
- Drizzle ORM vs Prisma: Which TypeScript ORM Should You Use in 2026? — Bytebase
- Drizzle ORM vs Prisma in 2026: The Honest Comparison — DEV Community
- Drizzle vs Prisma: 2026 Benchmarks — Tech Insider
- Prisma vs Drizzle: What Prisma 7 Changes — TECHSY
- Drizzle vs Prisma in 2026: We Run Drizzle on 66 Production Schemas — ECOSIRE
- Node.js ORMs in 2025: Choosing Between Prisma, Drizzle, TypeORM, and Beyond
- Drizzle ORM v1 vs Prisma 6 vs Kysely 2026 — PkgPulse
- Prisma vs Drizzle: Performance, DX & Migration Paths — DesignRevision