Serverless Database Integration

Deep dive into serverless and Prisma ORM to Framework-agnostic package configuration

Serverless Database Integration

What is Serverless?

Serverless is a cloud-native development model that allows developers to build and run applications without having to manage servers. The term “serverless” doesn’t mean there are no servers. - Red Hat

Cloud Native 환경이 발전함에 따라 전통적인 구조를 벗어난 새로운 형태의 서비스 또는 어플리케이션들이 많이 등장하고 있다. 마치 서버에 대한 의존도가 사라지는 것 같은 착각을 주는, serverless 도 그 중 하나이다. 도대체 serverless 는 무엇을 말하는 걸까?

Serverless 는 언급했듯, "No Server" 를 의미하는 것이 아니다. Server-maintenance-less 가 의미적으로는 더 정확하다. 서버가 없어지는 게 아니라, 서버에 대한 유지 관리 작업이 없어지는 셈이다. 엄밀하게는, 서버의 유지 관리 작업을 third party vendor 에게 위탁하는 것을 말한다. 기존의 물리 서버, bare metal server 는 24 시간 가동 및 외부 접근 허용이라는 특성 상 보안에 굉장히 취약할 수 밖에 없다. 간단한 NAS (Network-Attached Storage) 를 구축해보려고 해도 각종 보안 설정들 때문에 주저하게 될 정도. 하지만 serverless 는 이런 부분을 떠넘겨버릴 수 있는 것이다(!).

물론 장점만 있는 것은 아니다. 사실상 서버의 통제 권한을 위임하는 것과 다름 없기 때문에, 서버 측의 디버깅이 어려워질 수 있으며, 서버 측 장애 발생 시 즉각적인 대응이 불가능하다는 점, 그리고 직접 서버를 구성하는 것 만큼의 유연성은 확보하기 어렵고, vendor lock-in 이 발생한다. 특히 서비스 운영에 있어서는 서버 장애 대응이 큰 부분을 차지할 수도 있다.

Serverless Computing

24시간 always-on 상태여야 하는 물리 서버와 달리, serverless 는 필요할 때에만 활성화 된다는 on-demand 특징을 갖는다. 또한 Cloud infrastructure 를 기반으로, 그 특징을 살려 auto scaling 과 같은 기능을 함께 제공하는 경우가 많다. 이렇게 server 관리에 더해 cloud 환경 관리까지 겸하는 것을 serverless computing 이라고 하며, 다음과 같이 분류할 수 있다.

  • Function-as-a-Service (FAAS): 단일 함수를 서비스로서 제공하는 것으로, 가장 극단적인 형태의 microservice 가 아닐까 싶다.
  • Backend-as-a-Service (BAAS): 권한처리, 데이터베이스, 메시징, 스토리지 등 backend 서비스 그 자체를 제공한다.
  • Serverless Databases: 인프라 유지 관리를 필요로 하지 않으며 auto scaling 이 가능한 데이터베이스 서비스
  • Serverless Containers: Cloud 환경에 대한 관리가 필요 없는 컨테이너 서비스
  • Serverless Edge Computing: 컴퓨팅을 사용자 또는 리소스에서 물리적으로 가까운 곳에서 진행하여 네트워크 지연을 낮출 수 있는 서비스
  • Serverless GPU: AI/ML 연산을 위한 GPU 자원을 제공하는 서비스

Serverless computing 은 cloud 설정에 대한 복잡성을 신경쓰지 않고도 cloud native 환경을 이용할 수 있다는 점이 다른 무엇보다 매력적이다. 비용적인 측면에서도, 트래픽 변동이 심한 서비스들에 선택적으로 적용하면 큰 이점을 가져다 줄 수 있다. 하지만 Cloud 인프라 장애에 영향을 받는 또다른 어플리케이션이 될 가능성도 무시할 수 없다.

Serverless Database

이 프로젝트의 블로그 시스템은 처음부터 데이터베이스 사용을 염두에 두고 설계됐다. 전까지 사용하고 있었던 file-based dynamic routing 은 인프라를 확정하기 전까지의 임시방편에 불과했다. 사설 서버나 AWS 클라우드 같은 환경에의 서버를 직접 구축할 생각만 하다가, Next.js Dashboard Tutorial 을 진행하면서 Vercel Integrations 에서 제공하는 Neon 데이터베이스를 접하게 됐고, 이를 바탕으로 블로그 시스템을 개편했다. 여기서는 오랜 기간 결정을 내리지 못하고 있던 인프라 문제를 해결해준 Serverless Database, 그 중에서도 Neon 을 중심으로 알아보고자 한다.

Neon

Neon architecture

Serverless database vendor 중 하나인 Neon 은 compute 와 storage 를 분리하고, Neon Control Plane 을 통해 이들의 cloud 자원을 조율하여 Postgres 데이터베이스를 제공한다. Neon Architecture 를 정리하자면 다음과 같다:

  • On-Demand Compute(Postgres): Stateless Query Processor. SQL 쿼리들을 실행하거나 WAL records 를 생성하고, 자주 접근된 data page 의 캐싱을 진행하는 등, 요청받은 작업을 수행하여 Safekeeper 에게 전달한다.
  • Safekeeper: Durability & Replication Mediator. 트랜잭션의 안정성을 보장하며 Pageserver 의 버퍼 또는 relay point 의 역할을 한다. WAL stream 을 compute 로부터 Pageserver 에게 전달하는 일도 맡고 있다.
  • Pageserver: Managed Storage & Caching. multi-tenant storage 의 핵심으로, WAL stream 을 전달 받아 database page 에 적용하고, 데이터 캐시를 최신 상태로 유지하며, Object Storage 로 전달할 데이터를 관리한다. 각 tenant 들의 data isolation 을 담당함과 동시에 git 과 같은 branching 기능도 제공한다.
  • Object Storage(S3): Infinite & Immutable & Durable Archive. 데이터베이스의 이력 (historical data)과 스냅샷, 레이어 파일 등을 저장하는 저장소이다. Pageserver 는 이 곳을 참조해 복구를 진행하지만, 실시간 transaction 커밋에는 관여하지 않는다.

실제 연산 작업이 일어나는 compute 와 데이터를 저장하는 storage 를 분리해내고, compute 를 stateless 상태로 구조화하여 scale-to-zero, 비활성화를 가능하게 함으로써 serverless 서비스로 거듭날 수 있었다.

Integration

Vercel Integrations 에서는 통합 가능한 여러 서비스들을 소개하고 있으며, 이 프로젝트에서는 Neon 을 선택했다. 인프라 및 서버 운영을 위임한다는 부분이 제일 컸고, serverless database 라는 낯선 서비스 형태를 도입하는 것이기 때문에, 다른 대안을 알아보거나 하지는 않았다. Neon 에 Vercel 계정을 연동하여 체험 서비스까지 연결해주는 일련의 과정이 잘 짜여져 있어 따라하기만 해도 쉽게 사용 가능했다. Connection Configuration 에서 확인할 수 있는 PGHOST, PGUSER, PGPASSWORD 로 해당 데이터베이스를 DBeaver 에 직접 연결할 수도 있다. Neon 이 인프라와 데이터베이스를 제공해주지만, 그럼 query 는 어떻게 처리할까?

Prisma ORM

여기서는 튜토리얼에 함께 제공된 Prisma ORM 을 사용했다. 데이터베이스 데이터와 프로그래밍 언어의 객체 형태 사이를 중개해주는 매개체인 ORM, Object-Relational Mapping 중 하나인 Prisma ORM 은 TypeScript 및 Node.js 생태계를 위한 type-safe ORM 이다. 기본적인 사용법은 다음과 같다.

  1. prisma.config.ts 에 연결할 데이터베이스 connection configuration 을 설정한다. (7.x 기준)
  2. Prisma Schema 를 통해 데이터를 직접 모델링하거나, 설계된 모델링을 데이터베이스로부터 받아온다.
  3. prisma generate 를 실행한다. (여기서 prisma 가 객체를 생성해준다.)
  4. Prisma Client 로 생성된 객체를 사용한다.

Framework-Agnostic Database Package

여기에서는 monorepo 구조를 활용해 데이터베이스 패키지를 분리했다. React 나 Next 같은 프레임워크에 대한 dependency 를 완전히 제거하고 데이터베이스 접근 자체를 분리하여 순수한 query 실행의 역할만 할 수 있도록 @juun/db 패키지를 별도로 생성했다.

schema.prisma
1model post {
2  id          Int           @id @default(autoincrement())
3  title       String        @db.VarChar(255)
4  description String?
5  content     String
6  created_at  DateTime      @default(now()) @db.Timestamptz(6)
7  updated_at  DateTime      @updatedAt @db.Timestamptz(6)
8  category    PostCategory?
9  image       String?       @db.VarChar(500)
10  word_count  Int
11  post_tags   post_tag[]
12
13  @@index([id])
14  @@index([category])
15}
16

블로그 포스트를 위한 데이터 모델은 위와 같이 정의했다. post_tag 는 n:n 관계를 처리하기 위해 junction 테이블을 따로 만들어주었다.

client.ts
1import { PrismaPg } from "@prisma/adapter-pg";
2
3import { PrismaClient } from "./generated/prisma/client";
4
5const adapter = new PrismaPg({
6  connectionString: process.env.DATABASE_URL,
7});
8
9const globalForPrisma = globalThis as unknown as {
10  prisma: PrismaClient | undefined;
11};
12
13export const prisma =
14  globalForPrisma.prisma ??
15  new PrismaClient({
16    adapter,
17    log:
18      process.env.NODE_ENV === "development"
19        ? ["query", "error", "warn"]
20        : ["error"],
21  });
22
23if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;
24
25export default prisma;
26

데이터베이스의 connection pool 이 불필요하게 많아지는 것을 방지하기 위해서 Singleton 형태로 instance 를 제한한 Prisma Client 를 사용했다.

실제 query 는 entity > method > specific 순서를 따르는 namespace 패턴으로 정리했다. 이 프로젝트에는 GET 외의 method 를 추가할 생각은 없지만, 따로 주석이나 설명을 달지 않고도 의미를 보존하기 위한 게으름의 연장 작업이다.

post.ts
1namespace post {
2  export namespace get {
3  // SELECT (GET) methods
4    export async function all() {
5      const posts = await prisma.post.findMany(...);
6    },
7    ...
8  },
9  ...
10}
11

Closing

Serverless Database 라는 새로운 도메인을 도전적으로 도입해봤다. 이 프로젝트에서는 물리 서버의 유지 관리를 위임할 수 있다는 부분이 가장 크게 작용했고, backend framework 없이도 type-safe 한 데이터베이스 접근이 가능해졌다. 다만 서비스의 특징이 장점이자 동시에 단점일 수 있기 때문에, 실제 도입에서는 고려할 점이 많다.

그 중 하나인 on-demand 의 경우, 인스턴스가 필요에 따라 생성된다는 말은 곧 데이터베이스의 connection 이 없으면 인스턴스가 없어진다는 말과 같다. 즉, connection 이 없는 상태에서는 필요한 모든 인스턴스가 준비될 시간이 추가적으로 필요해지는 셈. 이를 Cold Start 라고 하는데, 이로 인한 지연 시간을 줄이기 위한 가이드도 별도로 존재하니, 성능 면에서 무시할 수 없는 비중을 차지하는 것으로 보인다.

ORM 또한 query 작성에 대한 자유도가 떨어진다는 점에서 복잡한 쿼리가 필요한 서비스에는 적합하지 않을 수 있다. 비용 측면에서도, 이 프로젝트처럼 트래픽이 적은 경우에는 오히려 scale-to-zero 가 비용 절감으로 이어지지만, 꾸준한 트래픽이 발생하는 서비스에서는 직접 운영 비용보다 커질 수도 있다. 도입에는 충분한 고려가 필요하다.

Return to List
DateNovember 28, 2025
Tags
ORMdatabaseedge computinginfrastructureserverless
Share article
FacebookFacebook
XX
LinkedInLinkedIn
neon architecture
Prisma Logo